/*
 *  mdc_line.cpp
 *  mdcanvas
 *
 *  Created by Alfredo Kojima on 07/May/1.
 *  Copyright 2007 MySQL AB. All rights reserved.
 *
 */

#include "stdafx.h"



#include "mdc_line.h"

#include "mdc_interaction_layer.h"
#include "mdc_vertex_handle.h"
#include "mdc_box_handle.h"
#include "mdc_connector.h"
#include "mdc_magnet.h"
#include "mdc_algorithms.h"
#include "mdc_canvas_view.h"

#define LINE_POINT_THRESHOLD 5


using namespace mdc;


LineLayouter::LineLayouter()
{
}


LineLayouter::~LineLayouter()
{
}


std::vector<ItemHandle*> LineLayouter::create_handles(Line *line, InteractionLayer *ilayer)
{
  std::vector<ItemHandle*> handles;
  ItemHandle *hdl;

  Connector *conn;
  
  conn= get_start_connector();
  if (conn && conn->is_draggable())
  {
    hdl= new VertexHandle(ilayer, line, line->get_layouter()->get_start_point(), true);
    hdl->set_tag(1);
    handles.push_back(hdl);
  }

  conn= get_end_connector();
  if (conn && conn->is_draggable())
  {
    hdl= new VertexHandle(ilayer, line, line->get_layouter()->get_end_point(), true);
    hdl->set_tag(2);
    handles.push_back(hdl);
  }

  return handles;
}


void LineLayouter::update_handles(Line *line, std::vector<ItemHandle*> &handles)
{
  if (!handles.empty())
  {
    for (std::vector<ItemHandle*>::iterator hdl= handles.begin(); hdl != handles.end(); ++hdl)
    {
      if ((*hdl)->get_tag() == 1)
        (*hdl)->move(get_start_connector()->get_position());
      else if ((*hdl)->get_tag() == 2)
        (*hdl)->move(get_end_connector()->get_position());
    }
  }
}



bool LineLayouter::handle_dragged(Line *line, ItemHandle *handle, const Point &pos, bool dragging)
{
  return false;
}



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

Line::Line(Layer *layer, LineLayouter *layouter)
: Figure(layer), _layouter(0)
{
  _start_type= NormalEnd;
  _end_type= NormalEnd;
  
  _hop_crossings= true;

  _line_pattern= SolidPattern;
  _line_width= 1.0;

  set_auto_sizing(false);

  set_accepts_focus(true);
  set_accepts_selection(true);

  _vertices.push_back(Point(0,0));
  _vertices.push_back(Point(100,200));

  if (layouter)
    set_layouter(layouter);
}


Line::~Line()
{
  delete _layouter;
}


void Line::set_layouter(LineLayouter *layouter)
{
  _layouter= layouter;

  _layouter->signal_changed().connect(sigc::mem_fun(this, &Line::update_layout));

  _layouter->update();
}


void Line::update_layout()
{
  set_vertices(_layouter->get_points());

  if (_hop_crossings)
    get_view()->update_line_crossings(this);

  _layout_changed.emit();
}


void Line::set_line_pattern(CairoCtx *cr, LinePatternType pattern)
{
  if (pattern != SolidPattern)
  {
    double dashes[9][5]= {
   //# items  paint-skip-paint-skip...
      {0,    0},          // SolidPattern
      {2,    2, 2},       // Dotted1Pattern
      {2,    2, 4},       // Dotted2Pattern
      {2,    5, 4},       // Dashed1Pattern
      {2,    10, 4},      // Dashed2Pattern
      {2,    4,  5},      // Dashed3Pattern
      {2,    4,  10},     // Dashed4Pattern
      {4,    10, 2, 2, 2},// DashDot1Pattern
      {4,    10, 2, 4, 2} // DashDot2Pattern
    };

    if (dashes[pattern][0] != 0.0)
      cr->set_dash(dashes[pattern]+1, (int)dashes[pattern][0], 0.0);
  }
}


