// Copyright (C)  2003  Dominique Devriese <devriese@kde.org>

// 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 "exporter.h"

#include "imageexporteroptions.h"
#include "latexexporter.h"
#include "svgexporter.h"

#include "../kig/kig_document.h"
#include "../kig/kig_part.h"
#include "../kig/kig_view.h"
#include "../misc/common.h"
#include "../misc/kigfiledialog.h"
#include "../misc/kigpainter.h"
#include "../objects/circle_imp.h"
#include "../objects/line_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/text_imp.h"

#include <tqcheckbox.h>
#include <tqcolor.h>
#include <tqfile.h>
#include <tqiconset.h>
#include <tqtextstream.h>

#include <tdeaction.h>
#include <kiconloader.h>
#include <kimageio.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <knuminput.h>

#include <map>

// we need this for storing colors in a std::map..
static bool operator<( const TQColor& a, const TQColor& b )
{
  return a.rgb() < b.rgb();
}

class ExporterAction
  : public TDEAction
{
  KigExporter* mexp;
  const KigPart* mdoc;
  KigWidget* mw;
public:
  ExporterAction( const KigPart* doc, KigWidget* w,
                  TDEActionCollection* parent, KigExporter* exp );
  void slotActivated();
};

ExporterAction::ExporterAction( const KigPart* doc, KigWidget* w,
                                TDEActionCollection* parent, KigExporter* exp )
  : TDEAction( exp->menuEntryName(), TDEShortcut(), 0, 0, parent ),
    mexp( exp ), mdoc( doc ), mw( w )
{
  TQString iconstr = exp->menuIcon();
  if ( iconstr.isEmpty() )
    return;
  TDEIconLoader* l = doc->instance()->iconLoader();
  TQPixmap icon = l->loadIcon( iconstr, TDEIcon::Small, 16, TDEIcon::DefaultState, 0L, true );
  if ( !icon.isNull() )
    setIconSet( TQIconSet( icon ) );
}

void ExporterAction::slotActivated()
{
  mexp->run( *mdoc, *mw );
}

KigExporter::~KigExporter()
{
}

ImageExporter::~ImageExporter()
{
}

TQString ImageExporter::exportToStatement() const
{
  return i18n( "&Export to image" );
}

TQString ImageExporter::menuEntryName() const
{
  return i18n( "&Image..." );
}

TQString ImageExporter::menuIcon() const
{
  return "image-x-generic";
}

void ImageExporter::run( const KigPart& doc, KigWidget& w )
{
  static bool kimageioRegistered = false;
  if ( ! kimageioRegistered )
  {
    KImageIO::registerFormats();
    kimageioRegistered = true;
  }

  KigFileDialog* kfd = new KigFileDialog(
      TQString(), KImageIO::pattern( KImageIO::Writing ),
      i18n( "Export as Image" ), &w );
  kfd->setOptionCaption( i18n( "Image Options" ) );
  ImageExporterOptions* opts = new ImageExporterOptions( 0L, w.size() );
  kfd->setOptionsWidget( opts );
  opts->WidthInput->setValue( w.size().width() );
  opts->HeightInput->setValue( w.size().height() );
  opts->showGridCheckBox->setChecked( doc.document().grid() );
  opts->showAxesCheckBox->setChecked( doc.document().axes() );
  if ( !kfd->exec() )
    return;

  TQString filename = kfd->selectedFile();
  bool showgrid = opts->showGridCheckBox->isOn();
  bool showaxes = opts->showAxesCheckBox->isOn();
  int width = opts->WidthInput->value();
  int height = opts->HeightInput->value();

  delete opts;
  delete kfd;

  TQString type = KImageIO::type( filename );
  if ( type.isNull() )
  {
    KMessageBox::sorry( &w, i18n( "Sorry, this file format is not supported." ) );
    return;
  };

  kdDebug() << k_funcinfo << type << endl;

  TQFile file( filename );
  if ( ! file.open( IO_WriteOnly ) )
  {
    KMessageBox::sorry( &w,
                        i18n( "The file \"%1\" could not be opened. Please check if the file permissions are set correctly." )
                        .arg( filename ) );
    return;
  };

  TQPixmap img( TQSize( width, height ) );
  img.fill( TQt::white );
  KigPainter p( ScreenInfo( w.screenInfo().shownRect(), img.rect() ), TQT_TQPAINTDEVICE(&img), doc.document() );
  p.setWholeWinOverlay();
  p.drawGrid( doc.document().coordinateSystem(), showgrid, showaxes );
  // FIXME: show the selections ?
  p.drawObjects( doc.document().objects(), false );
  if ( ! img.save( filename, type.latin1() ) )
  {
    KMessageBox::error( &w, i18n( "Sorry, something went wrong while saving to image \"%1\"" ).arg( filename ) );
  }

}

