#include "ext/Selector.h"
#include "tgl.h"
//#include "drawutil.h"
#include "tmathutil.h"

using namespace ToonzExt;

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

namespace {
const GLfloat s_normalColor[]      = {250.0 / 255.0, 127 / 255.0, 240 / 255.0};
const GLfloat s_highlightedColor[] = {150.0 / 255.0, 255.0 / 255.0,
                                      140.0 / 255.0};
const double s_sqrt_2      = sqrt(2.0);
const double s_radius      = 5.0;
const double s_over_size   = 10;
const double s_length      = 15;
const double s_square_size = 5;
const double s_arrow_ratio = 2.5;

void drawArrow(const TPointD &from, const TPointD &direction, double length,
               const TPointD &arrowDirection = TPointD(0.0, -1.0),
               double arrowLength            = 0.0) {
  if (length <= 0 || arrowLength < 0) return;

  if (arrowLength == 0.0) arrowLength = 0.2 * length;

  TPointD pnt(from), myDir = normalize(direction);

  TPointD myArrowDirection(arrowDirection);

  if (arrowDirection.y <= 0.0) myArrowDirection = normalize(TPointD(-0.5, 0.8));

  if (direction * TPointD(1, 0) <= 0.0)
    myArrowDirection.x = -myArrowDirection.x;

  myDir            = myDir * length;
  myArrowDirection = myArrowDirection * arrowLength;

  glBegin(GL_LINES);
  tglVertex(pnt);
  pnt += myDir;
  tglVertex(pnt);
  glEnd();
  glBegin(GL_TRIANGLES);
  TPointD up = myDir + myArrowDirection;
  tglVertex(from + up);
  // tglVertex(pnt);
  tglVertex(pnt);
  myArrowDirection.y = -myArrowDirection.y;
  TPointD down       = myDir + myArrowDirection;
  tglVertex(from + down);
  glEnd();
}
}

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

Selector::Selector(double stroke_length, double min_val, double max_val) {
  original_stroke_length_ = stroke_length;
  range_                  = TPointD(min_val, max_val);
  isVisible_              = false;
  pixel_size_             = 1.0;
  w_                      = 0.5;
  this->init();
}

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

void Selector::init() {
  strokeRef_  = 0;
  signum_     = 1.0;
  isSelected_ = NONE;
}

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

Selector::~Selector() {}

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

TPointD Selector::getUp() const {
  if (!strokeRef_) return TPointD();

  TThickPoint pnt = strokeRef_->getPoint(w_);

  TPointD p = convert(pnt), p1;

  TPointD v0, v1, v = normalize(rotate90(strokeRef_->getSpeed(w_)));

  const double delta = TConsts::epsilon;

  if (w_ - delta >= 0.0)
    v0 = rotate90(strokeRef_->getSpeed(w_ - delta));
  else
    v0 = rotate90(strokeRef_->getSpeed(0));

  if (w_ + delta <= 1.0)
    v1 = rotate90(strokeRef_->getSpeed(w_ + delta));
  else
    v1 = rotate90(strokeRef_->getSpeed(1.0));

  if (!isAlmostZero(v * v0) || !isAlmostZero(v * v1)) v = normalize(v1 + v0);

  return v;
}

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

