/** 
 *  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 "swindow/SAwt.h"
#include "swidget/SSlider.h"

static void sliderBitline (SCanvas* canvas, const SColor& fg, int x, int y, int tox, int toy, int width) {
   if (width == 1) {
        canvas->bitline (fg, x, y, tox, toy);
        return;
   }
    if (x == tox) {
        int size = toy > y ? toy - y : y - toy;
        int offset = toy > y ? 0 : y - toy;
        canvas->bitfill (fg, x, y-offset, width, size+width);
        return;
    }
    if (y == toy) {
        int size = tox > x ? tox - x : x - tox;
        int offset = tox > x ? 0 : x - tox;
        canvas->bitfill (fg, x-offset, y, size+width, width);
        return;
    }
}

/**
 * This component is used just to draw the slider pattern.
 */
SSliderComponent::SSliderComponent (void) : lighter ("white"), darker ("black")
{
  lighter = SColor (background.lighter());
  darker = SColor (background.darker());
  /* this is the minimum size we have */
  preferredSize  = SDimension ((int)(30.0*SAwt::getScale()), 
        (int) (30.0*SAwt::getScale()));
}

SSliderComponent::~SSliderComponent()
{
}

void
SSliderComponent::redraw(SCanvas* w, int _x, int _y,
      unsigned int _width ,unsigned int _height)
{
  int o = 0;
  int x = getLocation().x;
  int y = getLocation().y;
  int width = getSize().width;
  int height = getSize().height;
  w->bitfill (background, x, y, 
      (int)getSize().width, (int)getSize().height);
 
  int scale = SAwt::getRasterScale(); 
  for (int j=y + scale*2; j<=y+height-3*scale; j=j+scale)
  {
    SColor c = (o&1) ? darker : lighter;
    o++;
    o = o % 4;
    //for (int i=x + o *scale +3; i<x+(int)getSize().width-3; i+=4*scale)
    for (int i=x + o *scale + 2*scale; i<=x+width-3*scale; i+=4*scale)
    {
       if (scale == 1) 
       {
           w->bitpoint (c, i, j);
       }
       else
       {
           w->bitfill (c, i, j, scale, scale);
       }
    }
  }
#if 0
  sliderBitline (w, lighter, x+1, y, x+width-2, y, scale);
  sliderBitline (w, lighter, x, y, x, y+height-2, scale);
  sliderBitline (w, darker, x+width-1, y+1, x+width-1, y+height-1, scale);
  sliderBitline (w, darker, x+1, y+height-1, x+width-2, y+height-1, scale);
#endif

  sliderBitline (w, lighter, x+scale, y, x+width-scale, y, scale);
  sliderBitline (w, lighter, x, y, x, y+height-scale, scale);
  sliderBitline (w, darker, x+width-scale, y+scale, x+width-scale, y+height-scale, scale);
  sliderBitline (w, darker, x+scale, y+height-scale, x+width-scale, y+height-scale, scale);
}

void
SSliderComponent::redrawFull(SWindow* w)
{
  if (w->isDoubleBufferEnabled())
  {
    w->redraw (true, 0, 0, w->getWidth(), w->getHeight());
  }
  else
  {
    redraw (w, 0, 0, getSize().width, getSize().height);
  }
}

void
SSliderComponent::setBackground (const SColor& bg)
{
  SComponent::setBackground(bg);
  lighter = background.lighter();
  darker = background.darker();
}


/**
 * This is a real window slider.
 */
SSlider::SSlider (void) : 
   sliderBackground((unsigned char) 73,
     (unsigned char) 121, (unsigned char) 134, (unsigned char) 255),
   border (SBorder::IN)
{
  listener = 0;
  /* value, step, page, max */
  set (SLocation (0,0), SDimension (0,0), SDimension (1,1), SDimension (0,0));
  slider.move (SLocation((int)border.getBorderSize().width, 
        (int)border.getBorderSize().height));
  clip (true);
  window->resize(10,10);
  dragOK = false;
  setSliderBackground (sliderBackground);
}

SSlider::~SSlider ()
{
}

void
SSlider::redraw(SCanvas* w, int x, int y,
     unsigned int width ,unsigned int height)
{
  //fprintf (stderr, "redsize=%u,%u\n", size.width, size.height);
  //fprintf (stderr, "redraw: me=%lx location=%d,%d size=%u,%u windowsize=%u,%u\n", 
  //      (unsigned long) (SWindowListener*) this, 
//x,  y, width ,height, w->getWidth(), w->getHeight());
  
  clip (false);
  border.redraw (w, x, y, width, height);
  clip (true);
  slider.redraw (w, x, y, width, height);
}

