/****************************************************************************
 * apps/graphics/pdcurses/pdc_slk.c
 * Public Domain Curses
 * RCSID("$Id: slk.c,v 1.61 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: slk
 *
 * Synopsis:
 *       int slk_init(int fmt);
 *       int slk_set(int labnum, const char *label, int justify);
 *       int slk_refresh(void);
 *       int slk_noutrefresh(void);
 *       char *slk_label(int labnum);
 *       int slk_clear(void);
 *       int slk_restore(void);
 *       int slk_touch(void);
 *       int slk_attron(const chtype attrs);
 *       int slk_attr_on(const attr_t attrs, void *opts);
 *       int slk_attrset(const chtype attrs);
 *       int slk_attr_set(const attr_t attrs, short color_pair, void *opts);
 *       int slk_attroff(const chtype attrs);
 *       int slk_attr_off(const attr_t attrs, void *opts);
 *       int slk_color(short color_pair);
 *
 *       int slk_wset(int labnum, const wchar_t *label, int justify);
 *
 *       int PDC_mouse_in_slk(int y, int x);
 *       void PDC_slk_free(void);
 *       void PDC_slk_initialize(void);
 *
 *       wchar_t *slk_wlabel(int labnum)
 *
 * Description:
 *       These functions manipulate a window that contain Soft Label Keys
 *       (SLK). To use the SLK functions, a call to slk_init() must be
 *       made BEFORE initscr() or newterm(). slk_init() removes 1 or 2
 *       lines from the useable screen, depending on the format selected.
 *
 *       The line(s) removed from the screen are used as a separate
 *       window, in which SLKs are displayed.
 *
 *       slk_init() requires a single parameter which describes the
 *       format of the SLKs as follows:
 *
 *               0       3-2-3 format
 *               1       4-4 format
 *               2       4-4-4 format (ncurses extension)
 *               3       4-4-4 format with index line (ncurses extension)
 *                       2 lines used
 *               55      5-5 format (pdcurses format)
 *
 *       slk_refresh(), slk_noutrefresh() and slk_touch() are analogous
 *       to refresh(), noutrefresh() and touch().
 *
 * Return Value:
 *       All functions return OK on success and ERR on error.
 *
 * Portability                                X/Open    BSD    SYS V
 *       slk_init                                Y       -       Y
 *       slk_set                                 Y       -       Y
 *       slk_refresh                             Y       -       Y
 *       slk_noutrefresh                         Y       -       Y
 *       slk_label                               Y       -       Y
 *       slk_clear                               Y       -       Y
 *       slk_restore                             Y       -       Y
 *       slk_touch                               Y       -       Y
 *       slk_attron                              Y       -       Y
 *       slk_attrset                             Y       -       Y
 *       slk_attroff                             Y       -       Y
 *       slk_attr_on                             Y
 *       slk_attr_set                            Y
 *       slk_attr_off                            Y
 *       slk_wset                                Y
 *       PDC_mouse_in_slk                        -       -       -
 *       PDC_slk_free                            -       -       -
 *       PDC_slk_initialize                      -       -       -
 *       slk_wlabel                              -       -       -
 */

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

#include <stdlib.h>

#include "curspriv.h"

/****************************************************************************
 * Private Types
 ****************************************************************************/

