/****************************************************************************
 * apps/graphics/pdcurses/pdc_window.c
 * Public Domain Curses
 * RCSID("$Id: window.c,v 1.62 2008/07/13 16:08:18 wmcbrine Exp $")
 *
 *   Copyright (C) 2017 Gregory Nutt. All rights reserved.
 *   Adapted by: Gregory Nutt <gnutt@nuttx.org>
 *
 * Adapted from the original public domain pdcurses by Gregory Nutt and
 * released as part of NuttX under the 3-clause BSD license:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/* Name: window
 *
 * Synopsis:
 *       WINDOW *newwin(int nlines, int ncols, int begy, int begx);
 *       WINDOW *derwin(WINDOW* orig, int nlines, int ncols,
 *               int begy, int begx);
 *       WINDOW *subwin(WINDOW* orig, int nlines, int ncols,
 *               int begy, int begx);
 *       WINDOW *dupwin(WINDOW *win);
 *       int delwin(WINDOW *win);
 *       int mvwin(WINDOW *win, int y, int x);
 *       int mvderwin(WINDOW *win, int pary, int parx);
 *       int syncok(WINDOW *win, bool bf);
 *       void wsyncup(WINDOW *win);
 *       void wcursyncup(WINDOW *win);
 *       void wsyncdown(WINDOW *win);
 *
 *       WINDOW *resize_window(WINDOW *win, int nlines, int ncols);
 *       int wresize(WINDOW *win, int nlines, int ncols);
 *       WINDOW *PDC_makelines(WINDOW *win);
 *       WINDOW *PDC_makenew(int nlines, int ncols, int begy, int begx);
 *       void PDC_sync(WINDOW *win);
 *
 * Description:
 *       newwin() creates a new window with the given number of lines,
 *       nlines and columns, ncols. The upper left corner of the window
 *       is at line begy, column begx. If nlines is zero, it defaults to
 *       LINES - begy; ncols to COLS - begx. Create a new full-screen
 *       window by calling newwin(0, 0, 0, 0).
 *
 *       delwin() deletes the named window, freeing all associated
 *       memory. In the case of overlapping windows, subwindows should be
 *       deleted before the main window.
 *
 *       mvwin() moves the window so that the upper left-hand corner is
 *       at position (y,x). If the move would cause the window to be off
 *       the screen, it is an error and the window is not moved. Moving
 *       subwindows is allowed.
 *
 *       subwin() creates a new subwindow within a window.  The
 *       dimensions of the subwindow are nlines lines and ncols columns.
 *       The subwindow is at position (begy, begx) on the screen.  This
 *       position is relative to the screen, and not to the window orig.
 *       Changes made to either window will affect both.  When using this
 *       routine, you will often need to call touchwin() before calling
 *       wrefresh().
 *
 *       derwin() is the same as subwin(), except that begy and begx are
 *       relative to the origin of the window orig rather than the
 *       screen.  There is no difference between subwindows and derived
 *       windows.
 *
 *       mvderwin() moves a derived window (or subwindow) inside its
 *       parent window.  The screen-relative parameters of the window are
 *       not changed.  This routine is used to display different parts of
 *       the parent window at the same physical position on the screen.
 *
 *       dupwin() creates an exact duplicate of the window win.
 *
 *       wsyncup() causes a touchwin() of all of the window's parents.
 *
 *       If wsyncok() is called with a second argument of true, this
 *       causes a wsyncup() to be called every time the window is
 *       changed.
 *
 *       wcursyncup() causes the current cursor position of all of a
 *       window's ancestors to reflect the current cursor position of the
 *       current window.
 *
 *       wsyncdown() causes a touchwin() of the current window if any of
 *       its parent's windows have been touched.
 *
 *       resize_window() allows the user to resize an existing window. It
 *       returns the pointer to the new window, or NULL on failure.
 *
 *       wresize() is an ncurses-compatible wrapper for resize_window().
 *       Note that, unlike ncurses, it will NOT process any subwindows of
 *       the window. (However, you still can call it _on_ subwindows.) It
 *       returns OK or ERR.
 *
 *       PDC_makenew() allocates all data for a new WINDOW *except the
 *       actual lines themselves. If it's unable to allocate memory for
 *       the window structure, it will free all allocated memory and
 *       return a NULL pointer.
 *
 *       PDC_makelines() allocates the memory for the lines.
 *
 *       PDC_sync() handles wrefresh() and wsyncup() calls when a window
 *       is changed.
 *
 * Return Value:
 *       newwin(), subwin(), derwin() and dupwin() return a pointer
 *       to the new window, or NULL on failure. delwin(), mvwin(),
 *       mvderwin() and syncok() return OK or ERR. wsyncup(),
 *       wcursyncup() and wsyncdown() return nothing.
 *
 * Errors:
 *       It is an error to call resize_window() before calling initscr().
 *       Also, an error will be generated if we fail to create a newly
 *       sized replacement window for curscr, or stdscr. This could
 *       happen when increasing the window size. NOTE: If this happens,
 *       the previously successfully allocated windows are left alone;
 *       i.e., the resize is NOT cancelled for those windows.
 *
 * Portability                                X/Open    BSD    SYS V
 *       newwin                                  Y       Y       Y
 *       delwin                                  Y       Y       Y
 *       mvwin                                   Y       Y       Y
 *       subwin                                  Y       Y       Y
 *       derwin                                  Y       -       Y
 *       mvderwin                                Y       -       Y
 *       dupwin                                  Y       -      4.0
 *       wsyncup                                 Y       -      4.0
 *       syncok                                  Y       -      4.0
 *       wcursyncup                              Y       -      4.0
 *       wsyncdown                               Y       -      4.0
 *       resize_window                           -       -       -
 *       wresize                                 -       -       -
 *       PDC_makelines                           -       -       -
 *       PDC_makenew                             -       -       -
 *       PDC_sync                                -       -       -
 */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <stdlib.h>

