/****************************************************************************
 * apps/graphics/pdcurses/pdc_panel.c
 * Public Domain Curses
 * RCSID("$Id: panel.c,v 1.8 2008/07/14 12:35:23 wmcbrine Exp $")
 *
 *   Copyright (C) 2017 Gregory Nutt. All rights reserved.
 *   Original Author: Warren Tucker <wht@n4hgf.mt-park.ga.us>
 *   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: panel
 *
 * Synopsis:
 *       int bottom_panel(PANEL *pan);
 *       int del_panel(PANEL *pan);
 *       int hide_panel(PANEL *pan);
 *       int move_panel(PANEL *pan, int starty, int startx);
 *       PANEL *new_panel(WINDOW *win);
 *       PANEL *panel_above(const PANEL *pan);
 *       PANEL *panel_below(const PANEL *pan);
 *       int panel_hidden(const PANEL *pan);
 *       const void *panel_userptr(const PANEL *pan);
 *       WINDOW *panel_window(const PANEL *pan);
 *       int replace_panel(PANEL *pan, WINDOW *win);
 *       int set_panel_userptr(PANEL *pan, const void *uptr);
 *       int show_panel(PANEL *pan);
 *       int top_panel(PANEL *pan);
 *       void update_panels(void);
 *
 * Description:
 *       The panel library is built using the curses library, and any
 *       program using panels routines must call one of the curses
 *       initialization routines such as initscr(). A program using these
 *       routines must be linked with the panels and curses libraries.
 *       The header panel.h includes the header curses.h.
 *
 *       The panels package gives the applications programmer a way to
 *       have depth relationships between curses windows; a curses window
 *       is associated with every panel. The panels routines allow curses
 *       windows to overlap without making visible the overlapped
 *       portions of underlying windows. The initial curses window,
 *       stdscr, lies beneath all panels. The set of currently visible
 *       panels is the 'deck' of panels.
 *
 *       The panels package allows the applications programmer to create
 *       panels, fetch and set their associated windows, shuffle panels
 *       in the deck, and manipulate panels in other ways.
 *
 *       bottom_panel() places pan at the bottom of the deck. The size,
 *       location and contents of the panel are unchanged.
 *
 *       del_panel() deletes pan, but not its associated winwow.
 *
 *       hide_panel() removes a panel from the deck and thus hides it
 *       from view.
 *
 *       move_panel() moves the curses window associated with pan, so
 *       that its upper lefthand corner is at the supplied coordinates.
 *       (Do not use mvwin() on the window.)
 *
 *       new_panel() creates a new panel associated with win and returns
 *       the panel pointer. The new panel is placed at the top of the
 *       deck.
 *
 *       panel_above() returns a pointer to the panel in the deck above
 *       pan, or NULL if pan is the top panel. If the value of pan passed
 *       is NULL, this function returns a pointer to the bottom panel in
 *       the deck.
 *
 *       panel_below() returns a pointer to the panel in the deck below
 *       pan, or NULL if pan is the bottom panel. If the value of pan
 *       passed is NULL, this function returns a pointer to the top panel
 *       in the deck.
 *
 *       panel_hidden() returns OK if pan is hidden and ERR if it is not.
 *
 *       panel_userptr() - Each panel has a user pointer available for
 *       maintaining relevant information. This function returns a
 *       pointer to that information previously set up by
 *       set_panel_userptr().
 *
 *       panel_window() returns a pointer to the curses window associated
 *       with the panel.
 *
 *       replace_panel() replaces the current window of pan with win.
 *
 *       set_panel_userptr() - Each panel has a user pointer available
 *       for maintaining relevant information. This function sets the
 *       value of that information.
 *
 *       show_panel() makes a previously hidden panel visible and places
 *       it back in the deck on top.
 *
 *       top_panel() places pan on the top of the deck. The size,
 *       location and contents of the panel are unchanged.
 *
 *       update_panels() refreshes the virtual screen to reflect the
 *       depth relationships between the panels in the deck. The user
 *       must use doupdate() to refresh the physical screen.
 *
 * Return Value:
 *       Each routine that returns a pointer to an object returns NULL if
 *       an error occurs. Each panel routine that returns an integer,
 *       returns OK if it executes successfully and ERR if it does not.
 *
 * Portability                                X/Open    BSD    SYS V
 *       bottom_panel                            -       -       Y
 *       del_panel                               -       -       Y
 *       hide_panel                              -       -       Y
 *       move_panel                              -       -       Y
 *       new_panel                               -       -       Y
 *       panel_above                             -       -       Y
 *       panel_below                             -       -       Y
 *       panel_hidden                            -       -       Y
 *       panel_userptr                           -       -       Y
 *       panel_window                            -       -       Y
 *       replace_panel                           -       -       Y
 *       set_panel_userptr                       -       -       Y
 *       show_panel                              -       -       Y
 *       top_panel                               -       -       Y
 *       update_panels                           -       -       Y
 */

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

