/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "swidget/STextList.h"

STextList::STextList (void) : label (""), selectedLabel ("")
{
  listListener = 0;
  lastSelected = -1;
  sliderListener = 0;
  viewableSize=SDimension (1,1);
  lastSelected = -1;
  setSelectedBackground (SColor("blue"));
  setSelectedForeground (SColor("white")); 
  setTextBackground (SColor("white"));
}

STextList::~STextList ()
{
}

void
STextList::setText (const SStringVector& v)
{
  lastSelected = -1;
  vector = v;
  selectedVector.clear();
  for (unsigned int i=0; i<vector.size(); i++)
  {
    selectedVector.append (false);
  }
  /* calculate the sizes, scolles */
  recalc(0);
  window->redraw (true,
    (int) border.getBorderSize().width ,
    (int) border.getBorderSize().height,
    viewableSize.width,
    viewableSize.height);
}


/**
 * recalculate slider.
 */
void
STextList::recalc(int current)
{
  slidable.value.y = current; 
  slidable.step.height = label.textView.lineHeight;
  slidable.page.height = viewableSize.height/2+1;
  slidable.max.height = vector.size() * label.textView.lineHeight;
  slidable.max.height = (slidable.max.height > viewableSize.height) 
     ? slidable.max.height - viewableSize.height: 0;

  if ((int)slidable.max.height < slidable.value.y)
  {
    slidable.value.y = (int)slidable.max.height;
    window->redraw (true,
      (int) border.getBorderSize().width ,
      (int) border.getBorderSize().height,
      viewableSize.width,
      viewableSize.height);
  }
  if (sliderListener)
  {
    sliderListener->valueChanged (&slidable, SSliderListener::SS_VERTICAL);
  }
}

void
STextList::clear ()
{
}

void
STextList::slideDown () {
  int increment = label.textView.lineHeight;
  int max = label.textView.lineHeight * vector.size();
  if (max == 0) return;
  SSlidable s;
  s.value.y = slidable.value.y - increment;
  if (s.value.y < 0) {
     s.value.y = 0;
  }
  if (slidable.value.y == s.value.y) return;
  valueChanged (&s, SSliderListener::SS_VERTICAL);
  sliderListener->valueChanged (&slidable, SSliderListener::SS_VERTICAL);
}

void
STextList::slideUp () {
  int increment = label.textView.lineHeight;
  int max = label.textView.lineHeight * vector.size();
  if (max == 0) return;

  SSlidable s;
  s.value.y = slidable.value.y + increment;
  if (s.value.y > max-(int)viewableSize.height) {
     s.value.y = max-(int)viewableSize.height;
     if (s.value.y < 0) {
        s.value.y = 0;
     }
  }
  if (slidable.value.y == s.value.y) return;
  valueChanged (&s, SSliderListener::SS_VERTICAL);
  sliderListener->valueChanged (&slidable, SSliderListener::SS_VERTICAL);
}

void
STextList::makeSelectedVisible()
{
  if (lastSelected == -1) return;
  int loc = locationForIndex ((unsigned int) lastSelected);

  SSlidable s;
  bool changed = false;
  if (loc < (int) border.getBorderSize().height)
  {
    s.value.y = slidable.value.y 
        - ((int) border.getBorderSize().height - loc);
    changed = true;
  }
  else if (loc + (int) label.textView.lineHeight > 
      (int) border.getBorderSize().height + (int)viewableSize.height)  
  {
    s.value.y = slidable.value.y + 
        loc + (int) label.textView.lineHeight 
         - (int) border.getBorderSize().height - (int)viewableSize.height;
    changed = true;
  }
  if (changed)
  {
    valueChanged (&s, SSliderListener::SS_VERTICAL);
    sliderListener->valueChanged (&slidable, SSliderListener::SS_VERTICAL);
  }
}

void
STextList::setBackground (const SColor& bg)
{
  //SPanel::setBackground (bg);
  //label.setBackground (bg);
}

void
STextList::setTextBackground (const SColor& bg)
{
  SPanel::setBackground (bg);
  label.setBackground (bg);
}

void
STextList::setSelectedBackground (const SColor& bg)
{
  selectedLabel.setBackground (bg);
}

void
STextList::setForeground (const SColor& lrfg, const SColor& rlfg)
{
  label.setForeground (lrfg, rlfg);
}
void
STextList::setSelectedForeground (const SColor& fg)
{
  selectedLabel.setForeground (fg);
}