KigExportManager::KigExportManager()
{
  mexporters.push_back( new ImageExporter );
  // working on this one ;)
  mexporters.push_back( new XFigExporter );
  mexporters.push_back( new LatexExporter );
  mexporters.push_back( new SVGExporter );
}

KigExportManager::~KigExportManager()
{
  for ( uint i = 0; i < mexporters.size(); ++i )
    delete mexporters[i];
}

void KigExportManager::addMenuAction( const KigPart* doc, KigWidget* w,
                                      TDEActionCollection* coll )
{
  TDEActionMenu* m =
    new TDEActionMenu( i18n( "&Export To" ), coll, "file_export" );
  for ( uint i = 0; i < mexporters.size(); ++i )
    m->insert( new ExporterAction( doc, w, coll, mexporters[i] ) );
}

KigExportManager* KigExportManager::instance()
{
  static KigExportManager m;
  return &m;
}

XFigExporter::~XFigExporter()
{
}

TQString XFigExporter::exportToStatement() const
{
  return i18n( "Export to &XFig file" );
}


TQString XFigExporter::menuEntryName() const
{
  return i18n( "&XFig File..." );
}

TQString XFigExporter::menuIcon() const
{
  return "kig_xfig";
}

class XFigExportImpVisitor
  : public ObjectImpVisitor
{
  TQTextStream& mstream;
  ObjectHolder* mcurobj;
  const KigWidget& mw;
  Rect msr;
  std::map<TQColor, int> mcolormap;
  int mnextcolorid;
  int mcurcolorid;

  TQPoint convertCoord( const Coordinate& c )
    {
      Coordinate ret = ( c - msr.bottomLeft() );
      ret.y = msr.height() - ret.y;
//       kdDebug() << "msr: " << msr << endl
//                 << "ret: " << ret << endl
//                 << "c: " << c << endl;
      ret *= 9450;
      ret /= msr.width();
      return ret.toTQPoint();
    }

  void emitLine( const Coordinate& a, const Coordinate& b, int width, bool vector = false );
public:
  void visit( ObjectHolder* obj );
  void mapColor( const ObjectDrawer* obj );

  XFigExportImpVisitor( TQTextStream& s, const KigWidget& w )
    : mstream( s ), mw( w ), msr( mw.showingRect() ),
      mnextcolorid( 32 )
    {
      // predefined colors in XFig..
      mcolormap[TQt::black] = 0;
      mcolormap[TQt::blue] = 1;
      mcolormap[TQt::green] = 2;
      mcolormap[TQt::cyan] = 3;
      mcolormap[TQt::red] = 4;
      mcolormap[TQt::magenta] = 5;
      mcolormap[TQt::yellow] = 6;
      mcolormap[TQt::white] = 7;
    }
  void visit( const LineImp* imp );
  void visit( const PointImp* imp );
  void visit( const TextImp* imp );
  void visit( const AngleImp* imp );
  void visit( const VectorImp* imp );
  void visit( const LocusImp* imp );
  void visit( const CircleImp* imp );
  void visit( const ConicImp* imp );
  void visit( const CubicImp* imp );
  void visit( const SegmentImp* imp );
  void visit( const RayImp* imp );
  void visit( const ArcImp* imp );
};

void XFigExportImpVisitor::mapColor( const ObjectDrawer* obj )
{
  if ( ! obj->shown() ) return;
  TQColor color = obj->color();
  if ( mcolormap.find( color ) == mcolormap.end() )
  {
    int newcolorid = mnextcolorid++;
    mstream << "0 "
            << newcolorid << " "
            << color.name() << "\n";
    mcolormap[color] = newcolorid;
  }
}

