/** 
 *  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/SFreeHand.h"
#include "swidget/SIcon.h"
#include "swidget/SIconFactory.h"
#include "stoolkit/SUtil.h"
#include "stoolkit/SEncoder.h"
#include "stoolkit/SIO.h"

#define SS_LEFT_MARGIN 2

static SString SD_NOT_FOUND("???");

SFreeHandListener::SFreeHandListener(void)
{
}
SFreeHandListener::~SFreeHandListener()
{
}
/**
 * A drawing component that lets you make a small 
 * drawing with the mouse.
 */
SFreeHand::SFreeHand (void) 
{
  listener = 0;
  /* top line */

  titleLabel = new SLabel (translate ("Handwriting Input"));
  titleLabel->setAlignment (SD_Center);
  add (titleLabel);

  strokesLabel = new SLabel (translate ("Strokes:"));
  // SD_Right causes y alignment problem.
  strokesLabel->setAlignment (SD_Left);
  add (strokesLabel);

  strokesCount = new SLabel ("0");
  strokesCount->setAlignment (SD_Left);
  add (strokesCount);

  /* next line */
  converters = new SListBox (translate ("Converters"));
  converters->setListListener (this);
  add (converters);

  drawing = new SDrawing ();
  drawing->setDrawingListener(this);
  add (drawing);

  /* next four button are one under the other. */

  lookupButton = new SButton (translate ("Look-up"), 
       SIconFactory::getIcon("Yes"));
  lookupButton->setButtonListener (this);
  lookupButton->setAlignment (SD_Center);
  add (lookupButton);

  clearButton = new SButton (translate ("Clear"), 
       SIconFactory::getIcon("Cancel"));
  clearButton->setButtonListener (this);
  clearButton->setAlignment (SD_Center);
  add (clearButton);

  isDirected = true;
  directedButton = new SButton (translate ("Directed"));
  directedButton->setButtonListener (this);
  directedButton->setAlignment (SD_Center);
  directedButton->setIcon(SIconFactory::getIcon("CircleOn"));
  add (directedButton);

  candidates = new SListBox (translate ("Candidates"));
  candidates->setListListener (this);
  add (candidates);
  forceLayout (SLayout (SDimension (1000, 1000)));
  recalc ();
  initConverters();
  needConversion = false;
}

/**
 * Find converters in datapath, with extension .hwd
 */
void
SFreeHand::initConverters()
{
  SBinHashtable<int> mentioned;
  SStringVector all;
  SStringVector sp = SUniMap::getPath ();

  for (unsigned int i=0; i<sp.size(); i++)
  {
    SDir d(sp[i]);
    SStringVector l = d.list("*.hwd");
    for (unsigned int j=0; j<l.size(); j++)
    {
      SString s = l[j];
      s.truncate(s.size()-4);
      if (mentioned.get(s) != 0) continue;
      mentioned.put (s,1);
      SString fn = d.getName(); fn.append ("/");
      fn.append (s);
      fn.append (".hwd");
      SFile file (fn);
      SFileImage im=file.getFileImage();
      if (im.size() < 10 || (im.array())[0] != '#' 
        || (im.array())[1] != 'H' || (im.array())[2]  != 'W'
        || (im.array())[3] != 'D'  || (im.array())[4] != ' '
        || ((im.array())[5]  != '1' && (im.array())[5]  != '2'))
      {
        fprintf (stderr,"Bad magic number: %*.*s\n", SSARGS(fn));
        fprintf (stderr,"Expected: file that starts with '#HWD 1' or '#HWD 2'\n");
        s.insert (0, SString("?"));
        s.append (SString("?"));
        all.append (s);
      }
      else
      {
        all.append (s);
      }
    }
  }
  if (all.size())
  {
    all.sort();
    allConverters = all;
    converters->setText (all);
    setConverter (all[0]);
  }
  else
  {
    converters->setText (translate ("not found"));
    converters->selectItem (0);
  }
}

/**
 * Deletes this drawing. Nothing to do.
 */
SFreeHand::~SFreeHand ()
{
}
/*
void
printl (const SLayout lo)
{
  SLocation l = lo.getLocation();
  SDimension d = lo.getDimension();
  fprintf (stderr, "l= (%d,%d) d= (%u,%u)          l2 =(%d,%d)\n",
    l.x, l.y, d.width, d.height, l.x + (int) d.width, l.y + d.height);
}
*/
/**
 * Recalculate constraints.
 */