#include "curspriv.h"

/****************************************************************************
 * Public functions
 ****************************************************************************/

WINDOW *PDC_makenew(int nlines, int ncols, int begy, int begx)
{
  WINDOW *win;

  PDC_LOG(("PDC_makenew() - called: lines %d cols %d begy %d begx %d\n",
           nlines, ncols, begy, begx));

  /* Allocate the window structure itself */

  if ((win = calloc(1, sizeof(WINDOW))) == (WINDOW *) NULL)
    {
      return win;
    }

  /* Allocate the line pointer array */

  if ((win->_y = malloc(nlines * sizeof(chtype *))) == NULL)
    {
      free(win);
      return (WINDOW *)NULL;
    }

  /* allocate the minchng and maxchng arrays */

  if ((win->_firstch = malloc(nlines * sizeof(int))) == NULL)
    {
      free(win->_y);
      free(win);
      return (WINDOW *)NULL;
    }

  if ((win->_lastch = malloc(nlines * sizeof(int))) == NULL)
    {
      free(win->_firstch);
      free(win->_y);
      free(win);
      return (WINDOW *)NULL;
    }

  /* Initialize window variables */

  win->_maxy  = nlines;         /* real max screen size */
  win->_maxx  = ncols;          /* real max screen size */
  win->_begy  = begy;
  win->_begx  = begx;
  win->_bkgd  = ' ';            /* wrs 4/10/93 -- initialize background to
                                 * blank */
  win->_clear = (bool) ((nlines == LINES) && (ncols == COLS));
  win->_bmarg = nlines - 1;
  win->_parx  = win->_pary = -1;

  /* init to say window all changed */

  touchwin(win);
  return win;
}

WINDOW *PDC_makelines(WINDOW *win)
{
  int nlines;
  int ncols;
  int i;
  int j;

  PDC_LOG(("PDC_makelines() - called\n"));

  if (!win)
    {
      return (WINDOW *)NULL;
    }

  nlines = win->_maxy;
  ncols  = win->_maxx;

  for (i = 0; i < nlines; i++)
    {
      if ((win->_y[i] = malloc(ncols * sizeof(chtype))) == NULL)
        {
          /* If error, free all the data */

          for (j = 0; j < i; j++)
            {
              free(win->_y[j]);
            }

          free(win->_firstch);
          free(win->_lastch);
          free(win->_y);
          free(win);

          return (WINDOW *)NULL;
        }
    }

  return win;
}

void PDC_sync(WINDOW *win)
{
  PDC_LOG(("PDC_sync() - called:\n"));

  if (win->_immed)
    {
      wrefresh(win);
    }

  if (win->_sync)
    {
      wsyncup(win);
    }
}

WINDOW *newwin(int nlines, int ncols, int begy, int begx)
{
  WINDOW *win;

  PDC_LOG(("newwin() - called:lines=%d cols=%d begy=%d begx=%d\n",
           nlines, ncols, begy, begx));

  if (!nlines)
    {
      nlines = LINES - begy;
    }

  if (!ncols)
    {
      ncols = COLS - begx;
    }

  if ((begy + nlines > SP->lines || begx + ncols > SP->cols) ||
      !(win = PDC_makenew(nlines, ncols, begy, begx)) ||
      !(win = PDC_makelines(win)))
    {
      return (WINDOW *)NULL;
    }

  werase(win);
  return win;
}

