// Copyright (C)  2010,2011 Raoul Bourquin <raoulb@bluewin.ch>

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// 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., 51 Franklin Street, Fifth Floor, Boston, MA
// 02110-1301, USA.

#include "asyexporterimpvisitor.h"

#include "../misc/goniometry.h"
#include "../objects/circle_imp.h"
#include "../objects/cubic_imp.h"
#include "../objects/bezier_imp.h"
#include "../objects/curve_imp.h"
#include "../objects/line_imp.h"
#include "../objects/locus_imp.h"
#include "../objects/object_drawer.h"
#include "../objects/object_holder.h"
#include "../objects/object_imp.h"
#include "../objects/other_imp.h"
#include "../objects/point_imp.h"
#include "../objects/polygon_imp.h"
#include "../objects/text_imp.h"


void AsyExporterImpVisitor::newLine()
{
  mstream << "\n";
}


QString AsyExporterImpVisitor::emitPenColor( const QColor& c )
{
  QString pencolor(QLatin1String(""));
  // Asymptote definition of pen color
  pencolor = "rgb(" + QString::number(c.red()/255.0) + ',' + QString::number(c.green()/255.0) + ',' + QString::number(c.blue()/255.0) + ')';
  return pencolor;
}


QString AsyExporterImpVisitor::emitPenStyle( const Qt::PenStyle& style )
{
  QString penstyle(QLatin1String(""));
  // Asymptote definition of pen (line) style
  // TODO: Needs finetuning of Asymptote linestyle parameters
  if ( style == Qt::SolidLine ) {
    penstyle = QStringLiteral("solid");
  } else if ( style == Qt::DashLine ) {
    penstyle = QStringLiteral("dashed");
  } else if ( style == Qt::DotLine ) {
    penstyle = QStringLiteral("dotted");
  } else if ( style == Qt::DashDotLine ) {
    penstyle = QStringLiteral("dashdotted");
  } else if ( style == Qt::DashDotDotLine ) {
    penstyle = QStringLiteral("longdashdotted");
  }
  return penstyle;
}


QString AsyExporterImpVisitor::emitPenSize( const int width )
{
  // In this function we map the logical (integer) linewidth of Kig
  // to real line widths that can be used in Asymptote.
  // Default mapping is currently: asy_width = kig_width / 2.0
  QString pensize(QLatin1String(""));
  if ( width < 0 )
  {
    // Nothing specified, use asymptote default
    pensize = QStringLiteral("linewidth(0.5)");
  }
  else
  {
    // Asymptote definition of pen size
    pensize = "linewidth(" + QString::number(width/2.0) + ')';
  }
  return  pensize;
}


QString AsyExporterImpVisitor::emitPen( const QColor& c, const int width, const Qt::PenStyle& style )
{
  QString pen(QLatin1String(""));
  // Asymptote definition of a pen
  pen = emitPenColor(c) + '+' + emitPenSize(width) + '+' + emitPenStyle(style);
  return  pen;
}


QString AsyExporterImpVisitor::emitCoord( const Coordinate& c )
{
  QString ret(QLatin1String(""));
  ret = '(' + QString::number(c.x) + ',' + QString::number(c.y) + ')';
  return ret;
}


void AsyExporterImpVisitor::emitLine( const Coordinate& a, const Coordinate& b,
                                      const int width, const Qt::PenStyle s,
                                      bool vector )
{
  mstream << "path line = "
          << emitCoord( a )
          << "--"
          << emitCoord( b )
          << ";";
  newLine();

  if ( vector == true )
  {
    mstream << "draw(line, "
            << emitPen( mcurobj->drawer()->color(), width, s )
            << ", Arrow );";
  }
  else
  {
    mstream << "draw(line, "
            << emitPen( mcurobj->drawer()->color(), width, s )
            << " );";
  }
  newLine();
}


double AsyExporterImpVisitor::dimRealToCoord( int dim )
{
  QRect qr( 0, 0, dim, dim );
  Rect r = mw.screenInfo().fromScreen( qr );
  return fabs( r.width() );
}


void AsyExporterImpVisitor::visit( ObjectHolder* obj )
{
  if ( ! obj->drawer()->shown() )
    return;
  mcurobj = obj;
  obj->imp()->visit( this );
}


