/** 
 *  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/SCaret.h>
#include "stoolkit/STypes.h" 

/**
 * Create a caret.
 * A caret can have two directions : < or >
 */
SCaret::SCaret (void)
  : lrpen (SColor (0.0, 0.0, 0.0, 1.0)), 
  rlpen (SColor (0.0, 0.0, 1.0, 1.0))
{
  direction = SS_DR_L;
  showCaret = true;
  skipBlink = false;
  state = true;
  lrpen.setBackground (SColor(1.0, 1.0, 1.0, 1.0));
  rlpen.setBackground (SColor(1.0, 1.0, 1.0, 1.0));
  timer = 0;
}

SCaret::~SCaret ()
{
  if (timer) delete timer;
}

/**
 * Set the direction of this cursor.
 * @param lr is true if we are an lr cursor.
 */
void
SCaret::setDirection (SS_DR_Dir dir)
{
  direction = dir;
  redraw ();
}

SS_DR_Dir
SCaret::getDirection() const
{
  return direction;
}

bool
SCaret::isLR () const
{
  bool lr = (direction == SS_DR_L 
       || direction == SS_DR_LE || direction == SS_DR_LO);
  return lr;
}

/**
 * Set the background.
 * @param bg is the new background
 */
void
SCaret::setBackground (const SColor& bg)
{
  lrpen.setBackground (bg);
  rlpen.setBackground (bg);
}

/**
 * Set the foreground.
 * @param fg is the new foreground
 */
void
SCaret::setForeground (const SColor& rlfg, const SColor& lrfg)
{
  lrpen.setForeground (rlfg);
  rlpen.setForeground (lrfg);
}

/**
 * The caret usually takes two glyp positions.
 * The middle is the insertion point
 */
void
SCaret::redraw (SCanvas* canvas)
{
  if (isSaneSize())
  {
    redraw (canvas, location.x, location.y, size.width, size.height);
  }
}

/**
 * The caret usually takes two glyp positions.
 * The middle is the insertion point
 * caret is draw in the middle.
 */
void
SCaret::redraw (SCanvas* canvas, int x, int y, unsigned int width, unsigned int height)
{
  if (!isOn()) return;
  if (state == false) return;
  if (!isSaneSize()) return;

  double caretHeight = getSize().height;
  // regular triangle
  double caretWidth = getSize().height * 1.732;
  /* linewidth */
#ifdef USE_OSX
  double lw = (double)caretWidth/10.0;
#else
  double lw = (double)caretWidth/12.0;
#endif
  double middleX = (double)getLocation().x;
  double middleY = (double)getLocation().y + (double)getSize().height/2.0;
  double dx = (double)caretWidth/3.0 ;
  double dy = (double)caretHeight/3.0;
  //double dist = (dx+0.5)/(dy+0.5);
  char a[64];
  //bool lr = (direction == SS_DR_L 
  //     || direction == SS_DR_LE || direction == SS_DR_LO);
  /* This is the key for the drawing engine */
  snprintf (a, 64, "caret=%u %u %c;", 
         getSize().width, getSize().height, 
        (char)direction);

  switch (direction)
  {
  case SS_DR_L:
    if (!canvas->beginImage (middleX, middleY - dy +1, a, lrpen.getBackground()))
    {
      canvas->newpath ();
      canvas->moveto (middleX, middleY - dy +1);
      canvas->lineto (middleX + dx -1, middleY);
      canvas->lineto (middleX, middleY + dy -1);
      canvas->lineto (middleX, middleY + dy -1 - lw * 0.57736);
      canvas->lineto (middleX + dx -1 - lw, middleY);
      canvas->lineto (middleX, middleY - dy +1 + lw * 0.57736);
      canvas->closepath ();
      canvas->fill (lrpen);
    }
    canvas->endImage ();
#if 0
// Another attempt.
    canvas->bitline (lrpen.getForeground(), 
         (int)middleX, (int)(middleY - dy +1), 
         (int)middleX, (int)(middleY + dy -1)); 
    canvas->bitline (lrpen.getForeground(), 
         (int)middleX, (int)(middleY - dy +1), 
         (int)(middleX + dx -1), (int)(middleY)); 
    canvas->bitline (lrpen.getForeground(), 
         (int)middleX, (int)(middleY + dy -1), 
         (int)(middleX + dx -1), (int)(middleY)); 
#endif
    break;
  case SS_DR_R:
    if (!canvas->beginImage (middleX, middleY - dy +1, a, lrpen.getBackground()))
    {
      canvas->newpath ();
      canvas->moveto (middleX, middleY - dy +1);
      canvas->lineto (middleX - dx +1, middleY);
      canvas->lineto (middleX, middleY + dy -1);
      canvas->lineto (middleX, middleY + dy -1 - lw * 0.57736);
      canvas->lineto (middleX - dx +1 + lw, middleY);
      canvas->lineto (middleX, middleY - dy +1 + lw * 0.57736);
      canvas->closepath ();
      canvas->fill (lrpen);
    }
    canvas->endImage();
/*
    canvas->bitline (lrpen.getForeground(), 
         (int)middleX, (int)(middleY - dy +1), 
         (int)middleX, (int)(middleY + dy -1)); 
*/
    break;
  case SS_DR_LE:
    if (!canvas->beginImage (middleX, middleY - dy +1, a, lrpen.getBackground()))
    {
      canvas->newpath ();
      canvas->moveto (middleX, middleY - dy +1);
      canvas->lineto (middleX + dx -1, middleY);
      canvas->lineto (middleX, middleY + dy -1);
      canvas->closepath ();

      canvas->moveto (middleX + lw/2.0, middleY - dy +1 + 0.866 * lw);
      canvas->lineto (middleX + lw/2.0, middleY + dy -1 - 0.866 * lw);
      canvas->lineto (middleX + dx -1 -lw, middleY);
      canvas->closepath ();
      canvas->fill (lrpen);
    }
    canvas->endImage ();
    break;
  case SS_DR_RE:
    if (!canvas->beginImage (middleX, middleY - dy +1, a, lrpen.getBackground()))
    {
      canvas->newpath ();
      canvas->moveto (middleX, middleY - dy +1);
      canvas->lineto (middleX - dx +1, middleY);
      canvas->lineto (middleX, middleY + dy -1);
      canvas->closepath ();
      /* anti-clock */
      canvas->moveto (middleX -lw/2.0, middleY - dy +1 + 0.866 *lw);
      canvas->lineto (middleX -lw/2.0 , middleY + dy -1 - 0.866 *lw);
      canvas->lineto (middleX - dx +1 +lw, middleY);
      canvas->closepath ();
      canvas->fill (lrpen);
    }
    canvas->endImage();
    break;
  case SS_DR_LO:
    if (!canvas->beginImage (middleX, middleY - dy +1, a, lrpen.getBackground()))
    {
      canvas->newpath ();
      canvas->moveto (middleX, middleY - dy +1);
      canvas->lineto (middleX + dx -1, middleY);
      canvas->lineto (middleX, middleY + dy -1);
      canvas->closepath ();
      canvas->fill (lrpen);
    }
    canvas->endImage ();
    break;
  case SS_DR_RO:
    if (!canvas->beginImage (middleX, middleY - dy +1, a, lrpen.getBackground()))
    {
      canvas->newpath ();
      canvas->moveto (middleX, middleY - dy +1);
      canvas->lineto (middleX - dx +1, middleY);
      canvas->lineto (middleX, middleY + dy -1);
      canvas->closepath ();
      canvas->fill (rlpen);
    }
    canvas->endImage ();
    break;
  }
}