int delwin(WINDOW *win)
{
  int i;

  PDC_LOG(("delwin() - called\n"));

  if (!win)
    {
      return ERR;
    }

  /* subwindows use parents' lines */

  if (!(win->_flags & (_SUBWIN | _SUBPAD)))
    {
      for (i = 0; i < win->_maxy && win->_y[i]; i++)
        {
          if (win->_y[i])
            {
              free(win->_y[i]);
            }
        }
    }

  free(win->_firstch);
  free(win->_lastch);
  free(win->_y);
  free(win);
  return OK;
}

int mvwin(WINDOW *win, int y, int x)
{
  PDC_LOG(("mvwin() - called\n"));

  if (!win || (y + win->_maxy > LINES || y < 0) ||
      (x + win->_maxx > COLS || x < 0))
    {
      return ERR;
    }

  win->_begy = y;
  win->_begx = x;
  touchwin(win);
  return OK;
}

WINDOW *subwin(WINDOW *orig, int nlines, int ncols, int begy, int begx)
{
  WINDOW *win;
  int i;
  int j = begy - orig->_begy;
  int k = begx - orig->_begx;

  PDC_LOG(("subwin() - called: lines %d cols %d begy %d begx %d\n",
           nlines, ncols, begy, begx));

  /* Make sure window fits inside the original one */

  if (!orig || (begy < orig->_begy) || (begx < orig->_begx) ||
      (begy + nlines) > (orig->_begy + orig->_maxy) ||
      (begx + ncols) > (orig->_begx + orig->_maxx))
    {
      return (WINDOW *)NULL;
    }

  if (!nlines)
    {
      nlines = orig->_maxy - 1 - j;
    }

  if (!ncols)
    {
     ncols = orig->_maxx - 1 - k;
    }

  if (!(win = PDC_makenew(nlines, ncols, begy, begx)))
    {
      return (WINDOW *)NULL;
    }

  /* Initialize window variables */

  win->_attrs      = orig->_attrs;
  win->_bkgd       = orig->_bkgd;
  win->_leaveit    = orig->_leaveit;
  win->_scroll     = orig->_scroll;
  win->_nodelay    = orig->_nodelay;
  win->_use_keypad = orig->_use_keypad;
  win->_immed      = orig->_immed;
  win->_sync       = orig->_sync;
  win->_pary       = j;
  win->_parx       = k;
  win->_parent     = orig;

  for (i = 0; i < nlines; i++, j++)
    {
      win->_y[i] = orig->_y[j] + k;
    }

  win->_flags |= _SUBWIN;
  return win;
}

WINDOW *derwin(WINDOW *orig, int nlines, int ncols, int begy, int begx)
{
  return subwin(orig, nlines, ncols, begy + orig->_begy, begx + orig->_begx);
}

int mvderwin(WINDOW *win, int pary, int parx)
{
  int i, j;
  WINDOW *mypar;

  if (!win || !(win->_parent))
    {
      return ERR;
    }

  mypar = win->_parent;

  if (pary < 0 || parx < 0 || (pary + win->_maxy) > mypar->_maxy ||
      (parx + win->_maxx) > mypar->_maxx)
    {
      return ERR;
    }

  j = pary;

  for (i = 0; i < win->_maxy; i++)
    {
      win->_y[i] = (mypar->_y[j++]) + parx;
    }

  win->_pary = pary;
  win->_parx = parx;
  return OK;
}

WINDOW *dupwin(WINDOW *win)
{
  WINDOW *new;
  chtype *ptr;
  chtype *ptr1;
  int nlines;
  int ncols;
  int begy;
  int begx;
  int i;

  if (!win)
    {
      return (WINDOW *) NULL;
    }

  nlines = win->_maxy;
  ncols = win->_maxx;
  begy = win->_begy;
  begx = win->_begx;

  if (!(new = PDC_makenew(nlines, ncols, begy, begx)) ||
      !(new = PDC_makelines(new)))
    {
      return (WINDOW *) NULL;
    }

  /* copy the contents of win into new */

  for (i = 0; i < nlines; i++)
    {
      for (ptr = new->_y[i], ptr1 = win->_y[i];
           ptr < new->_y[i] + ncols; ptr++, ptr1++)
        {
          *ptr = *ptr1;
        }

      new->_firstch[i] = 0;
      new->_lastch[i] = ncols - 1;
    }

  new->_curx       = win->_curx;
  new->_cury       = win->_cury;
  new->_maxy       = win->_maxy;
  new->_maxx       = win->_maxx;
  new->_begy       = win->_begy;
  new->_begx       = win->_begx;
  new->_flags      = win->_flags;
  new->_attrs      = win->_attrs;
  new->_clear      = win->_clear;
  new->_leaveit    = win->_leaveit;
  new->_scroll     = win->_scroll;
  new->_nodelay    = win->_nodelay;
  new->_use_keypad = win->_use_keypad;
  new->_tmarg      = win->_tmarg;
  new->_bmarg      = win->_bmarg;
  new->_parx       = win->_parx;
  new->_pary       = win->_pary;
  new->_parent     = win->_parent;
  new->_bkgd       = win->_bkgd;
  new->_flags      = win->_flags;

  return new;
}