#include <stdlib.h>

#include "graphics/panel.h"
#include "curspriv.h"

/****************************************************************************
 * Public Data
 ****************************************************************************/

PANEL *_bottom_panel = (PANEL *) 0;
PANEL *_top_panel = (PANEL *) 0;
PANEL _stdscr_pseudo_panel = { (WINDOW *) 0 };

/****************************************************************************
 * Private Functions
 ****************************************************************************/

#ifdef CONFIG_PDCURSES_PANEL_DEBUG
static void dpanel(char *text, PANEL *pan)
{
  PDC_LOG(("%s id=%s b=%s a=%s y=%d x=%d", text, pan->user,
           pan->below ? pan->below->user : "--",
           pan->above ? pan->above->user : "--", pan->wstarty, pan->wstartx));
}

static void dstack(char *fmt, int num, PANEL *pan)
{
  char s80[80];

  sprintf(s80, fmt, num, pan);
  PDC_LOG(("%s b=%s t=%s", s80, _bottom_panel ? _bottom_panel->user : "--",
           _top_panel ? _top_panel->user : "--"));

  if (pan)
    {
      PDC_LOG(("pan id=%s", pan->user));
    }

  pan = _bottom_panel;

  while (pan)
    {
      dpanel("stk", pan);
      pan = pan->above;
    }
}

/* Debugging hook for wnoutrefresh */

static void dwnoutrefresh(PANEL *pan)
{
  dpanel("wnoutrefresh", pan);
  wnoutrefresh(pan->win);
}

static void dtouchwin(PANEL *pan)
{
  dpanel("dtouchwin", pan);
  touchwin(pan->win);
}

static void dtouchline(PANEL *pan, int start, int count)
{
  char s80[80];

  sprintf(s80, "dtouchline s=%d c=%d", start, count);
  dpanel(s80, pan);
  touchline(pan->win, start, count);
}

#else
#  define dpanel(text, pan)
#  define dstack(fmt, num, pan)
#  define dwnoutrefresh(pan) wnoutrefresh((pan)->win)
#  define dtouchwin(pan) touchwin((pan)->win)
#  define dtouchline(pan, start, count) touchline((pan)->win, start, count)
#endif

static bool _panels_overlapped(PANEL * pan1, PANEL * pan2)
{
  if (!pan1 || !pan2)
    {
      return false;
    }

  return ((pan1->wstarty >= pan2->wstarty && pan1->wstarty < pan2->wendy)
          || (pan2->wstarty >= pan1->wstarty && pan2->wstarty < pan1->wendy))
    && ((pan1->wstartx >= pan2->wstartx && pan1->wstartx < pan2->wendx)
        || (pan2->wstartx >= pan1->wstartx && pan2->wstartx < pan1->wendx));
}

static void _free_obscure(PANEL *pan)
{
  PANELOBS *tobs = pan->obscure;  /* "this" one */
  PANELOBS *nobs;                 /* "next" one */

  while (tobs)
    {
      nobs = tobs->above;
      free((char *)tobs);
      tobs = nobs;
    }

  pan->obscure = (PANELOBS *) 0;
}

static void _override(PANEL *pan, int show)
{
  int y;
  PANEL *pan2;
  PANELOBS *tobs = pan->obscure;        /* "this" one */

  if (show == 1)
    {
      dtouchwin(pan);
    }
  else if (!show)
    {
      dtouchwin(pan);
      dtouchwin(&_stdscr_pseudo_panel);
    }
  else if (show == -1)
    {
      while (tobs && (tobs->pan != pan))
        {
          tobs = tobs->above;
        }
    }

  while (tobs)
    {
      if ((pan2 = tobs->pan) != pan)
        {
          for (y = pan->wstarty; y < pan->wendy; y++)
            {
              if ((y >= pan2->wstarty) && (y < pan2->wendy) &&
                  ((is_linetouched(pan->win, y - pan->wstarty)) ||
                   (is_linetouched(stdscr, y))))
                {
                  dtouchline(pan2, y - pan2->wstarty, 1);
                }
            }
        }

      tobs = tobs->above;
    }
}