void
SFreeHand::recalc ()
{
  int gap = (int) (2.0 * SAwt::getScale());
  int bgap = (int) (4.0 * SAwt::getScale()); /* between buttons */

  SDimension d = layout.getDimension ();
  /* we should fit into this layout */

  // From left to right...
  SLocation t0 = SLocation (titleLabel->getPreferredSize());
  SLocation t1 = SLocation (strokesLabel->getPreferredSize());
  SLocation t2 = SLocation (strokesCount->getPreferredSize());

  SLocation l0 = SLocation (converters->getPreferredSize());
  SLocation m0 = SLocation (drawing->getPreferredSize());

  //SLocation b0 = SLocation (insertButton->getPreferredSize());
  SLocation b1 = SLocation (lookupButton->getPreferredSize());
  SLocation b0 = b1; // removed insertButton
  SLocation b2 = SLocation (directedButton->getPreferredSize());
  SLocation b3 = SLocation (clearButton->getPreferredSize());

  SLocation r0 = SLocation (candidates->getPreferredSize());
  b2.x += 20; /* don't ask */
  t2.x = t1.x;

  /* find longest button */
  int bX = 0;
  if (b0.x > bX) bX = b0.x;
  if (b1.x > bX) bX = b1.x;
  if (b2.x > bX) bX = b2.x;
  if (b3.x > bX) bX = b3.x;
  if (l0.x < r0.x + bX + bgap * 2) l0.x = r0.x + bX + bgap * 2;

  if (l0.y < b0.y + bgap + b1.y + bgap + b2.y)
  {
    l0.y = b0.y + bgap + b1.y + bgap + b2.y + bgap + b3.y;
  }
 
  if (l0.y < 140) l0.y = 140;
  int m0x = (int) (180* SAwt::getScale());
  m0.x = m0x; /* a bit more */
  m0.y = l0.y;
  r0.y = l0.y;
  r0.x = r0.x + 20; /* just in case */

  /* find minimum height */
  int minY = 2 + t1.y + + gap + l0.y;

  /* find minimum width, assume drawing wants 100.  */
  int minX = 2 + l0.x + gap + m0.x + bgap + bX + bgap + r0.x;

  setMinimumSize (SDimension ((unsigned int)minX, (unsigned int) minY));
  preferredSize = SDimension ((unsigned int)minX, (unsigned int) minY);

  /* top */
  int x = 1;
  int y = 1;
  titleLabel->setLayout (SLayout (
       SLocation (x, y),
       SLocation (x+t0.x, y + t0.y),
       SLocation (0, 0),
       SLocation (0, 0)
  ));

  x += (t0.x + (int) (20.0 * SAwt::getScale()));
  strokesLabel->setLayout (SLayout (
       SLocation (x, 1),
       SLocation (x + t1.x, y + t0.y),
       SLocation (0, 0),
       SLocation (0, 0)
  ));
  x += (t1.x + gap);
  strokesCount->setLayout (SLayout (
       SLocation (x, 1),
       SLocation (x + t2.x,  y + t0.y),
       SLocation (0, 0),
       SLocation (0, 0)
  ));
  /* new row. */
  x = 1;
  y +=  (t1.y + gap);

  converters->setLayout (SLayout (
   SLocation (x, y),
    SLocation (x + l0.x,  y + l0.y),
    SLocation (0, 0),
    SLocation (30, 100)
  ));

  x += (l0.x + gap);

  drawing->setLayout (SLayout (
    SLocation (x, y),
    SLocation (x + m0.x,  y + m0.y),
    SLocation (30, 0),
    SLocation (70, 100)
  ));

   /* set buttons later */
  int butX = x + m0.x;

  /* right */
  //int minX = 2 + l0.x + gap + 100 + bgap + bX + bgap + r0.width;
  x =  minX - 1 - r0.x;

  candidates->setLayout (SLayout (
    SLocation (x, y),
    SLocation (x + r0.x,  y + r0.y),
    SLocation (80, 0),
    SLocation (100, 100)
  ));

  int tbgap = r0.y - b0.y -bgap - b1.y -bgap -b2.y -bgap -b3.y; 
  if (tbgap < 0) tbgap = 0;

  /* now buttons between butx .. x- bgap */
  x = butX + bgap;

  lookupButton->setLayout (SLayout (
    SLocation (x, y),
    SLocation (x + bX,  y + b0.y),
    SLocation (70, 0),
    SLocation (80, 0)
  ));
  y += (b0.y + bgap);

  y += ((b1.y + bgap))/2;
  /* what is this gap ? there was a button here before ) */

  directedButton->setLayout (SLayout (
    SLocation (x, y),
    SLocation (x + bX,  y + b2.y),
    SLocation (70, 66),
    SLocation (80, 66)
  ));
  y += ((b1.y + bgap))/2 + tbgap;


  y += (b2.y + bgap);

  clearButton->setLayout (SLayout (
    SLocation (x, y),
    SLocation (x + bX,  y + b3.y),
    SLocation (70, 100),
    SLocation (80, 100)
  ));

  /* save current */
  SLayout goodlayout = layout;

  /* pretend we have this layout */
  forceLayout (preferredSize);

  /* accept old layout */
  setLayout (goodlayout);
}

/**
 * ButtonListener
 */
void
SFreeHand::buttonPressedAccel (void* source, const SAccelerator* acc)
{
  if (source == clearButton)
  {
    drawing->clear();
    lookupText.clear();
    SStringVector v;
    candidates->setText (v);
  }
  else if (source == lookupButton)
  {
    convertOne();
  }
  if (source == directedButton)
  {
    isDirected = !isDirected;
    const char * str = isDirected?"CircleOn" : "CircleOff";
    directedButton->setIcon (SIconFactory::getIcon(str));
    convertOne();
  }
}

/**
 * ItemListener
 */