void
SSlider::resize (const SDimension& d)
{
  if (getSize() == d) return;
  SPanel::resize (d);
  border.resize (d);
/*
  SDimension td;
  if (size.width > border.getBorderSize().width * 2)
  {
    td.width  = size.width - border.getBorderSize().width * 2;
  }
  if (size.height > border.getBorderSize().height * 2)
  {
    td.height  = size.height - border.getBorderSize().height * 2;
  }
*/
  clip (true);
  // slider.resize (td);
  /* we are a panel */
  recalculateGeometry ();
  window->redraw(true, 0, 0, size.width, size.height);
// fprintf (stderr, "XXX ListBox Slider %u,%u size=%u,%u\n", 
//   d.width, d.height, size.width, size.height);
}

/**
 * Turn clipping at the border on and off.
 * @param on is true if we turn on clipping.
 */
void
SSlider::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);
}

void
SSlider::setBackground (const SColor& bg)
{
  border.setBackground (bg);
}

void
SSlider::setSliderBackground (const SColor& bg)
{
  sliderBackground = bg;
  window->setBackground (sliderBackground.darker().darker());
  slider.setBackground (sliderBackground);
}

void
SSlider::buttonPressed (SWindow * w, int button, int x, int y)
{
  // scroll ignored.
  if (button == 3 ||  button == 4) return; 

  /* drag point is the difference between real location is drag location */
  SLocation l (x,y);
  dragPoint = slider.getLocation() - l;
  dragOK = true;
  /* see if we clicked the slider */
  if (l <  slider.getLocation()) 
  {
//fprintf (stderr, "1: dragPoint=%d,%d slider=%d,%d\n",
 // l.x, l.y, slider.getLocation().x, slider.getLocation().y);
     dragOK = false;
     page (-1);
  }
  if (l >  slider.getLocation() + slider.getSize()) 
  {
//fprintf (stderr, "2: dragPoint=%d,%d slider=%d,%d %u %u\n",
 // l.x, l.y, slider.getLocation().x, slider.getLocation().y,
  //    slider.getSize().width, slider.getSize().height);
     dragOK = false;
     page (1);
  }
}

void
SSlider::buttonReleased (SWindow * w, int button, int x, int y)
{
}

void
SSlider::buttonDragged (SWindow * w, int button, int x, int y)
{
  if (!dragOK) return;
  SLocation l = SLocation (x,y) + dragPoint;
  l = l.minimize (SLocation (0,0));

  /* how much is it in value? */
  SDimension estate = getSize() - (border.getBorderSize() * 2);
  SDimension sliderSize = slider.getSize ();
  SDimension gmax  = estate - sliderSize;
  SLocation newValue = (l * slidable.max) / gmax;
  newValue = newValue.minimize(SLocation (0,0)).maximize(slidable.max);
  if (newValue == slidable.value) return;
  //fprintf (stderr, "old=%d,%d new=%d,%d\n", 
  //  value.x, value.y, newValue.x, newValue.y);
  slidable.value = newValue;
  slide (true);
}

/**
 * page up (positive) or page down (negative)
 */
void
SSlider::page (int count)
{
  SLocation newValue = slidable.value + slidable.page * count;
  newValue = newValue.minimize(SLocation (0,0)).maximize(slidable.max);
  if (newValue == slidable.value) return;
  //fprintf (stderr, "old=%d,%d new=%d,%d\n", 
  //  value.x, value.y, newValue.x, newValue.y);
  slidable.value = newValue;
  slide (true);
}
/**
 * Set these slider properties.
 * @param _value is the slider value.
 * @param _step is the single click step.
 * @param _page is the double click step.
 * @param _max is the maximum value
 * @param _vertical is true if vertical values will be set.
 */
void
SSlider::set (int _value, unsigned int _step, 
   unsigned int _page, unsigned int _max, bool _vertical)
{
  if (_step==0 && _page==0 && _max==0)
  {
    slide (_value, _vertical);
    return;
  }
  slidable.step = (_vertical) 
    ? SDimension (slidable.step.width, _step) 
    : SDimension (_step, slidable.step.height);

  slidable.page = (_vertical) 
    ? SDimension (slidable.page.width, _page) 
    : SDimension (_page, slidable.page.height);

  slidable.max = (_vertical)  
    ? SDimension (slidable.max.width, _max) 
    : SDimension (_max, slidable.max.height);

  SLocation newValue = (_vertical) 
    ? SLocation (slidable.value.x, _value) 
    : SLocation (_value, slidable.value.y);

  slidable.value = newValue;
  recalculateGeometry();
}

void
SSlider::set (const SLocation& _value, const SDimension&  _step, 
       const SDimension & _page, const SDimension&  _max)
{
  slidable.value = _value;
  slidable.step = _step;
  slidable.page = _page;
  slidable.max = _max;
  recalculateGeometry();
}


/**
 * recalculate the slider geometry 
 */