void
STextList::redraw (SCanvas *canvas, int x, int y, 
 unsigned int width, unsigned int height)
{
  clip (false);
  border.redraw (canvas, x, y, width, height);
  clip (true);
  if (vector.size()==0) return;
  unsigned int from = indexForLocation (y);
  unsigned int till = indexForLocation (y+(int)height 
       + (int) label.textView.lineHeight);

  for (unsigned int i=from; i<=till; i++)
  {
    redraw (canvas, i, 
         (int) border.getBorderSize().width, viewableSize.width);
  }
}

void
STextList::keyPressed (SWindow * w, SKey key, const SString& s,
     bool ctrl, bool shift, bool meta)
{
}

void
STextList::keyReleased (SWindow * w, SKey key, const SString& s,
   bool ctrl, bool shift, bool meta)
{
}
void
STextList::buttonPressed (SWindow * w, int button, int x, int y)
{
  if (vector.size()==0) return;
  // SGC caretDown
  if (button == 4)  {
    // no item selected
     slideUp();
     return;
  }
  // SGC caretUp
  if (button == 3)  {
     slideDown();
     return;
  }
  selectLocation (y);
  if (listListener)
  {
    listListener->itemHighlighted(this, lastSelected);
  }
}


void
STextList::buttonReleased (SWindow * w, int button, int x, int y)
{
  if (lastSelected < 0) return;
  if (listListener)
  {
    listListener->itemSelected(this, 0);
  }
}

void
STextList::buttonDragged (SWindow * w, int button, int x, int y)
{
  selectLocation (y);
  if (listListener)
  {
    listListener->itemHighlighted(this, lastSelected);
  }
}

void
STextList::resize (const SDimension& d)
{
  SPanel::resize (d);
  border.resize (d);
  viewableSize = getSize();
  if (viewableSize.width > 2 * border.getBorderSize().width)
  {
     viewableSize.width = viewableSize.width 
        - 2 * border.getBorderSize().width;
  }
  if (viewableSize.height > 2 * border.getBorderSize().height)
  {
     viewableSize.height = viewableSize.height 
        - 2 * border.getBorderSize().height;
  }
  selectedLabel.resize (SDimension (viewableSize.width,
           selectedLabel.textView.lineHeight));
  label.resize (SDimension (viewableSize.width,
           selectedLabel.textView.lineHeight));
  recalc (slidable.value.y);
}

void
STextList::move (const SLocation& loc)
{
  SPanel::move (loc);
}

SSlidable*
STextList::setSliderListener (SSliderListener* l)
{
  sliderListener = l;
  return &slidable;
}

/* This is from SSliderListener */
void
STextList::valueChanged (SSlidable* _slidable, SSlideType type)
{
  if (type==SSliderListener::SS_HORIZONTAL) return;
  if (slidable.value.y == _slidable->value.y) return;
  int diff = slidable.value.y - _slidable->value.y;
  unsigned int udiff = (diff<0) ? (unsigned int) (-diff) : (unsigned int) diff;
  slidable.value = _slidable->value;
  /* redraw the whole thing */
  if (window->isDoubleBufferEnabled())
  {
    window->redraw (true, 0, 0, window->getWidth(), window->getHeight());
  }
  else if (udiff > viewableSize.height)
  {
    window->redraw (true,
      (int) border.getBorderSize().width ,
      (int) border.getBorderSize().height,
       viewableSize.width, viewableSize.height);
  }
  /* up */
  else if (diff < 0)
  {
    window->copy ((int) border.getBorderSize().width, 
        (int) udiff + (int) border.getBorderSize().height,
        (int) viewableSize.width, (int) viewableSize.height-(int) udiff, 
        border.getBorderSize().width,
        border.getBorderSize().height);
    /* redraw down */
    window->redraw (true,
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height 
             + (int) viewableSize.height - (int) udiff,
       viewableSize.width,
       udiff);
  }
  else
  {
    /* copy down */
    window->copy ((int) border.getBorderSize().width, 
        (int) border.getBorderSize().height,
        (int) viewableSize.width, (int) viewableSize.height-(int) udiff, 
        border.getBorderSize().width,
        border.getBorderSize().height + udiff);
    /* redraw up */
    window->redraw (true,
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       viewableSize.width,
       udiff);
  }
}

void
STextList::clip (bool on)
{
  if (!on)
  {
    window->removeClippingArea ();
    return;
  }
  window->setClippingArea (
       (int) border.getBorderSize().width ,
       (int) border.getBorderSize().height,
       size.width - 2 * border.getBorderSize().width,
       size.height - 2 * border.getBorderSize().height);
}