/**
 * Show or hide the caret
 */
void
SCaret::on (bool _on)
{
  if (_on != showCaret) redraw ();
  showCaret = _on;
  if (state != _on) redraw ();
  state = _on;
  skipBlink = true;
}

/**
 * Request a redraw from parent if possible.
 * The redraw will happen async, later.
 */
void
SCaret::redraw ()
{
  SWindow* w = getWindow();
  if (w != 0)
  {
    /* to be in sync with STextView::textChanged() */ 
    int starty = (location.y > 5) ? location.y - 5: 0;
    unsigned int lheight = size.height + 9;
    if (isSaneSize())
    {
      w->redraw (true, location.x - (int) size.width, starty,
         size.width * 3 + 1,  lheight);
    }
  }
}

/**
 * Show if caret is shown
 */
bool
SCaret::isOn () const
{
  return showCaret;
}

/**
 * Show if it is animating.
 */
bool
SCaret::isAnimating() const
{
  return timer!=0;
}

/**
 * The timer event
 */
bool
SCaret::timeout (const SEventSource* s)
{
//  fprintf (stderr, "SCaret %lx::timeout\n", (unsigned long) this);
  if (timer == 0)
  {
    fprintf (stderr, "strange. timeout..\n");
    return false;
  }
  if (!showCaret)
  {
    return true;
  }
  if (skipBlink)
  {
    skipBlink = false;
    return true;
  }
  state = !state;
  redraw ();
  return true;
}

/**
 * Resize the component
 * @param d is the new size
 */
void 
SCaret::resize(const SDimension& d)
{
  redraw ();
  SComponent::resize (d);
  redraw ();
}

/**
 * Move the component
 * @param l is the new location
 */
void 
SCaret::move(const SLocation& l)
{
  redraw ();
  SComponent::move (l);
  redraw ();
}

/**
 * Move the component
 * @param l is the new location
 */
void 
SCaret::move(const SCursorIndex& index)
{
  cursorIndex = index;
}

/**
 * Do bliking animation of 
 * @param _animate is true. 
 * stop animation otherwise.
 */
void
SCaret::animate (bool _animate)
{
  if ((timer!=0)==_animate) return;
  if (timer) delete timer;
  timer = 0;
  //fprintf (stderr, "SCaret %lx:: animate%d\n", (unsigned long) this, _animate);
  if (_animate)
  {
    timer = STimer::newTimer(500, this);
    skipBlink = false;
  }
  state = showCaret;
  redraw ();
}

/**
 * return true if it has a possible size.
 */
bool
SCaret::isSaneSize()
{
  SLocation up = getLocation() + getSize();
  /* out */
  if (up.x < 0 || up.y < 0) return false;
  /* out */
  if (up.x > 8000 || up.y > 8000) return false;
  return true;
}