void Line::draw_contents(CairoCtx *cr)
{
  cr->translate(get_position());

#if 0
  // draws debugging bounding box
  cr->save();
  cr->set_color(mdc::Color(0.8,0.8,0.8));
  cr->move_to(_segments.front().pos.x+0.5, _segments.front().pos.y+0.5);
  cr->line_to(_segments.back().pos.x+0.5, _segments.back().pos.y+0.5);
  cr->stroke();
  cr->restore();
  cr->save();
  cr->set_color(mdc::Color(0.8,0.8,0.8));
  cr->rectangle(0, 0, get_size().width, get_size().height);
  cr->stroke(); 

  
  cr->set_color(mdc::Color(1,0,0));
  cr->move_to(_segments.front().pos);
  cr->line_to(_segments.back().pos);
  cr->stroke();

  cr->restore();
#endif
  
  stroke_outline(cr);

  cr->set_line_width(_line_width);
  cr->set_color(_pen_color);
  cr->set_line_cap(CAIRO_LINE_CAP_SQUARE);
  set_line_pattern(cr, _line_pattern);

  cr->stroke();

  cr->set_dash(0, 0, 0);

  draw_line_ends(cr);
}


bool Line::contains_point(const Point &point) const
{  
  Point p;

  // if the line is horizontal or vertical, the bounding box is too small, so we give some slack
  if (!CanvasItem::contains_point(point))
  {
    Rect r= get_root_bounds();
    bool flag= false;

    if (r.width() <= 2)
    {
      r.pos.x -= (3-r.width())/2;
      r.size.width += 4-r.width();
      flag= true;
    }
    if (r.height() <= 2)
    {
      r.pos.y -= (3-r.height())/2;
      r.size.height += 4-r.height();
      flag= true;
    }

    if (flag)
      return bounds_contain_point(r, point.x, point.y);

    return false;
  }

  p= convert_point_from(point, get_parent());
  
  // check if the point is in any of the segments of this line
  
  std::vector<SegmentPoint>::const_iterator iter= _segments.begin();
  Point prev= iter->pos, cur;
  ++iter;
  for (; iter != _segments.end(); ++iter)
  {
    cur= iter->pos;

    if (cur.x == prev.x || cur.y == prev.y)
    {
      Point corner= Point(std::min(cur.x, prev.x)-LINE_POINT_THRESHOLD, std::min(cur.y, prev.y)-LINE_POINT_THRESHOLD);
      Size size= Size(fabs(cur.x-prev.x)+LINE_POINT_THRESHOLD*2, fabs(cur.y-prev.y)+LINE_POINT_THRESHOLD*2);

      // orthogonal segment, do a simple bounds check
      if (bounds_contain_point(Rect(corner, size), p.x, p.y))
        return true;
    }
    else
    {
      double d= point_line_distance(prev, cur, p);
      if (fabs(d) <= LINE_POINT_THRESHOLD)
        return true;
    }

    prev= cur;
  }
  
  return false;
}


void Line::draw_outline_ring(CairoCtx *cr, const Color &color)
{  
  cr->save();
  
  cr->translate(get_position());

  cr->set_color(color, 0.6);
  cr->set_line_width(4);
  stroke_outline(cr);
  cr->stroke_preserve();
  
  cr->set_color(color, 0.3);
  cr->set_line_width(8);
  cr->stroke();
  
  cr->restore();
}


