//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library General Public
//  License along with this program; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307, USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Based on CXL by Mike Smedley.
//  ------------------------------------------------------------------
//  Lets user pick from an array of strings.
//  ------------------------------------------------------------------

#include <gctype.h>
#include <cstdlib>
#include <cstring>
#include <gkbdcode.h>
#include <gmoubase.h>
#include <gtimall.h>
#include <gwinall.h>

extern char m_title[];


//  ------------------------------------------------------------------

int wpickstr_tag = false;


//  ------------------------------------------------------------------
//  define record that will hold pick window info

struct r_t {
  int   numelems;
  int   lastelem;
  int   curr;
  int   first;
  int   last;
  int   strsperline;
  int   strsperwin;
  int   maxstrlen;
  int   wwidth;
  int   wheight;
  int   fillspaces;
  int   gapspaces;
  int   xtraspaces;
  vattr winattr;
  vattr barattr;
  int   scrollbar;
};


//  ------------------------------------------------------------------
//  this function will display the mouse
//  cursor if mouse cursor mode is on

static void show_mouse_cursor_pck() {

  #ifdef GOLD_MOUSE
  if(gmou.FreeCursor()) {
    gmou.ShowCursor();
    gmou.SetCursor(0,0xFFFF,((LGREY_|_LGREY)<<8));
  }
  #endif
}


//  ------------------------------------------------------------------
//  this function will hide the mouse
//  cursor if mouse cursor mode is on

static void hide_mouse_cursor_pck() {

  #ifdef GOLD_MOUSE
  if(gmou.FreeCursor())
    gmou.HideCursor();
  #endif
}


//  ------------------------------------------------------------------
//  this function updates the current item by either
//  displaying or erasing the selection bar on it

static void update_curr( const gstrarray &strarr, r_t* r, int bar)
{
  // calculate row and column string will be displayed
  // at, then print out the string character-by-character

  int crow = (r->curr-r->first)/r->strsperline;
  int temp = r->curr-((crow*r->strsperline)+r->first);
  int ccol = (temp*r->maxstrlen)+((temp+1)*(r->gapspaces))+r->xtraspaces;

  wgotoxy(crow,ccol);

  hide_mouse_cursor_pck();

  wprintns(crow, ccol, bar ? r->barattr : r->winattr, strarr[r->curr], r->maxstrlen);

  show_mouse_cursor_pck();
}


//  ------------------------------------------------------------------

static void update_line(const gstrarray &strarr, r_t* r, int wrow, int upcurr)
{
  int nomore = false;
  int ccol = r->gapspaces + r->xtraspaces;
  int celem = (wrow*r->strsperline) + r->first;

  if(celem >= r->numelems)
    nomore = true;

  for(int j=0; j<r->strsperline; j++) {
    int ccolbeg = ccol;
    wprintns(wrow, ccolbeg, (upcurr and r->curr==celem) ? r->barattr : r->winattr, nomore ? "" : strarr[celem], r->maxstrlen);
    ccol += r->maxstrlen;
    if(++celem >= r->numelems)
      nomore = true;
    ccol += r->gapspaces;
  }
}


//  ------------------------------------------------------------------
//  this function will update all items in the window

static void update_window(const gstrarray &strarr, r_t* r)
{
  hide_mouse_cursor_pck();
  for(int crow=0; crow<r->wheight; crow++)
    update_line(strarr, r, crow, 1);
  show_mouse_cursor_pck();
}


//  ------------------------------------------------------------------
//  this function will find the element number of the
//  first element on the same line as the given element

static int e_begline(r_t* r, int elem) {

  return (elem/r->strsperline) * r->strsperline;
}


//  ------------------------------------------------------------------
//  this function will find the first element in a
//  window, using a given last element as input

static int e_begwin(r_t* r, int lelem) {

  int beg = e_begline(r,lelem) + r->strsperline - r->strsperwin;
  if(beg < 0)
    beg = 0;

  return beg;
}


//  ------------------------------------------------------------------
//  this function will find the element number of the
//  last element on the same line as the given element

static int e_endline(r_t* r, int elem) {

  int end = (((elem/r->strsperline)+1)*r->strsperline)-1;
  if(end>=r->numelems)
    end = r->lastelem;

  return end;
}


//  ------------------------------------------------------------------
//  this function will find the last element in a
//  window, using a given first element as input

static int e_endwin(r_t* r, int felem) {

  int end = felem + r->strsperwin - 1;
  if(end > (r->lastelem))
    end = r->lastelem;

  return end;
}


//  ------------------------------------------------------------------