static void _calculate_obscure(void)
{
  PANEL *pan, *pan2;
  PANELOBS *tobs;               /* "this" one */
  PANELOBS *lobs;               /* last one */

  pan = _bottom_panel;

  while (pan)
    {
      if (pan->obscure)
        {
          _free_obscure(pan);
        }

      lobs = (PANELOBS *) 0;
      pan2 = _bottom_panel;

      while (pan2)
        {
          if (_panels_overlapped(pan, pan2))
            {
              if ((tobs = malloc(sizeof(PANELOBS))) == NULL)
                {
                  return;
                }

              tobs->pan = pan2;
              dpanel("obscured", pan2);
              tobs->above = (PANELOBS *) 0;

              if (lobs)
                {
                  lobs->above = tobs;
                }
              else
                {
                  pan->obscure = tobs;
                }

              lobs = tobs;
            }

          pan2 = pan2->above;
        }

      _override(pan, 1);
      pan = pan->above;
    }
}

/* Check to see if panel is in the stack */

static bool _panel_is_linked(const PANEL *pan)
{
  PANEL *pan2 = _bottom_panel;

  while (pan2)
    {
      if (pan2 == pan)
        {
          return true;
        }

      pan2 = pan2->above;
    }

  return false;
}

/* Link panel into stack at top */

static void _panel_link_top(PANEL *pan)
{
#ifdef CONFIG_PDCURSES_PANEL_DEBUG
  dstack("<lt%d>", 1, pan);
  if (_panel_is_linked(pan))
    {
      return;
    }
#endif

  pan->above = (PANEL *) 0;
  pan->below = (PANEL *) 0;

  if (_top_panel)
    {
      _top_panel->above = pan;
      pan->below = _top_panel;
    }

  _top_panel = pan;

  if (!_bottom_panel)
    {
      _bottom_panel = pan;
    }

  _calculate_obscure();
  dstack("<lt%d>", 9, pan);
}

/* Link panel into stack at bottom */

static void _panel_link_bottom(PANEL *pan)
{
#ifdef CONFIG_PDCURSES_PANEL_DEBUG
  dstack("<lb%d>", 1, pan);
  if (_panel_is_linked(pan))
    {
      return;
    }
#endif

  pan->above = (PANEL *)0;
  pan->below = (PANEL *)0;

  if (_bottom_panel)
    {
      _bottom_panel->below = pan;
      pan->above = _bottom_panel;
    }

  _bottom_panel = pan;

  if (!_top_panel)
    {
      _top_panel = pan;
    }

  _calculate_obscure();
  dstack("<lb%d>", 9, pan);
}