/**
 * redraw one line 
 */
void
STextList::redraw (SCanvas* canvas, unsigned int index, 
    int x, unsigned int w)
{
  bool ison = selectedVector[index];
  SLocation l((int) border.getBorderSize().width, 
      locationForIndex (index));
  if (ison)
  {
    canvas->bitfill (selectedLabel.getBackground(), 
           l.x, l.y, getSize().width, selectedLabel.textView.lineHeight);
    selectedLabel.move(l);
    selectedLabel.setText(vector[index]);
    selectedLabel.redraw (canvas, l.x, l.y, getSize().width, 
           selectedLabel.textView.lineHeight);
  }
  else
  {
    label.move(l);
    label.setText(vector[index]);
//fprintf (stderr, "draw='%*.*s'\n", SSARGS(vector[index]));
    label.redraw (canvas, l.x, l.y, getSize().width, 
           selectedLabel.textView.lineHeight);
  }
}

void
STextList::selectLocation (int y)
{
  if (vector.size()==0) return;
  int line = indexForLocation (y, false);
  selectItem (line);
}

/**
 * select an item. if item is -1 deselect all
 * @return true if anything got selected.
 */
bool
STextList::selectItem (int line)
{
  if (lastSelected  == line) {
    makeSelectedVisible ();
    return line>=0;
  }

  /* do real calculation here */
  if (lastSelected >= 0)
  {
    selectedVector.replace ((unsigned int) lastSelected, false);
    window->redraw (true,
      (int) border.getBorderSize().width ,
      locationForIndex ((unsigned int) lastSelected),
      viewableSize.width, label.textView.lineHeight);
  }
  lastSelected = line;

  if (lastSelected >= 0)
  {
    selectedVector.replace ((unsigned int) lastSelected, true);
    /* do real calculation here */
    window->redraw (true,
      (int) border.getBorderSize().width ,
      locationForIndex ((unsigned int) lastSelected),
      viewableSize.width, label.textView.lineHeight);
    makeSelectedVisible ();
    return true;
  }
  return false;
}

/**
 * Select first text if found. Unselect all otherwise.
 */
bool
STextList::selectText (const SString& text)
{
  int si = -1;
  for (unsigned int i=0; i<vector.size(); i++)
  {
    if (vector[i] == text)
    {
      si = (int) i;
      break;
    }
  }
  return selectItem (si);
}

/**
 * calculate the index for a given physical location
 */
int
STextList::indexForLocation (int _y, bool correct)
{
  int y = _y + slidable.value.y;
  int linex = y - (int)border.getBorderSize().height;
  int line = linex / (int) label.textView.lineHeight;
  if (correct && line < 0) line = 0;
  if (line >= (int) vector.size())
  {
    line = correct ? (int) vector.size() -1 : -1;
  }
  return line;
}

/**
 * calculate the physical location for a given index
 */
int
STextList::locationForIndex (unsigned int index)
{
  int ret = (int)border.getBorderSize().height + 
       (int) index * (int) label.textView.lineHeight
       - slidable.value.y;
  return ret;
}
/**
 * Chose between fonts.
 * @param fnt is the font to chose.
 * @param size is the size of the font or zero if old size is used.
 */
void
STextList::setFont (const SString& fnt, double size)
{
  label.setFont (fnt, size);
  selectedLabel.setFont (fnt, size);
  window->redraw (true,
    (int) border.getBorderSize().width ,
    (int) border.getBorderSize().height,
    viewableSize.width,
    viewableSize.height);
  recalc(slidable.value.y);
}

/**
 * Set the size of the font.
 */
void
STextList::setFontSize (double size)
{
  label.setFontSize (size);
  selectedLabel.setFontSize (size);
  window->redraw (true,
    (int) border.getBorderSize().width ,
    (int) border.getBorderSize().height,
    viewableSize.width,
    viewableSize.height);
  recalc(slidable.value.y);
}

void
STextList::setListListener (SListListener* l)
{
  listListener = l;
}

SListListener::SListListener(void)
{
}

SListListener::~SListListener()
{
}

void
SListListener::itemHighlighted (void* source, int it)
{
}

/**
 * return the last selected item or -1
 */
int
STextList::getLastSelected ()
{
  return lastSelected;
}

/**
 * return the last selected item or -1
 */
SString
STextList::getLastSelectedText ()
{
  if (lastSelected < 0) return SString();
  return SString(vector[(unsigned int)lastSelected]);
}