static void goto_item(r_t* r, const gstrarray &strarr, int elem)
{
  if(elem<0 or elem>r->lastelem)
    elem = 0;
  int outside = (elem<r->first or elem>r->last) ? YES : NO;
  if(outside == NO)
      update_curr(strarr,r,0);
  r->curr = elem;
  if(outside) {
    r->first = e_begwin(r,r->last = e_endwin(r,e_begline(r,r->curr)));
    update_window(strarr,r);
  }
  else {
    update_curr(strarr,r,1);
  }
}


//  ------------------------------------------------------------------
//  this function determines if the mouse cursor is on a item

#ifdef GOLD_MOUSE
static int mouse_on_item(r_t* r, int mcrow, int mccol) {

  int found  = -1;
  int srow   = gwin.active->srow;
  int scol   = gwin.active->scol;
  int border = gwin.active->border;

  for(int i=r->first; i<=r->last; i++) {
    if(mcrow==(srow+border+((i-r->first)/r->strsperline))) {
      int start = scol+border+r->xtraspaces+r->gapspaces+((i%r->strsperline)*(r->maxstrlen+r->gapspaces));
      int end   = start+r->maxstrlen-1;
      if(mccol>=start and mccol<=end) {
        found = i;
        break;
      }
    }
  }
  if(found==-1 and r->scrollbar and mccol==gwin.active->ecol) {
    if(mcrow==srow+1)
      found=-2;
    else {
      if(mcrow==gwin.active->erow-1)
        found=-3;
    }
  }

  return found;
}
#endif


//  ------------------------------------------------------------------

static void page_down(const gstrarray &strarr, r_t* r)
{
  if(r->curr != r->last) {
    r->curr = r->last;
    update_window(strarr, r);
  }
  else if(r->last != (r->lastelem)) {
    int i = r->curr-r->first;
    r->last = e_endwin(r,r->last+1);
    r->first = e_begwin(r, r->last);
    if((r->curr = r->first+i) > (r->lastelem))
      r->curr -= r->strsperline;
    update_window(strarr, r);
  }
}


//  ------------------------------------------------------------------

static void page_up(const gstrarray &strarr, r_t* r)
{
  if(r->curr != r->first) {
    r->curr = r->first;
    update_window(strarr, r);
  }
  else if(r->first) {
    int i = r->curr-r->first;
    r->first = e_begwin(r,r->first-1);
    r->last = e_endwin(r, r->first);
    r->curr = r->first+i;
    update_window(strarr,r);
  }
}


//  ------------------------------------------------------------------

static void scroll_down(const gstrarray &strarr, r_t* r, int upcurr)
{
  if(r->first) {
    hide_mouse_cursor_pck();
    if(upcurr)
      update_curr(strarr,r,0);
    r->first -= r->strsperline;
    r->last = e_endline(r,r->last-r->strsperline);
    if(upcurr>1)
      r->curr -= r->strsperline;
    if(r->first != e_begline(r,r->last))
      wscroll(1,SDOWN);
    update_line(strarr, r, 0, (upcurr>2) ? 0 : upcurr);
    show_mouse_cursor_pck();
  }
}


//  ------------------------------------------------------------------

static void scroll_up(const gstrarray &strarr, r_t* r, int upcurr)
{
  if(r->last!=(r->lastelem)) {
    hide_mouse_cursor_pck();
    if(upcurr)
      update_curr(strarr,r,0);
    r->first+=r->strsperline;
    r->last=e_endline(r,r->last+1);
    if(upcurr>1)
      if((r->curr+r->strsperline)<=r->last)
        r->curr+=r->strsperline;
    if(r->first!=e_begline(r,r->last))
      wscroll(1,SUP);
    update_line(strarr,r,r->wheight-1,(upcurr>2)?0:upcurr);
    show_mouse_cursor_pck();
  }
}


//  ------------------------------------------------------------------
//  this function reads the mouse for input

static gkey read_mouse(const gstrarray & /*strarr*/, r_t* /*r*/)
{
  #ifdef GOLD_MOUSE
  // if free-floating mouse cursor support is on
  if(gmou.FreeCursor()) {

    // clear mouse button queue
    gmou.ClearEvents();

    // loop until a key is pressed
    while(!kbxhit() and gkbd.kbuf==NULL) {

      // call the keyboard loop function, if defined
      //if(gkbd.kbloop!=NULL)
        //(*gkbd.kbloop)();

      // see if the right button (Esc) was pressed
      gmou.GetRightRelease();
      if(gmou.Count())
        return Key_Esc;

      // see where mouse cursor is at - if it is on a scroll bar
      // or menu item, and the left button is pressed, then scroll
      // menu or select item
      gmou.GetStatus();
      int i = mouse_on_item(r,gmou.Row(),gmou.Column());
      switch(i) {
        case -1:
          gmou.ClearEvents();
          break;
        case -2:
          if(gmou.LeftButton()) {
            scroll_down(strarr,r,3);
            if(gvid->isbios())
              usleep(50);
            gmou.ClearEvents();
          }
          break;
        case -3:
          if(gmou.LeftButton()) {
            scroll_up(strarr,r,3);
            if(gvid->isbios())
              usleep(50);
            gmou.ClearEvents();
          }
          break;
        default:
          gmou.GetLeftRelease();
          if(gmou.Count()) {
            r->curr = i;
            return Key_Ent;
          }
      }
    }
  }
  #endif

  // return zero - it means a key was pressed
  return 0;
}