void XFigExportImpVisitor::visit( ObjectHolder* obj )
{
  if ( ! obj->drawer()->shown() ) return;
  assert( mcolormap.find( obj->drawer()->color() ) != mcolormap.end() );
  mcurcolorid = mcolormap[ obj->drawer()->color() ];
  mcurobj = obj;
  obj->imp()->visit( this );
}

void XFigExportImpVisitor::visit( const LineImp* imp )
{
  Coordinate a = imp->data().a;
  Coordinate b = imp->data().b;
  calcBorderPoints( a, b, msr );
  int width = mcurobj->drawer()->width();
  if ( width == -1 ) width = 1;

  if ( a != b )
    emitLine( a, b, width );
}

void XFigExportImpVisitor::emitLine( const Coordinate& a, const Coordinate& b, int width, bool vector )
{
  mstream << "2 "; // polyline type;
  mstream << "1 "; // polyline subtype;
  mstream << "0 "; // line_style: Solid
  mstream << width << " "; // thickness: *1/80 inch
  mstream << mcurcolorid << " "; // pen_color: default
  mstream << "7 "; // fill_color: white
  mstream << "50 "; // depth: 50
  mstream << "-1 "; // pen_style: unused by XFig
  mstream << "-1 "; // area_fill: no fill
  mstream << "0.000 "; // style_val: the distance between dots and
                       // dashes in case of dotted or dashed lines..
  mstream << "0 "; // join_style: Miter
  mstream << "0 "; // cap_style: Butt
  mstream << "-1 "; // radius in case of an arc-box, but we're a
                    // polyline, so nothing here..
  if ( ! vector )
    mstream << "0 "; // forward arrow: no
  else
    mstream << "1 "; // forward arrow: yes
  mstream << "0 "; // backward arrow: no
  mstream << "2"; // a two points polyline..

  mstream << "\n\t ";

  if ( vector )
  {
    // first the arrow line in case of a vector..
    mstream << "1 " // arrow_type: closed triangle
            << "1 " // arrow_style: filled with pen color..
            << "1.00 " // arrow_thickness: 1
            << "195.00 " // arrow_width
            << "165.00 " // arrow_height
            << "\n\t";
  }
  TQPoint ca = convertCoord( a );
  TQPoint cb = convertCoord( b );

  mstream << ca.x() << " " << ca.y() << " " << cb.x() << " " << cb.y() << "\n";
}

void XFigExportImpVisitor::visit( const PointImp* imp )
{
  const TQPoint center = convertCoord( imp->coordinate() );
  int width = mcurobj->drawer()->width();
  if ( width == -1 ) width = 5;
  width *= 10;

  mstream << "1 "  // Ellipse type
          << "3 "  // circle defined by radius subtype
          << "0 "; // line_style: Solid
  mstream << "1 " << " " // thickness: *1/80 inch
          << mcurcolorid << " " // pen_color: default
          << mcurcolorid << " " // fill_color: black
          << "50 " // depth: 50
          << "-1 " // pen_style: unused by XFig
          << "20 " // area_fill: full saturation of the fill color
          << "0.000 " // style_val: the distance between dots and
                      // dashes in case of dotted or dashed lines..
          << "1 "  // direction: always 1
          << "0.0000 "  // angle: the radius of the x-axis: 0
          << center.x() << " " << center.y() << " " // the center..
          << width << " " << width << " " // radius_x and radius_y
          << center.x() << " " // start_x and start_y, appear
          << center.y() << " " // unused..
          << center.x() + width << " " // end_x and end_y,
          << center.y() << "\n";        // appear unused too...
}

void XFigExportImpVisitor::visit( const TextImp* imp )
{
  TQString text = imp->text();
  TQPoint coord = convertCoord( imp->surroundingRect().bottomLeft() );

  mstream << "4 "    // text type
          << "0 "    // subtype: left justfied
          << mcurcolorid << " "    // color: black
          << "50 "   // depth: 50
          << "-1 "   // pen style: unused
          << "0 "    // font: default
          << "11 "   // font-size: 11
          << "0 "    // angle
          << "0 "    // font-flags: all the defaults..
          << "500 500 " // height, width: large enough..
          << coord.x() << " " // x, y
          << coord.y() << " "
          << text.ascii() << "\\001" // text, terminated by \001
          << "\n";
}