static void draw_line_end(CairoCtx *cr, LineEndType type, const Color &lcolor, const Color &bcolor)
{
  switch (type)
  {
  case NormalEnd:
    break;

  case FilledTriangleEnd:
    cr->move_to(0.5, 0.5);
    cr->line_to(-3.5, 7.5);
    cr->line_to(3.5, 7.5);
    cr->line_to(0.5, 0.5);
    cr->close_path();
    cr->set_color(lcolor);
    cr->stroke_preserve();
    cr->fill();
    break;

  case HollowTriangleEnd:
    cr->move_to(0, 0);
    cr->line_to(-4, 8);
    cr->line_to(4, 8);
    cr->close_path();
    cr->set_color(bcolor);
    cr->fill_preserve();
    cr->set_color(lcolor);
    cr->stroke_preserve();
    break;
    
  case ChickenFootEnd:
    cr->set_color(lcolor);
    cr->move_to(-5, 0);
    cr->line_to(0, 10);
    cr->line_to(5, 0);
    cr->stroke();
    break;

  case ChickenFoot0End:
    cr->set_color(lcolor);
    cr->move_to(-5, 0);
    cr->line_to(0, 10);
    cr->line_to(5, 0);
    cr->stroke();      
    cr->arc(0, 12, 3, 0, 2*M_PI);
    cr->set_color(bcolor);
    cr->fill_preserve();
    cr->set_color(lcolor);
    cr->stroke();
    break;
    
  case ChickenFoot1End:
    cr->move_to(-5, 0);
    cr->line_to(0, 10);
    cr->line_to(5, 0);
    cr->set_color(lcolor);
    cr->stroke();
    cr->move_to(-5, 12);
    cr->line_to(5, 12);
    cr->stroke();
    break;
    
  case Cross0End:
    cr->move_to(-4, 6);
    cr->line_to(4, 6);
    cr->set_color(lcolor);
    cr->stroke();
    cr->arc(0, 12, 4, 0, 2*M_PI);
    cr->set_color(bcolor);
    cr->fill_preserve();
    cr->set_color(lcolor);
    cr->stroke();      
    break;
    
  case Cross1End:
    cr->move_to(-4, 6);
    cr->line_to(4, 6);
    cr->set_color(lcolor);
    cr->stroke();
    cr->move_to(-4, 10);
    cr->line_to(4, 10);
    cr->stroke();      
    break;

  case HollowDiamondEnd:
    cr->new_path();
    cr->move_to(0.5, 0.5);
    cr->line_to(-3.5, 6.5);
    cr->line_to(0.5, 13);
    cr->line_to(4.5, 6.5);
    cr->close_path();
    cr->set_color(bcolor);
    cr->fill_preserve();
    cr->set_color(lcolor);
    cr->stroke();
    break;

  case FilledDiamondEnd:
    cr->new_path();
    cr->move_to(0.5, 0.5);
    cr->line_to(-3.5, 6.5);
    cr->line_to(0.5, 13);
    cr->line_to(4.5, 6.5);
    cr->close_path();
    cr->set_color(lcolor);
    cr->fill_preserve();
    cr->stroke();
    break;

  case HollowCircleEnd:
    cr->arc(0, 4, 4, 0, 2*M_PI);
    cr->set_color(lcolor);
    cr->stroke();
    break;

  case FilledCircleEnd:
    cr->arc(0, 4, 4, 0, 2*M_PI);
    cr->set_color(lcolor);
    cr->fill_preserve();
    cr->stroke();      
    break;

  case BoldStickEnd:
    cr->move_to(0.5, 0.5);
    cr->line_to(0.5, 15.5);
    cr->set_color(lcolor);
    cr->set_line_width(3);
    cr->stroke();
    break;
  }
}


void Line::stroke_outline(CairoCtx *cr, float offset) const
{  
  std::vector<SegmentPoint>::const_iterator pv, v= _segments.begin();

  if (v == _segments.end())
    return;

  cr->move_to(v->pos.x+0.5, v->pos.y+0.5);
  pv= v;
  while (++v != _segments.end())
  {
    Point pos(v->pos.round());
    if (v->hop)
    {
      Point p(pos);
      double angle= -angle_of_line(pv->pos, v->pos);
      double rangle= angle*M_PI/180;
      double dx, dy;
      
      dx= cos(rangle) * 5;
      dy= sin(rangle) * 5;

      p= p - Point(dx, dy).round();
      cr->line_to(p.x+0.5, p.y+0.5);
      
      cr->arc(pos.x, pos.y, 5, (angle+180)*M_PI/180, (angle)*M_PI/180);
      
      p= p + Point(dx, dy);
      p= p + Point(dx, dy);
    }
    else
      cr->line_to(pos.x+0.5, pos.y+0.5);
    pv= v;
  }
}



double Line::get_line_start_angle()
{
  std::vector<Point>::const_iterator iter= _vertices.begin();
  ++iter;

  return angle_of_line(_vertices.front(), *iter);
}


double Line::get_line_end_angle()
{
  std::vector<Point>::const_reverse_iterator iter= _vertices.rbegin();
  ++iter;

  return angle_of_line(_vertices.back(), *iter);
}


void Line::draw_line_ends(CairoCtx *cr)
{
  cr->save();

  cr->translate(_segments.front().pos);
  cr->rotate(M_PI*(270-get_line_start_angle())/180.0);
  draw_line_end(cr, _start_type, _pen_color, _fill_color);
  cr->restore();
  
  cr->save();

  cr->translate(_segments.back().pos);
  cr->rotate(M_PI*(270-get_line_end_angle())/180.0);
  draw_line_end(cr, _end_type, _pen_color, _fill_color);
  
  cr->restore();
}



void Line::set_hops_crossings(bool flag)
{
  _hop_crossings= flag;
}

  
void Line::set_end_type(LineEndType start, LineEndType end)
{
  _start_type= start;
  _end_type= end;
  set_needs_render();
}