//  ------------------------------------------------------------------

int wpickstr(int srow, int scol, int erow, int ecol, int btype, vattr bordattr, vattr winattr, vattr barattr, gstrarray &strarr, int initelem, VfvCP open)
{
  int outside;
  gkey xch;
  char ch;
  r_t r;

  int quickpos = (strarr[0][0] == ' ') ? 1 : 0;

  // go through input array and determine the longest
  // string, and count the number of elements in the array

  size_t maxlen = strlen(m_title);

  gstrarray::const_iterator it = strarr.begin();
  gstrarray::const_iterator end = strarr.end();
  for (; it != end; it++)
  {
    size_t len;
    if ((len = it->length()) > maxlen)
    {
      maxlen = len;
    }
  }

  r.maxstrlen = maxlen;
  r.numelems = strarr.size();
  r.lastelem = r.numelems - 1;
  r.winattr = winattr;
  r.barattr = barattr;

  // see if window is to have a border
  int border = (btype==5) ? NO : YES;

  // if ecol == -1 then adjust it to conform to length of longest string
  if(ecol==-1)
    ecol = scol+border+r.maxstrlen+border-1;

  // calculate window area width and height
  r.wwidth  = (ecol-border)-(scol+border)+1;
  r.wheight = (erow-border)-(srow+border)+1;

  // make sure longest string can fit in window
  if(r.maxstrlen > r.wwidth)
    r.maxstrlen = r.wwidth;

  // open window which strings will reside in
  hide_mouse_cursor_pck();
  if(!wopen(srow,scol,erow,ecol,btype,bordattr,winattr,bordattr))
    return -1;

  // if mouse cursor mode is on and window has a border,
  // display scroll indicator arrows on right window border
  #ifdef GOLD_MOUSE
  if(gmou.FreeCursor() and (btype!=5) and ((srow+2) < erow)) {
    vputc(srow+1,ecol,bordattr,(char) 24);
    vputc(erow-1,ecol,bordattr,(char) 25);
    r.scrollbar=true;
  }
  else {
  #endif
    r.scrollbar=false;
  #ifdef GOLD_MOUSE
  }
  #endif
  show_mouse_cursor_pck();

  // if an open function has been specified, then call it
  if(open!=NULL)
    open();

  // if mouse cursor mode is on, then turn on mouse cursor
  show_mouse_cursor_pck();

  // calculate how many strings can fit into 1 window line, number of
  // filler spaces needed per window line, number of spaces per gap in
  // between strings, number of extra spaces to add to first gap, and
  // number of strings that can fit inside the window
  r.strsperline = (r.wwidth-2)/(r.maxstrlen+2);
  if(!r.strsperline)
    r.strsperline++;
  r.fillspaces = r.wwidth-(r.strsperline*r.maxstrlen);
  r.gapspaces = r.fillspaces/(r.strsperline+1);
  r.xtraspaces = (r.fillspaces%(r.strsperline+1))/2;
  r.strsperwin = r.strsperline*r.wheight;

  // initialize first, last, and current elements
  r.curr = r.first = 0;
  r.last = (r.numelems<r.strsperwin) ? (r.lastelem) : (r.strsperwin-1);

  // move selection bar to specified initial element
  goto_item(&r,strarr,initelem);

  // fill window with as many strings as can fit and display bar
  update_window(strarr,&r);

  // loop until an element is selected or Escape is pressed
  for(;;) {

    // get keystroke/mouse movement
    gkbd.inmenu = true;
    xch = read_mouse(strarr,&r);
    if(!xch) {
      xch = getxch();
    }
    gkbd.inmenu=false;

    // test keystroke for a special key
    switch(xch) {

      case Key_Space:
        if (wpickstr_tag)
        {
          char p = strarr[r.curr][0];
          switch (p)
          {
            case ' ':
              strarr[r.curr][0] = (char)wpickstr_tag;
              update_curr(strarr, &r, 1);
              break;
            default:
              strarr[r.curr][0] = ' ';
              update_curr(strarr, &r, 1);
          }
          kbput(Key_Dwn);
        }
        break;

      case Key_Ins:
      case Key_PlsG:
      case Key_S_Equ:
        if(wpickstr_tag) {
          strarr[r.curr][0] = (char)wpickstr_tag;
          update_curr(strarr,&r,1);
          kbput(Key_Dwn);
        }
        break;

      case Key_Del:
      case Key_Min:
      case Key_MinG:
        if(wpickstr_tag) {
          strarr[r.curr][0] = ' ';
          update_curr(strarr,&r,1);
          kbput(Key_Dwn);
        }
        break;

      case Key_StrG:
      case Key_S_8:
        if (wpickstr_tag)
        {
          for (size_t i = 0; i < r.numelems; i++)
          {
            strarr[i][0] = (char)wpickstr_tag;
          }

          update_window(strarr, &r);
        }
        break;

      case Key_Sls:
        if (wpickstr_tag)
        {
          for (size_t i = 0; i < r.numelems; i++)
          {
            strarr[i][0] = ' ';
          }

          update_window(strarr, &r);
        }
        break;

      case Key_Esc:

        // close window and set error to Escape press code
        if(gwin.esc) {
          hide_mouse_cursor_pck();
          wclose();
          gwin.werrno=W_ESCPRESS;
          return -1;
        }
        break;

      case Key_Ent:

        // close window and return subscript of selected string
        hide_mouse_cursor_pck();
        wclose();
        gwin.werrno=W_NOERROR;
        return(r.curr);

      case Key_Lft:

        // move to the item to the left, scrolling if necessary
        if(r.curr) {
          update_curr(strarr,&r,0);
          if(r.curr==r.first)
            scroll_down(strarr,&r,0);
          r.curr--;
          update_curr(strarr,&r,1);
        }
        break;

      case Key_Rgt:

        // move to the item to the right, scrolling if necessary
        if(r.curr!=r.lastelem) {
          update_curr(strarr,&r,0);
          if(r.curr==r.last)
            scroll_up(strarr,&r,0);
          r.curr++;
          update_curr(strarr,&r,1);
        }
        break;

      case Key_Up:

        // move to the item up, scrolling if necessary
        if((r.curr-r.strsperline)<r.first)
          scroll_down(strarr,&r,2);
        else {
          update_curr(strarr,&r,0);
          r.curr-=r.strsperline;
          update_curr(strarr,&r,1);
        }
        break;

      case Key_Dwn:

        // move to the item down, scrolling if necessary
        if((r.curr+r.strsperline)>r.last)
          scroll_up(strarr,&r,((r.curr+r.strsperline)>r.lastelem) ? 0 : 2);
        else {
          update_curr(strarr,&r,0);
          r.curr+=r.strsperline;
          update_curr(strarr,&r,1);
        }
        break;

      case Key_PgUp:

        // move up 1 page.  adjust position if at 1st element
        page_up(strarr,&r);
        break;

      case Key_PgDn:

        // move down 1 page.  adjust position if at last element
        page_down(strarr,&r);
        break;

      case Key_Home:

        // set position to 1st element
        if(r.curr) {
          if((outside=r.first)==NO)
            update_curr(strarr,&r,0);
          r.first=r.curr=0;
          if(outside) {
            r.last=e_endwin(&r,r.first);
            update_window(strarr,&r);
          }
          else {
            update_curr(strarr,&r,1);
          }
        }
        break;

      case Key_End:

        // set position to last element
        if(r.curr!=r.lastelem) {
          if((outside=(r.last<(r.lastelem))?YES:NO)==NO)
            update_curr(strarr,&r,0);
          r.last=r.curr=r.lastelem;
          if(outside) {
            r.first=e_begwin(&r,r.last);
            update_window(strarr,&r);
          }
          else {
            update_curr(strarr,&r,1);
          }
        }
        break;

      default:

        // if not an extended keypress, then search from current
        // position for the item that begins with the same ASCII
        // character as the keypress.  If not found after current
        // position, search from the beginning for a match
        ch = (char)g_toupper(char(xch & 0xFF));
        if (!ch) break;
        
        size_t i;
        for (i = r.curr + 1; i < r.numelems; i++)
        {
          if (ch == g_toupper(strarr[i][quickpos]))
            break;
        }

        if (i == r.numelems)
        {
          for (i = 0; i < r.curr; i++)
          {
            if (ch == g_toupper(strarr[i][quickpos]))
              break;
          }

          if (i == r.curr) continue;
        }

        // a matching ASCII character was found.  set position
        // to matching element, adjusting window if necessary
        goto_item(&r, strarr, i);
    }
  }
}


//  ------------------------------------------------------------------