void XFigExportImpVisitor::visit( const AngleImp* )
{
}

void XFigExportImpVisitor::visit( const VectorImp* imp )
{
  int width = mcurobj->drawer()->width();
  if ( width == -1 ) width = 1;
  emitLine( imp->a(), imp->b(), width, true );
}

void XFigExportImpVisitor::visit( const LocusImp* )
{

}

void XFigExportImpVisitor::visit( const CircleImp* imp )
{
  const TQPoint center = convertCoord( imp->center() );
  const int radius =
    ( convertCoord( imp->center() + Coordinate( imp->radius(), 0 ) ) - center ).x();

  mstream << "1 "  // Ellipse type
          << "3 "  // circle defined by radius subtype
          << "0 "; // line_style: Solid
  int width = mcurobj->drawer()->width();
  if ( width == -1 ) width = 1;
  mstream << width << " " // thickness: *1/80 inch
          << mcurcolorid << " " // pen_color: default
          << "7 " // fill_color: white
          << "50 " // depth: 50
          << "-1 " // pen_style: unused by XFig
          << "-1 " // area_fill: no fill
          << "0.000 " // style_val: the distance between dots and
                      // dashes in case of dotted or dashed lines..
          << "1 "  // direction: always 1
          << "0.0000 "  // angle: the radius of the x-axis: 0
          << center.x() << " " << center.y() << " " // the center..
          << radius << " " << radius << " " // radius_x and radius_y
          << center.x() << " " // start_x and start_y, appear
          << center.y() << " " // unused..
          << center.x() + radius << " " // end_x and end_y,
          << center.y() << "\n";        // appear unused too...
}

void XFigExportImpVisitor::visit( const ConicImp* imp )
{
  int width = mcurobj->drawer()->width();
  if ( width == -1 ) width = 1;
  if ( imp->conicType() == 1 )
  {
    // an ellipse, this is good, cause this allows us to export to a
    // real ellipse type..
    const ConicPolarData data = imp->polarData();

    // gather some necessary data..
    // the angle of the x axis..
    double anglex = atan2( data.esintheta0, data.ecostheta0 );
    // the exentricity
    double e = hypot( data.esintheta0, data.ecostheta0 );
    // the x radius is easy to find..
    double radiusx = data.pdimen / ( 1 - e * e );
    // the y radius is a bit harder: we first find the distance from
    // the focus point to the mid point of the two focuses, this is:
    double d = -e * data.pdimen / ( 1 - e * e );
    // the distance from the first focus to the intersection of the
    // second axis with the conic equals radiusx:
    double r = radiusx;
    double radiusy = sqrt( r*r - d*d );

    Coordinate center = data.focus1 - Coordinate( cos( anglex ), sin( anglex ) ).normalize( d );
    const TQPoint qcenter = convertCoord( center );
    const int radius_x = ( convertCoord( center + Coordinate( radiusx, 0 ) ) -
                           convertCoord( center ) ).x();
    const int radius_y = ( convertCoord( center + Coordinate( radiusy, 0 ) ) -
                           convertCoord( center ) ).x();
    const TQPoint qpoint2 = convertCoord( center + Coordinate( -sin( anglex ), cos( anglex ) ) * radiusy );

    mstream << "1 "  // ellipse type
            << "1 "  // subtype: ellipse defined by readii
            << "0 "  // line_style: Solid
            << width << " " // thickness
            << mcurcolorid << " "  // pen_color: black
            << "7 "  // fill_color: white
            << "50 " // depth: 50
            << "-1 " // pan_style: not used
            << "-1 " // area_fill: no fill
            << "0.000 " // style_val: not used
            << "1 " // direction: always 1
            << anglex << " " // angle of the main axis
            << qcenter.x() << " " // center
            << qcenter.y() << " "
            << radius_x << " " // radiuses
            << radius_y << " "
            << qcenter.x() << " " // start point
            << qcenter.y() << " "
            << qpoint2.x() << " " // end point
            << qpoint2.y() << " ";
  }
  else return;
}