void AsyExporterImpVisitor::plotGenericCurve( const CurveImp* imp )
{
  std::vector< std::vector< Coordinate > > coordlist;
  coordlist.push_back( std::vector< Coordinate >() );
  uint curid = 0;

  Coordinate c;
  Coordinate prev = Coordinate::invalidCoord();
  for ( double i = 0.0; i <= 1.0; i += 0.0001 )
  {
    c = imp->getPoint( i, mw.document() );
    if ( !c.valid() )
    {
      if ( coordlist[curid].size() > 0 )
      {
        coordlist.push_back( std::vector< Coordinate >() );
        ++curid;
        prev = Coordinate::invalidCoord();
      }
      continue;
    }
    if ( ! ( ( fabs( c.x ) <= 10000 ) && ( fabs( c.y ) <= 10000 ) ) )
      continue;
    // if there's too much distance between this coordinate and the previous
    // one, then it's another piece of curve not joined with the rest
    if ( prev.valid() && ( c.distance( prev ) > 50.0 ) )
    {
      coordlist.push_back( std::vector< Coordinate >() );
      ++curid;
    }
    coordlist[curid].push_back( c );
    prev = c;
  }
  // special case for ellipse
  if ( const ConicImp* conic = dynamic_cast< const ConicImp* >( imp ) )
  {
    // if ellipse, close its path
    // THSI IS WRONG, think of ellipse arcs!!
    if ( conic->conicType() == 1 && coordlist.size() == 1 && coordlist[0].size() > 1 )
    {
      coordlist[0].push_back( coordlist[0][0] );
    }
  }
  for ( uint i = 0; i < coordlist.size(); ++i )
  {
    uint s = coordlist[i].size();
    // there's no point in draw curves empty or with only one point
    if ( s <= 1 )
      continue;

    uint linelength = 13;
    QString tmp;
    mstream << "path curve = ";
    for ( uint j = 0; j < s; ++j )
    {
      tmp = emitCoord( coordlist[i][j] );
      // Avoid too long lines in the output file
      if(linelength + tmp.length() > maxlinelength)
      {
    linelength = tmp.length();
    newLine();
      }
      else
      {
    linelength += tmp.length();
      }
      mstream << tmp;
      if ( j < s-1 )
      {
    linelength += 2;
    mstream << "--";
      }
      else
      {
    mstream << ";";
    newLine();
    linelength = 0;
      }
    }
    mstream << "draw(curve, "
            << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
            << " );";
    newLine();
  }
}


void AsyExporterImpVisitor::visit( const LineImp* imp )
{
  Coordinate a = imp->data().a;
  Coordinate b = imp->data().b;
  calcBorderPoints( a, b, msr );
  emitLine( a, b, mcurobj->drawer()->width(), mcurobj->drawer()->style() );
}


void AsyExporterImpVisitor::visit( const PointImp* imp )
{
  mstream << "pair point = "
          << emitCoord( imp->coordinate() )
          << ";";
  newLine();
  // The factor of 6 is necessary to get the asymptote default dot size
  // which is 6*asy_default_linewidth where asy_default_linewidth = 0.5
  int width = mcurobj->drawer()->width();
  if ( width == -1 ) width = 6;
  mstream << "dot(point, "
          << emitPen( mcurobj->drawer()->color(), width, mcurobj->drawer()->style() )
          << ");";
  newLine();
}


void AsyExporterImpVisitor::visit( const TextImp* imp )
{
  // FIXME: support multiline texts...
  mstream << "pair anchor = "
          << emitCoord( imp->coordinate() )
          << ";";
  newLine();
  mstream << "Label l = Label(\""
          << imp->text()
          << "\", "
          << emitPenColor( mcurobj-> drawer()->color() )
          << ");";
  newLine();
  if ( imp->hasFrame() )
  {
    mstream << "draw(l, box, anchor, textboxmargin);";
  }
  else
  {
    mstream << "draw(l, anchor);";
  }
  newLine();
}


void AsyExporterImpVisitor::visit( const AngleImp* imp )
{
  const Coordinate center = imp->point();
  // TODO: How to choose radius size?
  const double radius = 0.5;
  double startangle = imp->startAngle();
  double endangle = startangle + imp->angle();

  startangle = Goniometry::convert( startangle, Goniometry::Rad, Goniometry::Deg );
  endangle = Goniometry::convert( endangle, Goniometry::Rad, Goniometry::Deg );

  mstream << "path a = Arc("
          << emitCoord(center)
          << ", "
          << radius
          << ", "
          << startangle
          << ", "
          << endangle
          << " );";
  newLine();
  mstream << "draw(a, "
          << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
          << ", Arrow );";
  newLine();
}


void AsyExporterImpVisitor::visit( const VectorImp* imp )
{
  Coordinate a = imp->data().a;
  Coordinate b = imp->data().b;
  emitLine( a, b, mcurobj->drawer()->width(), mcurobj->drawer()->style(), true );
}


void AsyExporterImpVisitor::visit( const LocusImp* imp )
{
  plotGenericCurve( imp );
}


void AsyExporterImpVisitor::visit( const CircleImp* imp )
{
  mstream << "pair center = "
          << emitCoord( imp->center() )
          << ";";
  newLine();
  mstream << "real radius = " << imp->radius()
          << ";";
  newLine();
  mstream << "path circle = Circle(center, radius);";
  newLine();
  mstream << "draw(circle, "
          << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
          << " );";
  newLine();
}


void AsyExporterImpVisitor::visit( const ConicImp* imp )
{
  plotGenericCurve( imp );
}


void AsyExporterImpVisitor::visit( const CubicImp* imp )
{
  // FIXME: cubic are not drawn correctly with plotGenericCurve
  plotGenericCurve( imp );
}


void AsyExporterImpVisitor::visit( const SegmentImp* imp )
{
  Coordinate a = imp->data().a;
  Coordinate b = imp->data().b;
  emitLine( a, b, mcurobj->drawer()->width(), mcurobj->drawer()->style() );
}