void Line::set_line_pattern(LinePatternType pattern)
{
  _line_pattern= pattern;
  if (_line_pattern >= LastPattern)
    _line_pattern= SolidPattern;
}



void Line::set_vertices(const std::vector<Point> &points)
{
  _vertices= points;

  update_bounds();
  set_needs_render();
}


void Line::add_vertex(const Point &pos)
{
  _vertices.push_back(pos);

  update_bounds();
  set_needs_render();
}


void Line::set_vertex(size_t vertex, const Point &pos)
{
  _vertices[vertex]= pos;

  update_bounds();
  set_needs_render();
}


void Line::mark_crossings(Line *line)
{
  bool changed= false;
  int count= 0;
  // we use oldlist to store the old intersections and to check if the new ones
  // are the same
  std::vector<SegmentPoint> oldlist;
  std::vector<SegmentPoint>::const_iterator olditer;

  // look for intersections between our segments and the other line's segments
  
  // first remove all hops on that line
  size_t i= 0;
  while (i < _segments.size())
  {
    std::vector<SegmentPoint>::iterator p= _segments.begin()+i;

    if (p->hop == line)
    {
      oldlist.push_back(*p);
      _segments.erase(p);
    }
    else
      i++;
  }
  olditer= oldlist.begin();

  // then look for intersections
  Point p1, p2;
  Point op1, op2;
  Point offset= line->get_root_position() - get_root_position();

  p1= _segments[0].pos;

  i= 0;
  while (i < _segments.size())
  {
    p2= _segments[i].pos;

    std::vector<SegmentPoint>::const_iterator oiter= line->_segments.begin();
    op1= oiter->pos + offset;
    ++oiter;
    for (; oiter != line->_segments.end(); ++oiter)
    {
      Point intersection;

      op2= oiter->pos + offset;

      if (intersect_lines(p1, p2, op1, op2, intersection))
      {
        // check if the list of intersections with this line keeps being the same
        if (olditer != oldlist.end())
        {
          if (olditer->pos != intersection)
            changed= true;
          ++olditer;
        }

        _segments.insert(_segments.begin()+i++, SegmentPoint(intersection, line));
        count++;
      }
      op1= op2;
    }
    p1= p2;
    ++i;
  }
  
  // check if all intersections were removed
  if (count == 0 && !oldlist.empty())
    changed= true;

  if (changed)
    set_needs_render();
}


void Line::update_bounds()
{
  if (_vertices.size() <= 1)
  {
    set_bounds(Rect());
  }
  else
  {
    double xmin= INFINITY, ymin= INFINITY, xmax= 0, ymax= 0;

    // find new boundaries
    for (std::vector<Point>::const_iterator v= _vertices.begin(); 
         v != _vertices.end(); ++v)
    {
      xmin= std::min(v->x, xmin);
      ymin= std::min(v->y, ymin);
      xmax= std::max(v->x, xmax);
      ymax= std::max(v->y, ymax);
    }

    Point new_pos(xmin, ymin);

    // update the line's bounding box to the new bounds
    set_bounds(Rect(xmin, ymin, xmax-xmin, ymax-ymin));

    // update the segments. crossings should be handled by a listener of layout_changed
    _segments.clear();
    for (std::vector<Point>::const_iterator iter= _vertices.begin(); 
      iter != _vertices.end(); ++iter)
    {
      _segments.push_back(SegmentPoint(*iter - new_pos, 0));
    }
  }

  update_handles();

  _layout_changed.emit();
}


void Line::resize_to(const Size &size)
{
  CanvasItem::resize_to(size);
}


void Line::move_to(const Point &pos)
{
  CanvasItem::move_to(pos);
}




void Line::create_handles(InteractionLayer *ilayer)
{
  if (_layouter)
  {
    _handles= _layouter->create_handles(this, ilayer);

    for (std::vector<ItemHandle*>::const_iterator iter= _handles.begin(); 
      iter != _handles.end(); ++iter)
      ilayer->add_handle(*iter);
  }
}


void Line::update_handles()
{
  if (_layouter)
    _layouter->update_handles(this, _handles);
}


bool Line::on_drag_handle(ItemHandle *handle, const Point &pos, bool dragging)
{
  if (_layouter)
  {
    bool flag= _layouter->handle_dragged(this, handle, pos, dragging);
    if (flag)
    {
      update_layout();
      set_needs_render();
    }
    return flag;
  }
  return false;
}