static void _panel_unlink(PANEL *pan)
{
  PANEL *prev;
  PANEL *next;

#ifdef CONFIG_PDCURSES_PANEL_DEBUG
  dstack("<u%d>", 1, pan);
  if (!_panel_is_linked(pan))
    {
      return;
    }
#endif

  _override(pan, 0);
  _free_obscure(pan);

  prev = pan->below;
  next = pan->above;

  /* If non-zero, we will not update the list head */

  if (prev)
    {
      prev->above = next;
      if (next)
        {
          next->below = prev;
        }
    }
  else if (next)
    {
      next->below = prev;
    }

  if (pan == _bottom_panel)
    {
      _bottom_panel = next;
    }

  if (pan == _top_panel)
    {
      _top_panel = prev;
    }

  _calculate_obscure();

  pan->above = (PANEL *) 0;
  pan->below = (PANEL *) 0;
  dstack("<u%d>", 9, pan);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int bottom_panel(PANEL *pan)
{
  if (!pan)
    {
      return ERR;
    }

  if (pan == _bottom_panel)
    {
      return OK;
    }

  if (_panel_is_linked(pan))
    {
      hide_panel(pan);
    }

  _panel_link_bottom(pan);
  return OK;
}

int del_panel(PANEL *pan)
{
  if (pan)
    {
      if (_panel_is_linked(pan))
        {
          hide_panel(pan);
        }

      free((char *)pan);
      return OK;
    }

  return ERR;
}

int hide_panel(PANEL *pan)
{
  if (!pan)
    {
      return ERR;
    }

  if (!_panel_is_linked(pan))
    {
      pan->above = (PANEL *) 0;
      pan->below = (PANEL *) 0;
      return ERR;
    }

  _panel_unlink(pan);
  return OK;
}

int move_panel(PANEL *pan, int starty, int startx)
{
  WINDOW *win;
  int maxx;
  int maxy;

  if (!pan)
    {
      return ERR;
    }

  if (_panel_is_linked(pan))
    {
      _override(pan, 0);
    }

  win = pan->win;

  if (mvwin(win, starty, startx) == ERR)
    {
      return ERR;
    }

  getbegyx(win, pan->wstarty, pan->wstartx);
  getmaxyx(win, maxy, maxx);
  pan->wendy = pan->wstarty + maxy;
  pan->wendx = pan->wstartx + maxx;

  if (_panel_is_linked(pan))
    {
      _calculate_obscure();
    }

  return OK;
}

PANEL *new_panel(WINDOW *win)
{
  PANEL *pan = malloc(sizeof(PANEL));

  if (!_stdscr_pseudo_panel.win)
    {
      _stdscr_pseudo_panel.win = stdscr;
      _stdscr_pseudo_panel.wstarty = 0;
      _stdscr_pseudo_panel.wstartx = 0;
      _stdscr_pseudo_panel.wendy = LINES;
      _stdscr_pseudo_panel.wendx = COLS;
      _stdscr_pseudo_panel.user = "stdscr";
      _stdscr_pseudo_panel.obscure = (PANELOBS *) 0;
    }

  if (pan)
    {
      int maxx;
      int maxy;

      pan->win = win;
      pan->above = (PANEL *) 0;
      pan->below = (PANEL *) 0;
      getbegyx(win, pan->wstarty, pan->wstartx);
      getmaxyx(win, maxy, maxx);
      pan->wendy = pan->wstarty + maxy;
      pan->wendx = pan->wstartx + maxx;
#ifdef CONFIG_PDCURSES_PANEL_DEBUG
      pan->user = "new";
#else
      pan->user = (char *)0;
#endif
      pan->obscure = (PANELOBS *) 0;
      show_panel(pan);
    }

  return pan;
}

PANEL *panel_above(const PANEL *pan)
{
  return pan ? pan->above : _bottom_panel;
}

PANEL *panel_below(const PANEL *pan)
{
  return pan ? pan->below : _top_panel;
}

int panel_hidden(const PANEL *pan)
{
  if (!pan)
    {
      return ERR;
    }

  return _panel_is_linked(pan) ? ERR : OK;
}

const void *panel_userptr(const PANEL *pan)
{
  return pan ? pan->user : NULL;
}

WINDOW *panel_window(const PANEL *pan)
{
  PDC_LOG(("panel_window() - called\n"));

  return pan->win;
}

int replace_panel(PANEL *pan, WINDOW *win)
{
  int maxy, maxx;

  if (!pan)
    {
      return ERR;
    }

  if (_panel_is_linked(pan))
    {
      _override(pan, 0);
    }

  pan->win = win;
  getbegyx(win, pan->wstarty, pan->wstartx);
  getmaxyx(win, maxy, maxx);
  pan->wendy = pan->wstarty + maxy;
  pan->wendx = pan->wstartx + maxx;

  if (_panel_is_linked(pan))
    {
      _calculate_obscure();
    }

  return OK;
}

int set_panel_userptr(PANEL *pan, const void *uptr)
{
  if (!pan)
    {
      return ERR;
    }

  pan->user = uptr;
  return OK;
}

int show_panel(PANEL *pan)
{
  if (!pan)
    {
      return ERR;
    }

  if (pan == _top_panel)
    {
      return OK;
    }

  if (_panel_is_linked(pan))
    {
      hide_panel(pan);
    }

  _panel_link_top(pan);
  return OK;
}

int top_panel(PANEL *pan)
{
  return show_panel(pan);
}

void update_panels(void)
{
  PANEL *pan;

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

  pan = _bottom_panel;

  while (pan)
    {
      _override(pan, -1);
      pan = pan->above;
    }

  if (is_wintouched(stdscr))
    {
      dwnoutrefresh(&_stdscr_pseudo_panel);
    }

  pan = _bottom_panel;

  while (pan)
    {
      if (is_wintouched(pan->win) || !pan->above)
        {
          dwnoutrefresh(pan);
        }

      pan = pan->above;
    }
}