void AsyExporterImpVisitor::visit( const RayImp* imp )
{
  Coordinate a = imp->data().a;
  Coordinate b = imp->data().b;
  calcRayBorderPoints( a, b, msr );
  emitLine( a, b, mcurobj->drawer()->width(), mcurobj->drawer()->style() );
}


void AsyExporterImpVisitor::visit( const ArcImp* imp )
{
  const Coordinate center = imp->center();
  const double radius = imp->radius();
  double startangle = imp->startAngle();
  double endangle = startangle + imp->angle();

  startangle = Goniometry::convert( startangle, Goniometry::Rad, Goniometry::Deg );
  endangle = Goniometry::convert( endangle, Goniometry::Rad, Goniometry::Deg );

  mstream << "path arc = Arc("
          << emitCoord(center)
          << ", "
          << radius
          << ", "
          << startangle
          << ", "
          << endangle
          << " );";
  newLine();
  mstream << "draw(arc, "
          << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
          << " );";
  newLine();
}


void AsyExporterImpVisitor::visit( const FilledPolygonImp* imp )
{
  uint linelength = 15;
  QString tmp;
  mstream << "path polygon = ";
  std::vector<Coordinate> pts = imp->points();
  for ( uint i = 0; i < pts.size(); i++ )
  {
    tmp = emitCoord( pts[i] );
    tmp.append("--");
    if ( linelength + tmp.length() > maxlinelength )
    {
      newLine();
      linelength = tmp.length();
    }
    else
    {
      linelength += tmp.length();
    }
    mstream << tmp;
  }
  mstream << "cycle;";
  newLine();
  mstream << "fill(polygon, "
          << emitPenColor( mcurobj->drawer()->color() )
          << "+opacity(0.5) );";
  newLine();
  mstream << "draw(polygon, "
          << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
          << " );";
  newLine();
}


void AsyExporterImpVisitor::visit(const ClosedPolygonalImp* imp)
{
  uint linelength = 15;
  QString tmp;
  mstream << "path polygon = ";
  std::vector<Coordinate> pts = imp->points();
  for ( uint i = 0; i < pts.size(); i++ )
  {
    tmp = emitCoord( pts[i] );
    tmp.append("--");
    if ( linelength + tmp.length() > maxlinelength )
    {
      newLine();
      linelength = tmp.length();
    }
    else
    {
      linelength += tmp.length();
    }
    mstream << tmp;
  }
  mstream << "cycle;";
  newLine();
  mstream << "draw(polygon, "
          << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
          << " );";
  newLine();
}


void AsyExporterImpVisitor::visit(const OpenPolygonalImp* imp)
{
  uint linelength = 15;
  QString tmp;
  mstream << "path polygon = ";
  std::vector<Coordinate> pts = imp->points();
  for ( uint i = 0; i < pts.size(); i++ )
  {
    tmp = emitCoord( pts[i] );
    if ( linelength + tmp.length() > maxlinelength )
    {
      newLine();
      linelength = tmp.length();
    }
    else
    {
      linelength += tmp.length();
    }
    mstream << tmp;
    if ( i < pts.size()-1 )
    {
      linelength += 2;
      mstream << "--";
    }
    else
    {
      linelength += 1;
      mstream << ";";
    }
  }
  newLine();
  mstream << "draw(polygon, "
          << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
          << " );";
  newLine();
}


void AsyExporterImpVisitor::visit ( const BezierImp* imp )
{
  std::vector<Coordinate> pts = imp->points();
  switch ( pts.size() )
  {
  case 3:
    // Formula for cubic control points
    // CP1 = QP0 + 2/3 *(QP1-QP0)
    // CP2 = CP1 + 1/3 *(QP2-QP0)
    mstream << "pair cp1 = " << emitCoord(pts.at(0)) << " +2/3*(" << emitCoord(pts.at(1)) << "-" << emitCoord(pts.at(0)) << ");";
    newLine();
    mstream << "pair cp2 = cp1 +1/3*(" << emitCoord(pts.at(2)) << "-" << emitCoord(pts.at(0)) << ");";
    newLine();
    mstream << "path bezier = ";
    mstream << emitCoord( pts.at(0) );
    mstream << " .. controls cp1 and cp2 .. ";
    mstream << emitCoord( pts.at(2) );
    mstream << ";";
    newLine();
    mstream << "draw(bezier, "
            << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
            << " );";
    newLine();
    break;
  case 4:
    mstream << "path bezier = ";
    mstream << emitCoord( pts.at(0) );
    mstream << " .. controls ";
    mstream << emitCoord( pts.at(1) );
    mstream << " and ";
    mstream << emitCoord( pts.at(2) );
    mstream << " .. ";
    mstream << emitCoord( pts.at(3) );
    mstream << ";";
    newLine();
    mstream << "draw(bezier, "
            << emitPen( mcurobj->drawer()->color(), mcurobj->drawer()->width(), mcurobj->drawer()->style() )
            << " );";
    newLine();
    break;
  default:
    plotGenericCurve ( imp );
    break;
  }
}


void AsyExporterImpVisitor::visit ( const RationalBezierImp* imp )
{
  plotGenericCurve ( imp );
}