WINDOW *resize_window(WINDOW *win, int nlines, int ncols)
{
  WINDOW *new;
  int save_curx;
  int save_cury;
  int new_begx;
  int new_begy;
  int i;

  PDC_LOG(("resize_window() - called: nlines %d ncols %d\n", nlines, ncols));

  if (!win)
    {
      return (WINDOW *)NULL;
    }

  if (win->_flags & _SUBPAD)
    {
      if (!(new = subpad(win->_parent, nlines, ncols, win->_begy, win->_begx)))
        {
          return (WINDOW *)NULL;
        }
    }
  else if (win->_flags & _SUBWIN)
    {
      if (!(new = subwin(win->_parent, nlines, ncols, win->_begy, win->_begx)))
        {
          return (WINDOW *)NULL;
        }
    }
  else
    {
      if (win == SP->slk_winptr)
        {
          new_begy = SP->lines - SP->slklines;
          new_begx = 0;
        }
      else
        {
          new_begy = win->_begy;
          new_begx = win->_begx;
        }

      if (!(new = PDC_makenew(nlines, ncols, new_begy, new_begx)))
        {
          return (WINDOW *)NULL;
        }
    }

  save_curx = min(win->_curx, new->_maxx);
  save_cury = min(win->_cury, new->_maxy);

  if (!(win->_flags & (_SUBPAD | _SUBWIN)))
    {
      if (!(new = PDC_makelines(new)))
        {
          return (WINDOW *) NULL;
        }

      werase(new);

      copywin(win, new, 0, 0, 0, 0, min(win->_maxy, new->_maxy) - 1,
              min(win->_maxx, new->_maxx) - 1, false);

      for (i = 0; i < win->_maxy && win->_y[i]; i++)
        {
          if (win->_y[i])
            {
              free(win->_y[i]);
            }
        }
    }

  new->_flags      = win->_flags;
  new->_attrs      = win->_attrs;
  new->_clear      = win->_clear;
  new->_leaveit    = win->_leaveit;
  new->_scroll     = win->_scroll;
  new->_nodelay    = win->_nodelay;
  new->_use_keypad = win->_use_keypad;
  new->_tmarg      = (win->_tmarg > new->_maxy - 1) ? 0 : win->_tmarg;
  new->_bmarg      = (win->_bmarg == win->_maxy - 1) ?
                     new->_maxy - 1 : min(win->_bmarg, (new->_maxy - 1));
  new->_parent     = win->_parent;
  new->_immed      = win->_immed;
  new->_sync       = win->_sync;
  new->_bkgd       = win->_bkgd;
  new->_curx       = save_curx;
  new->_cury       = save_cury;

  free(win->_firstch);
  free(win->_lastch);
  free(win->_y);

  *win = *new;
  free(new);
  return win;
}

int wresize(WINDOW *win, int nlines, int ncols)
{
  return (resize_window(win, nlines, ncols) ? OK : ERR);
}

void wsyncup(WINDOW *win)
{
  WINDOW *tmp;

  PDC_LOG(("wsyncup() - called\n"));

  for (tmp = win; tmp; tmp = tmp->_parent)
    {
      touchwin(tmp);
    }
}

int syncok(WINDOW *win, bool bf)
{
  PDC_LOG(("syncok() - called\n"));

  if (!win)
    {
      return ERR;
    }

  win->_sync = bf;

  return OK;
}

void wcursyncup(WINDOW *win)
{
  WINDOW *tmp;

  PDC_LOG(("wcursyncup() - called\n"));

  for (tmp = win; tmp && tmp->_parent; tmp = tmp->_parent)
    {
      wmove(tmp->_parent, tmp->_pary + tmp->_cury, tmp->_parx + tmp->_curx);
    }
}

void wsyncdown(WINDOW *win)
{
  WINDOW *tmp;

  PDC_LOG(("wsyncdown() - called\n"));

  for (tmp = win; tmp; tmp = tmp->_parent)
    {
      if (is_wintouched(tmp))
        {
          touchwin(win);
          break;
        }
    }
}