void
SFreeHand::itemSelected (void* source, const SAccelerator* acc)
{
  if (source == candidates->textList)
  {
    SString str = candidates->textList->getLastSelectedText();
    lookupText.clear();
    if (str == SD_NOT_FOUND) return;
    lookupText = str;
    if (lookupText.size()>0 && listener != 0)
    {
      listener->freeHandTextChanged (this, lookupText);
    }
    window->putClipUTF8 (lookupText);
  }
  else  
  {
    SString str = converters->textList->getLastSelectedText();
    converter = SHWConverter(str);
    convertOne();
  }
}


/**
 * There can be only one drawing listener.
 */
void
SFreeHand::setFreeHandListener (SFreeHandListener* _listener)
{
  listener = _listener;
}

const SString&
SFreeHand::getLookupText()
{
  return lookupText;
}

void
SFreeHand::setFont (const SString& font, double fontSize)
{
  candidates->textList->setFont (font, fontSize);
  recalc ();
}

void
SFreeHand::setFontSize (double fontSize)
{
  candidates->textList->setFontSize (fontSize);
  recalc ();
}

void
SFreeHand::setButtonFont (const SString& font, double fontSize)
{
  lookupButton->setFont (font, fontSize);
  clearButton->setFont (font, fontSize);
  directedButton->setFont (font, fontSize);

  titleLabel->setFont (font, fontSize);
  strokesLabel->setFont (font, fontSize);
  strokesCount->setFont (font, fontSize);

  converters->setFont (font, fontSize);
  candidates->setFont (font, fontSize);
  recalc ();
}

/**
 * @param bg is the background.
 */
void
SFreeHand::setBackground (const SColor& bg)
{
  SPanel::setBackground (bg);
}

/**
 * Set the background of the drawing itself.
 * @param bg is the background.
 */
void
SFreeHand::setDrawingBackground (const SColor& bg)
{
  drawing->setTextBackground (bg);
}

void
SFreeHand::setSliderBackground (const SColor& bg)
{
  candidates->setSliderBackground (bg);
  converters->setSliderBackground (bg);
}

/**
 * Set the foreground of the drawing itself.
 * @param fg is the foreground
 * @param fgrecent is the foreground of the last line, being drawn
 */
void
SFreeHand::setForeground (const SColor& lrfg, const SColor& rlfg)
{
  lookupButton->setForeground (lrfg);
  clearButton->setForeground (lrfg);
  directedButton->setForeground (lrfg);

  candidates->setLabelForeground (lrfg);
  converters->setLabelForeground (lrfg);

  titleLabel->setForeground (lrfg);
  strokesLabel->setForeground (lrfg);
}

/**
 * Set the foreground of the label
 * @param fg is the foreground
 * @param fgrecent is the foreground of the last line, being drawn
 */
void
SFreeHand::setLabelForeground (const SColor& lrfg, const SColor& rlfg)
{
  strokesCount->setForeground (lrfg);
}

/**
 * Set the foreground of the drawing itself.
 * @param fg is the foreground
 * @param fgrecent is the foreground of the last line, being drawn
 */
void
SFreeHand::setDrawingForeground (const SColor& _fg, const SColor& fgrecent)
{
  drawing->setForeground (_fg, fgrecent);
}

/**
 * Redraw the Component on a canvas 
 * @param canvas is where we redraw this.
 */
void
SFreeHand::redraw (SCanvas *canvas, int x, int y, unsigned int width, unsigned int height)
{
  SPanel::redraw (canvas, x, y, width, height);
}

void
SFreeHand::resize (const SDimension& _size)
{
  SPanel::resize (_size);
}
void
SFreeHand::setConverter (const SString& name)
{
  for (unsigned int i=0; i<allConverters.size(); i++)
  {
    if (allConverters[i] == name)
    {
      converter = SHWConverter(name);
      drawing->clear();
      lookupText.clear();
      candidates->setText (SStringVector());
      converters->selectItem (i);
      return;
    }
  }
}

const SString& 
SFreeHand::getConverter() const
{
  return converter.getName();
}

bool
SFreeHand::isOK () const
{
  return converter.isOK();
}

void
SFreeHand::clicked (void* source, int button)
{
  if (button==2)
  {
    drawing->undo();
  }
  if (button==1)
  {
    drawing->clear();
    lookupText.clear();
    candidates->setText (SString());
    return;
  }
}

void
SFreeHand::strokeChanged(void* src, unsigned int newsize)
{
  SString str;
  str.print ((int)newsize);
  strokesCount->setText (str);
  candidates->setText (SStringVector());
  needConversion = true;
}

void
SFreeHand::convertOne ()
{
  needConversion  = false;
  SLineCurves set = drawing->getDrawing();
  if (set.size()==0)
  {
    lookupText = "";
    candidates->setText (SStringVector());
    return;
  }
  SStringVector v = converter.convert (set, isDirected);
  if (v.size()==0)
  {
    lookupText = "";
    v.append (SD_NOT_FOUND);
    candidates->setText (v);
    return;
  }
  lookupText = v[0];
  candidates->setText (v);
  window->putClipUTF8 (v[0]);
}