void XFigExportImpVisitor::visit( const CubicImp* )
{
}

void XFigExportImpVisitor::visit( const SegmentImp* imp )
{
  Coordinate a = imp->data().a;
  Coordinate b = imp->data().b;
  int width = mcurobj->drawer()->width();
  if ( width == -1 ) width = 1;

  emitLine( a, b, width );
}

void XFigExportImpVisitor::visit( const RayImp* imp )
{
  Coordinate a = imp->data().a;
  Coordinate b = imp->data().b;
  calcRayBorderPoints( a, b, msr );

  int width = mcurobj->drawer()->width();
  if ( width == -1 ) width = 1;

  emitLine( a, b, width );
}

void XFigExportImpVisitor::visit( const ArcImp* imp )
{
  const Coordinate center = imp->center();
  const double radius = imp->radius();
  const double startangle = imp->startAngle();
  const double endangle = startangle + imp->angle();
  const double middleangle = ( startangle + endangle ) / 2;
  const Coordinate ad = Coordinate( cos( startangle ), sin( startangle ) ).normalize( radius );
  const Coordinate bd = Coordinate( cos( middleangle ), sin( middleangle ) ).normalize( radius );
  const Coordinate cd = Coordinate( cos( endangle ), sin( endangle ) ).normalize( radius );
  const TQPoint a = convertCoord( center + ad );
  const TQPoint b = convertCoord( center + bd );
  const TQPoint c = convertCoord( center + cd );
  const TQPoint cent = convertCoord( center );

  mstream << "5 "  // Ellipse type
          << "1 "  // subtype: open ended arc
          << "0 "; // line_style: Solid
  int width = mcurobj->drawer()->width();
  if ( width == -1 ) width = 1;
  mstream << width << " " // thickness: *1/80 inch
          << mcurcolorid << " " // pen_color: default
          << "7 " // fill_color: white
          << "50 " // depth: 50
          << "-1 " // pen_style: unused by XFig
          << "-1 " // area_fill: no fill
          << "0.000 " // style_val: the distance between dots and
                      // dashes in case of dotted or dashed lines..
          << "0 "; // cap_style: Butt..
  // 0 is clockwise, 1 is counterclockwise .
  int direction = imp->angle() > 0 ? 1 : 0;
    // direction next
  mstream << direction << " "  // direction..
          << "0 "  // forward_arrow: no
          << "0 "  // backward_arrow: no
          << cent.x() << " " << cent.y() << " " // the center..
          << a.x() << " " << a.y() << " " // x1, y1
          << b.x() << " " << b.y() << " " // x2, y2
          << c.x() << " " << c.y() << " " // x3, y3
          << "\n";
}

void XFigExporter::run( const KigPart& doc, KigWidget& w )
{
  KigFileDialog* kfd = new KigFileDialog(
      ":document", i18n( "*.fig|XFig Documents (*.fig)" ),
      i18n( "Export as XFig File" ), &w );
  if ( !kfd->exec() )
    return;

  TQString file_name = kfd->selectedFile();

  delete kfd;

  TQFile file( file_name );
  if ( ! file.open( IO_WriteOnly ) )
  {
    KMessageBox::sorry( &w, i18n( "The file \"%1\" could not be opened. Please "
                                  "check if the file permissions are set correctly." )
                        .arg( file_name ) );
    return;
  };
  TQTextStream stream( &file );
  stream << "#FIG 3.2\n";
  stream << "Landscape\n";
  stream << "Center\n";
  stream << "Metric\n";
  stream << "A4\n";
  stream << "100.00\n";
  stream << "Single\n";
  stream << "-2\n";
  stream << "1200 2\n";

  std::vector<ObjectHolder*> os = doc.document().objects();
  XFigExportImpVisitor visitor( stream, w );

  for ( std::vector<ObjectHolder*>::const_iterator i = os.begin();
        i != os.end(); ++i )
  {
    visitor.mapColor( ( *i )->drawer() );
  };

  for ( std::vector<ObjectHolder*>::const_iterator i = os.begin();
        i != os.end(); ++i )
  {
    visitor.visit( *i );
  };
}