void Selector::draw(Designer *designer) {
  if (!strokeRef_ || !isVisible_) return;

  pixel_size_ = designer ? sqrt(designer->getPixelSize2()) : 1.0;

  TPointD v = this->getUp(), n = normalize(rotate90(v));

  TThickPoint pnt = strokeRef_->getThickPoint(w_);

  height_ = (pnt.thick + s_over_size) * pixel_size_;

  TPointD p(pnt.x, pnt.y), up = p + v * height_, down = p - v * height_;

  glColor3fv(s_normalColor);
  glBegin(GL_LINES);
  tglVertex(down);
  tglVertex(up);
  glEnd();

  if (isSelected_ == POSITION)
    glColor3fv(s_highlightedColor);
  else
    glColor3fv(s_normalColor);

  double radius = s_radius * pixel_size_;

  if (isSelected_ == POSITION) tglDrawDisk(up + v * radius, radius);

  tglDrawCircle(up + v * radius, radius);

  if (isSelected_ == LENGTH)
    glColor3fv(s_highlightedColor);
  else
    glColor3fv(s_normalColor);

  {
    // the circle center is in
    TPointD down = -this->getUp(), center = pnt + down * (height_);
    const double length = s_square_size * 0.5 * pixel_size_;

    TPointD
        //  offset(length, 0.0),
        offset(length, length),
        bottom_left = center - offset, top_right = center + offset;

    TRectD rectangle(bottom_left, top_right);

    if (isSelected_ == LENGTH) tglFillRect(rectangle);

    tglDrawRect(rectangle);
  }
  /*
TPointD
arrowDirection(-0.5,0.8);

if( signum_ < 0.0 )
arrowDirection.x = -arrowDirection.x;
const double
length = s_length  * pixel_size_,
arrow_length = length/s_arrow_ratio;

drawArrow(down,
      TPointD(1,0),
      length,
      arrowDirection,
      arrow_length);
drawArrow(down,
      TPointD(-1,0),
      length,
      arrowDirection,
      arrow_length);
*/
  // draw stroke related information
  if (designer && this->isSelected()) designer->draw(this);

#if 0  // def  _DEBUG
  {
  TThickPoint
    pnt = strokeRef_->getThickPoint(w_);

  TPointD
      up = this->getUp(),
      down = -up;

  double
    radius = s_radius * pixel_size_;

  // the circle center is in 
  TPointD 
    center = pnt + up * ( height_ + radius);

  glColor3d(1.0,0.0,1.0);
  tglDrawCircle(center,radius+pixel_size_);

  // one pixel of tolerance
  center = pnt + down * ( height_ );

  const double
    length = s_length * pixel_size_;

  TPointD
    offset(length, 0.0),
    bottom_left = center - offset,
    top_right   = center + offset;

  TRectD
    rectangle(bottom_left,
              top_right);

  rectangle = rectangle.enlarge( 2.0* pixel_size_);

  tglDrawRect(rectangle);
  }
#endif
}

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

void Selector::mouseDown(const TPointD &pos) {
  click_ = curr_ = pos;
  if (!strokeRef_) return;
  stroke_length_ = original_stroke_length_;
  prev_          = curr_;
}

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

void Selector::mouseUp(const TPointD &pos) {
  curr_ = pos;
  if (!strokeRef_) return;
  original_stroke_length_ = stroke_length_;
  prev_                   = curr_;
}

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

void Selector::mouseMove(const TPointD &pos) {
  curr_ = pos;
  if (!strokeRef_) return;

  isSelected_ = this->getSelection(pos);
  prev_       = curr_;
}

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

void Selector::mouseDrag(const TPointD &pos) {
  curr_ = pos;
  if (!strokeRef_) return;

  double val = original_stroke_length_;

  const double stroke_length = strokeRef_->getLength();

  double max_val = std::min(stroke_length, range_.y);

  TPointD v = curr_ - prev_;

  signum_ = 1.0;

  switch (isSelected_) {
  case POSITION:
    w_ = strokeRef_->getW(pos);
    break;
  case LENGTH:
    signum_        = v * TPointD(1.0, 0.0) < 0.0 ? -1.0 : 1.0;
    val            = original_stroke_length_ + signum_ * pixel_size_ * norm(v);
    stroke_length_ = std::max(std::min(max_val, val), range_.x);
    break;
  default:
    assert(false);
  }
  prev_ = curr_;
}

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

void Selector::setStroke(const TStroke *ref) {
  // if(strokeRef_  &&
  //   strokeRef_ != ref )
  //  w_=strokeRef_->getParameterAtLength( strokeRef_->getLength()*0.5);
  // else
  //  w_=0.5;
  this->init();
  strokeRef_ = ref;
}

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

Selector::Selection Selector::getSelection(const TPointD &pos) {
  if (!strokeRef_ || !isVisible_) return NONE;

  TThickPoint pnt = strokeRef_->getThickPoint(w_);

  TPointD up = this->getUp(), down = -up;

  double radius = s_radius * pixel_size_;

  // the circle center is in
  TPointD center = pnt + up * (height_ + radius);

  // one pixel of tolerance
  if (tdistance2(center, pos) <= sq(radius + pixel_size_)) return POSITION;

  center = pnt + down * (height_);

  // const double  length = s_length * pixel_size_;
  const double length = s_square_size * 0.5 * pixel_size_;

  TPointD
      //  offset(length, 0.0),
      offset(length, length),
      bottom_left = center - offset, top_right = center + offset;

  TRectD rectangle(bottom_left, top_right);

  rectangle = rectangle.enlarge(2.0 * pixel_size_);

  if (rectangle.contains(pos)) return LENGTH;

  return NONE;
}

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

double Selector::getLength() const { return stroke_length_; }

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

void Selector::setLength(double length) {
  stroke_length_ = original_stroke_length_ = length;
}

//-----------------------------------------------------------------------------
//  End Of File
//-----------------------------------------------------------------------------