enum
{
  LABEL_NORMAL = 8, LABEL_EXTENDED = 10, LABEL_NCURSES_EXTENDED = 12
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

static int label_length = 0;
static int labels = 0;
static int label_fmt = 0;
static int label_line = 0;
static bool hidden = false;

static struct SLK
{
  chtype label[32];
  int len;
  int format;
  int start_col;
} *slk = (struct SLK *)NULL;

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

/* slk_init() is the slk initialization routine.
 * This must be called before initscr().
 *
 * label_fmt = 0, 1 or 55.
 *     0 = 3-2-3 format
 *     1 = 4 - 4 format
 *     2 = 4-4-4 format (ncurses extension for PC 12 function keys)
 *     3 = 4-4-4 format (ncurses extension for PC 12 function keys -
 *  with index line)
 *    55 = 5 - 5 format (extended for PC, 10 function keys)
 */

int slk_init(int fmt)
{
  PDC_LOG(("slk_init() - called\n"));

  if (SP)
    {
      return ERR;
    }

  switch (fmt)
    {
    case 0:                    /* 3 - 2 - 3 */
      labels = LABEL_NORMAL;
      break;

    case 1:                    /* 4 - 4 */
      labels = LABEL_NORMAL;
      break;

    case 2:                    /* 4 4 4 */
      labels = LABEL_NCURSES_EXTENDED;
      break;

    case 3:                    /* 4 4 4 with index */
      labels = LABEL_NCURSES_EXTENDED;
      break;

    case 55:                   /* 5 - 5 */
      labels = LABEL_EXTENDED;
      break;

    default:
      return ERR;
    }

  label_fmt = fmt;

  slk = calloc(labels, sizeof(struct SLK));
  if (!slk)
    {
      labels = 0;
    }

  return slk ? OK : ERR;
}

/* Draw a single button */

static void _drawone(int num)
{
  int col;
  int slen;
  int i;

  if (hidden)
    {
      return;
    }

  slen = slk[num].len;

  switch (slk[num].format)
    {
    case 0:                    /* LEFT */
      col = 0;
      break;

    case 1:                    /* CENTER */
      col = (label_length - slen) / 2;

      if (col + slen > label_length)
        {
          --col;
        }

      break;

    default:                   /* RIGHT */
      col = label_length - slen;
      break;
    }

  wmove(SP->slk_winptr, label_line, slk[num].start_col);

  for (i = 0; i < label_length; ++i)
    {
      waddch(SP->slk_winptr, (i >= col && i < (col + slen)) ?
             slk[num].label[i - col] : ' ');
    }
}

/* Redraw each button */

static void _redraw(void)
{
  int i;

  for (i = 0; i < labels; ++i)
    {
      _drawone(i);
    }
}

/* slk_set() Used to set a slk label to a string.
 *
 * labnum  = 1 - 8 (or 10) (number of the label)
 * label   = string (8 or 7 bytes total), or NULL
 * justify = 0 : left, 1 : center, 2 : right
 */

int slk_set(int labnum, const char *label, int justify)
{
#ifdef CONFIG_PDCURSES_WIDE
  wchar_t wlabel[32];

  PDC_mbstowcs(wlabel, label, 31);
  return slk_wset(labnum, wlabel, justify);
#else
  PDC_LOG(("slk_set() - called\n"));

  if (labnum < 1 || labnum > labels || justify < 0 || justify > 2)
    {
      return ERR;
    }

  labnum--;

  if (!label || !(*label))
    {
      /* Clear the label */

      *slk[labnum].label = 0;
      slk[labnum].format = 0;
      slk[labnum].len = 0;
    }
  else
    {
      int i;
      int j = 0;

      /* Skip leading spaces */

      while (label[j] == ' ')
        {
          j++;
        }

      /* Copy it */

      for (i = 0; i < label_length; i++)
        {
          chtype ch = label[i + j];

          slk[labnum].label[i] = ch;

          if (!ch)
            {
              break;
            }
        }

      /* Drop trailing spaces */

      while ((i + j) && (label[i + j - 1] == ' '))
        {
          i--;
        }

      slk[labnum].label[i] = 0;
      slk[labnum].format   = justify;
      slk[labnum].len      = i;
    }

  _drawone(labnum);
  return OK;
#endif
}

int slk_refresh(void)
{
  PDC_LOG(("slk_refresh() - called\n"));

  return (slk_noutrefresh() == ERR) ? ERR : doupdate();
}

int slk_noutrefresh(void)
{
  PDC_LOG(("slk_noutrefresh() - called\n"));

  return wnoutrefresh(SP->slk_winptr);
}

char *slk_label(int labnum)
{
  static char temp[33];
#ifdef CONFIG_PDCURSES_WIDE
  wchar_t *wtemp = slk_wlabel(labnum);

  PDC_wcstombs(temp, wtemp, 32);
#else
  chtype *p;
  int i;

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

  if (labnum < 1 || labnum > labels)
    {
      return (char *)0;
    }

  for (i = 0, p = slk[labnum - 1].label; *p; i++)
    {
      temp[i] = *p++;
    }

  temp[i] = '\0';
#endif

  return temp;
}

int slk_clear(void)
{
  PDC_LOG(("slk_clear() - called\n"));

  hidden = true;
  werase(SP->slk_winptr);
  return wrefresh(SP->slk_winptr);
}

int slk_restore(void)
{
  PDC_LOG(("slk_restore() - called\n"));

  hidden = false;
  _redraw();
  return wrefresh(SP->slk_winptr);
}

int slk_touch(void)
{
  PDC_LOG(("slk_touch() - called\n"));

  return touchwin(SP->slk_winptr);
}

int slk_attron(const chtype attrs)
{
  int rc;

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

  rc = wattron(SP->slk_winptr, attrs);
  _redraw();
  return rc;
}

int slk_attr_on(const attr_t attrs, void *opts)
{
  PDC_LOG(("slk_attr_on() - called\n"));

  return slk_attron(attrs);
}

int slk_attroff(const chtype attrs)
{
  int rc;

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

  rc = wattroff(SP->slk_winptr, attrs);
  _redraw();
  return rc;
}

int slk_attr_off(const attr_t attrs, void *opts)
{
  PDC_LOG(("slk_attr_off() - called\n"));

  return slk_attroff(attrs);
}

int slk_attrset(const chtype attrs)
{
  int rc;

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

  rc = wattrset(SP->slk_winptr, attrs);
  _redraw();
  return rc;
}

int slk_color(short color_pair)
{
  int rc;

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

  rc = wcolor_set(SP->slk_winptr, color_pair, NULL);
  _redraw();
  return rc;
}

int slk_attr_set(const attr_t attrs, short color_pair, void *opts)
{
  PDC_LOG(("slk_attr_set() - called\n"));

  return slk_attrset(attrs | COLOR_PAIR(color_pair));
}

static void _slk_calc(void)
{
  int i, center, col = 0;
  label_length = COLS / labels;

  if (label_length > 31)
    {
      label_length = 31;
    }

  switch (label_fmt)
    {
    case 0:                    /* 3 - 2 - 3 F-Key layout */

      --label_length;

      slk[0].start_col = col;
      slk[1].start_col = (col += label_length);
      slk[2].start_col = (col += label_length);

      center = COLS / 2;

      slk[3].start_col = center - label_length + 1;
      slk[4].start_col = center + 1;

      col = COLS - (label_length * 3) + 1;

      slk[5].start_col = col;
      slk[6].start_col = (col += label_length);
      slk[7].start_col = (col += label_length);
      break;

    case 1:                    /* 4 - 4 F-Key layout */

      for (i = 0; i < 8; i++)
        {
          slk[i].start_col = col;
          col += label_length;

          if (i == 3)
            {
              col = COLS - (label_length * 4) + 1;
            }
        }

      break;

    case 2:                    /* 4 4 4 F-Key layout */
    case 3:                    /* 4 4 4 F-Key layout with index */

      for (i = 0; i < 4; i++)
        {
          slk[i].start_col = col;
          col += label_length;
        }

      center = COLS / 2;

      slk[4].start_col = center - (label_length * 2) + 1;
      slk[5].start_col = center - label_length - 1;
      slk[6].start_col = center + 1;
      slk[7].start_col = center + label_length + 1;

      col = COLS - (label_length * 4) + 1;

      for (i = 8; i < 12; i++)
        {
          slk[i].start_col = col;
          col += label_length;
        }

      break;

    default:                   /* 5 - 5 F-Key layout */

      for (i = 0; i < 10; i++)
        {
          slk[i].start_col = col;
          col += label_length;

          if (i == 4)
            {
              col = COLS - (label_length * 5) + 1;
            }
        }
    }

  --label_length;

  /* make sure labels are all in window */

  _redraw();
}

void PDC_slk_initialize(void)
{
  if (slk)
    {
      if (label_fmt == 3)
        {
          SP->slklines = 2;
          label_line = 1;
        }
      else
        {
          SP->slklines = 1;
        }

      if (!SP->slk_winptr)
        {
          if (!(SP->slk_winptr = newwin(SP->slklines, COLS,
                                        LINES - SP->slklines, 0)))
            {
              return;
            }

          wattrset(SP->slk_winptr, A_REVERSE);
        }

      _slk_calc();

      /* If we have an index line, display it now */

      if (label_fmt == 3)
        {
          chtype save_attr;
          int i;

          save_attr = SP->slk_winptr->_attrs;
          wattrset(SP->slk_winptr, A_NORMAL);
          wmove(SP->slk_winptr, 0, 0);
          whline(SP->slk_winptr, 0, COLS);

          for (i = 0; i < labels; i++)
            {
              mvwprintw(SP->slk_winptr, 0, slk[i].start_col, "F%d", i + 1);
            }

          SP->slk_winptr->_attrs = save_attr;
        }

      touchwin(SP->slk_winptr);
    }
}

void PDC_slk_free(void)
{
  if (slk)
    {
      if (SP->slk_winptr)
        {
          delwin(SP->slk_winptr);
          SP->slk_winptr = (WINDOW *) NULL;
        }

      free(slk);
      slk = (struct SLK *)NULL;

      label_length = 0;
      labels       = 0;
      label_fmt    = 0;
      label_line   = 0;
      hidden       = false;
    }
}

int PDC_mouse_in_slk(int y, int x)
{
  int i;

  PDC_LOG(("PDC_mouse_in_slk() - called: y->%d x->%d\n", y, x));

  /* If the line on which the mouse was clicked is NOT the last line of the
   * screen, we are not interested in it.
   */

  if (!slk || !SP->slk_winptr || (y != SP->slk_winptr->_begy + label_line))
    {
      return 0;
    }

  for (i = 0; i < labels; i++)
    {
      if (x >= slk[i].start_col && x < (slk[i].start_col + label_length))
        {
          return i + 1;
        }
    }

  return 0;
}

#ifdef CONFIG_PDCURSES_WIDE
int slk_wset(int labnum, const wchar_t *label, int justify)
{
  PDC_LOG(("slk_wset() - called\n"));

  if (labnum < 1 || labnum > labels || justify < 0 || justify > 2)
    {
      return ERR;
    }

  labnum--;

  if (!label || !(*label))
    {
      /* Clear the label */

      *slk[labnum].label = 0;
      slk[labnum].format = 0;
      slk[labnum].len = 0;
    }
  else
    {
      int i, j = 0;

      /* Skip leading spaces */

      while (label[j] == L' ')
        {
          j++;
        }

      /* Copy it */

      for (i = 0; i < label_length; i++)
        {
          chtype ch = label[i + j];

          slk[labnum].label[i] = ch;

          if (!ch)
            {
              break;
            }
        }

      /* Drop trailing spaces */

      while ((i + j) && (label[i + j - 1] == L' '))
        {
          i--;
        }

      slk[labnum].label[i] = 0;
      slk[labnum].format = justify;
      slk[labnum].len = i;
    }

  _drawone(labnum);
  return OK;
}

wchar_t *slk_wlabel(int labnum)
{
  static wchar_t temp[33];
  chtype *p;
  int i;

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

  if (labnum < 1 || labnum > labels)
    {
      return (wchar_t *) 0;
    }

  for (i = 0, p = slk[labnum - 1].label; *p; i++)
    {
      temp[i] = *p++;
    }

  temp[i] = '\0';
  return temp;
}
#endif