void
SSlider::recalculateGeometry ()
{
  if (slidable.page == SDimension(0,0)) slidable.page = SDimension (1,1);
  /* calculate the scale and the slider dimensions */
  SDimension estate = getSize() - (border.getBorderSize() * 2);
  /* This is what we would like to have */
  /* max + page = estate ; page = estate - max */
  SDimension mestate =  slidable.max + slidable.page;
  SDimension sliderSize  = (estate * slidable.page) / mestate;
  SDimension sliderPrefers = slider.getPreferredSize ();
  /* this is what geometrically possible to have */
  sliderSize = sliderSize.minimize (sliderPrefers).maximize (estate);
  /* this is out geometrically possible max. */
  SDimension gmax  = estate - sliderSize;
  SLocation sliderLocation = (slidable.value * gmax) / slidable.max;
  setSliderGeometry (sliderLocation + border.getBorderSize(), sliderSize);
}

/**
 * there is no geometry change. just scroll.
 */
void
SSlider::slide (int _value, bool _vertical)
{
  SLocation newValue = (_vertical) 
     ? SLocation (slidable.value.x, _value) 
     : SLocation (_value, slidable.value.y);

  if (newValue == slidable.value) return;
  slidable.value = newValue;
  slide (false);
}

/**
 * 2 d slide
 */
void
SSlider::slide (const SLocation& _value)
{
  if (_value == slidable.value) return;
  slide (false);
}

void
SSlider::slide (bool notify)
{
  slidable.value = 
       slidable.value.maximize (slidable.max).minimize (SLocation(0,0));

  SDimension estate = getSize() - (border.getBorderSize() * 2);
  SDimension sliderSize = slider.getSize ();
  SDimension gmax  = estate - sliderSize;
  SLocation sliderLocation = (slidable.value * gmax) / slidable.max;
  setSliderGeometry (sliderLocation + border.getBorderSize(), sliderSize);
  if (notify && listener)
  {
    listener->valueChanged (&slidable, SSliderListener::SS_BOTH);
  }
}

/**
 * Set the slide geometry and redraw slider if necessary.
 */
void
SSlider::setSliderGeometry (SLocation l, SDimension d)
{
  SDimension ssize = slider.getSize ();
  SLocation sloc = slider.getLocation ();
  if (l==sloc && d ==ssize) return;
  SLocation smax = sloc+ssize;

  /* find the biggest containing rectangle */
  SLocation minl = sloc; minl = minl.minmerge (l);
  SLocation lpd = l+d;
  SLocation maxl = smax; maxl.maxmerge (lpd);

  if (!window->isDoubleBufferEnabled())
  {
    window->copy (sloc.x, sloc.y, d.width, d.height, l.x, l.y);
  }

  SLocation delta = maxl-minl;
  /* clear upper */
  if (minl.y < l.y && delta.x >0)
  {
    if (!window->isDoubleBufferEnabled())
    {
       SLocation ll = l - minl;
       window->clear (minl.x, minl.y, delta.x, ll.y);
    }
  }
  /* clear lower */
  if (maxl.y > lpd.y && delta.x >0)
  {
    if (!window->isDoubleBufferEnabled())
    {
       SLocation lu = maxl - lpd;
       window->clear (minl.x, lpd.y, delta.x, lu.y);
    }
  }
  /* clear left */
  if (minl.x < l.x && delta.y >0)
  {
    if (!window->isDoubleBufferEnabled())
    {
       SLocation ll = l - minl;
       window->clear (minl.x, minl.y, ll.x, delta.y);
    }
  }
  /* clear right */
  if (maxl.y > lpd.y && delta.x >0)
  {
    if (!window->isDoubleBufferEnabled())
    {
      SLocation lu = maxl - lpd;
      window->clear (lpd.x, minl.y, lu.x, delta.y);
    }
  }
  slider.move (l);
  if (ssize != d)
  {
     slider.resize (d);
     slider.redrawFull (window);
  }
  else if (window->isDoubleBufferEnabled())
  {
    window->redraw (true, 0, 0, window->getWidth(), window->getHeight());
  }
}

SLocation
SSlider::getValue() const
{
  return SLocation (slidable.value);
}

/**
 * This is called by a slibadle and it shows that vales have been changed.
 */
void
SSlider::valueChanged (SSlidable* _slidable, SSlideType _type)
{
  switch (_type)
  {
  case SSliderListener::SS_HORIZONTAL:
    set (_slidable->value.x, _slidable->step.width, _slidable->page.width, 
         _slidable->max.width, false);
    break;
  case SSliderListener::SS_VERTICAL:
    set (_slidable->value.y, _slidable->step.height, _slidable->page.height, 
         _slidable->max.height, true);
    break;
  case SSliderListener::SS_BOTH:
    set (_slidable->value, _slidable->step, _slidable->page, _slidable->max);
    break;
  }
}

/**
 * @param l  is the listener.
 */
SSlidable*
SSlider::setSliderListener (SSliderListener* l)
{
  listener = l;
  return &slidable;
}
