/**
 This file is part of Kig, a KDE program for Interactive Geometry...
 Copyright (C) 2002  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 USA
**/

#include "cabri-filter.h"

#include "../kig/kig_document.h"
#include "../kig/kig_part.h"
#include "../misc/coordinate.h"
#include "../objects/arc_type.h"
#include "../objects/bogus_imp.h"
#include "../objects/circle_type.h"
#include "../objects/conic_types.h"
#include "../objects/curve_imp.h"
#include "../objects/line_imp.h"
#include "../objects/line_type.h"
#include "../objects/object_calcer.h"
#include "../objects/object_drawer.h"
#include "../objects/object_factory.h"
#include "../objects/object_holder.h"
#include "../objects/other_imp.h"
#include "../objects/other_type.h"
#include "../objects/point_type.h"
#include "../objects/transform_types.h"
#include "../objects/vector_type.h"

#include <qcolor.h>
#include <qfile.h>
#include <qregexp.h>

#include <kdebug.h>
#include <klocale.h>

/**
 * a line:
 * "Nr": Type, Unknown, "CN:"NumberOfParents, "VN:"Unknown
 * Color, FillType, Thickness, "DS":Dots, "GT":SpecialAppearance, Visible, Fixed
 * ["Const": Parents] ["Val": Constants]
 *
 * Nr: Simple sequential numbering of the objects in a file.
 * Type: seen so far: Pt, Axes, Line, Cir
 * NumberOfParents: The number of parents that will be specified in
 *      Parents
 * Color:
 *   R -> red
 *   O -> purple
 *   Y -> yellow
 *   P -> dark purple
 *   V -> dark blue
 *   Bl -> blue
 *   lBl -> bright blue
 *   G -> bright green
 *   dG -> dark green
 *   Br -> brown
 *   dBr -> beige
 *   lGr -> light grey
 *   Gr -> grey
 *   dGr -> dark grey
 *   B -> black
 * FillType:
 *   W -> not filled ( white ? )
 *   all values of the Color item are valid here too..
 * Thickness:
 *   t -> thin
 *   tT -> medium
 *   T -> Thick
 * Dots:
 *   a specification for how a line should be drawn ( with many
 *   dots, with short lines, it's a pretty generic format: the first
 *   number signifies the number of sequential dots to draw first, and
 *   the next is the sum of this first number with the number of
 *   spaces to leave before starting a new segment.
 * SpecialAppearance:
 *   a number indicating some way of specially drawing an object. This
 *   can be modified using the "Modify Appearance" button in
 *   Cabri. For example, for a segment, the number indicates the
 *   amount of ticks to put on the segment, to indicate
 *   correspondances between segments..
 * Visible:
 *   V means visible, I means invisible
 * Fixed:
 *   St means fix this object ( if you move one of its parents, it
 *   won't move ), nSt ( the default ) means don't fix this object.
 * Parents:
 *   The numbers of the objects this object depends on
 * Constants:
 *   Constants whose meaning depends on the type of object.  E.g. for
 *   a point, this means first x, then y component.
 */

struct CabriObject
{
  uint id;
  QCString type;
  uint numberOfParents;
  QColor color;
  QColor fillColor;
  int thick;
  int lineSegLength;
  int lineSegSplit;
  int specialAppearanceSwitch;
  bool visible;
  bool fixed;
  std::vector<int> parents;
  std::vector<double> data;
};

KigFilterCabri::KigFilterCabri()
{
}

KigFilterCabri::~KigFilterCabri()
{
}

bool KigFilterCabri::supportMime( const QString& mime )
{
  // ugly hack to avoid duplicate extension ( XFig and Cabri files
  // have the same .fig extension ).
  return ( mime == "image/x-xfig" ) ||
         ( mime == "application/x-cabri" );
}

static QString readLine( QFile& file )
{
  QString ret;
  file.readLine( ret, 10000L );
  if ( ret[ret.length() - 1] == '\n' )
    ret.truncate( ret.length() - 1 );
  if ( ret[ret.length() - 1] == '\r' )
    ret.truncate( ret.length() - 1 );
  return ret;
}

static QColor translatecolor( const QString& s )
{
  if ( s == "R" ) return Qt::red;
  if ( s == "O" ) return Qt::magenta;
  if ( s == "Y" ) return Qt::yellow;
  if ( s == "P" ) return Qt::darkMagenta;
  if ( s == "V" ) return Qt::darkBlue;
  if ( s == "Bl" ) return Qt::blue;
  if ( s == "lBl" ) return Qt::cyan; // TODO: bright blue
  if ( s == "G" ) return Qt::green;
  if ( s == "dG" ) return Qt::darkGreen;
  if ( s == "Br" ) return QColor( 165, 42, 42 );
  if ( s == "dBr" ) return Qt::black; // TODO: beige-brown
  if ( s == "lGr" ) return Qt::lightGray;
  if ( s == "Gr" ) return Qt::gray;
  if ( s == "dGr" ) return Qt::darkGray;
  if ( s == "B" ) return Qt::black;
  if ( s == "W" ) return Qt::white;

  kdDebug() << k_funcinfo << "unknown color: " << s << endl;
  return Qt::black;
}

bool KigFilterCabri::readObject( QFile& f, CabriObject& myobj )
{
  // there are 4 lines per object in the file, so we read them all
  // four now.
  QString line1, line2, line3, s;
  QString file = f.name();
  line1 = readLine( f );
  line2 = readLine( f );
  line3 = readLine( f );
  // ignore line 4, it is empty..
  s = readLine( f );

  QRegExp firstlinere( "^([^:]+): ([^,]+), ([^,]+), CN:([^,]*), VN:(.*)$" );
  if ( ! firstlinere.exactMatch( line1 ) )
    KIG_FILTER_PARSE_ERROR;

  bool ok;
  QString tmp;

  tmp = firstlinere.cap( 1 );
  myobj.id = tmp.toInt( &ok );
  if ( !ok ) KIG_FILTER_PARSE_ERROR;

  tmp = firstlinere.cap( 2 );
  myobj.type = tmp.latin1();

  tmp = firstlinere.cap( 3 );
  // i have no idea what this number means..

  tmp = firstlinere.cap( 4 );
  myobj.numberOfParents = tmp.toInt( &ok );
  if ( ! ok ) KIG_FILTER_PARSE_ERROR;

  tmp = firstlinere.cap( 5 );
  // i have no idea what this number means..

  QRegExp secondlinere( "^([^,]+), ([^,]+), ([^,]+), DS:([^ ]+) ([^,]+), GT:([^,]+), ([^,]+), (.*)$" );
  if ( ! secondlinere.exactMatch( line2 ) )
    KIG_FILTER_PARSE_ERROR;

  tmp = secondlinere.cap( 1 );
  myobj.color = translatecolor( tmp );
//  if ( ! color.isValid() ) KIG_FILTER_PARSE_ERROR;

  tmp = secondlinere.cap( 2 );
  myobj.fillColor = translatecolor( tmp );
//  if ( ! fillcolor.isValid() ) KIG_FILTER_PARSE_ERROR;

  tmp = secondlinere.cap( 3 );
  myobj.thick = tmp == "t" ? 1 : tmp == "tT" ? 2 : 3;

  tmp = secondlinere.cap( 4 );
  myobj.lineSegLength = tmp.toInt( &ok );
  if ( ! ok ) KIG_FILTER_PARSE_ERROR;

  tmp = secondlinere.cap( 5 );
  myobj.lineSegSplit = tmp.toInt( &ok );
  if ( ! ok ) KIG_FILTER_PARSE_ERROR;

  tmp = secondlinere.cap( 6 );
  myobj.specialAppearanceSwitch = tmp.toInt( &ok );
  if ( ! ok ) KIG_FILTER_PARSE_ERROR;

  tmp = secondlinere.cap( 7 );
  myobj.visible = tmp == "V";

  tmp = secondlinere.cap( 8 );
  myobj.fixed = tmp == "St";

  QRegExp thirdlinere( "^(Const: ([^,]*),? ?)?(Val: (.*))?$" );
  if ( ! thirdlinere.exactMatch( line3 ) )
    KIG_FILTER_PARSE_ERROR;

  tmp = thirdlinere.cap( 2 );
  QStringList parentsids = QStringList::split( ' ', tmp );
  for ( QStringList::iterator i = parentsids.begin();
        i != parentsids.end(); ++i )
  {
    myobj.parents.push_back( ( *i ).toInt( &ok ) );
    if ( ! ok ) KIG_FILTER_PARSE_ERROR;
  }
  if ( myobj.parents.size() != myobj.numberOfParents )
    KIG_FILTER_PARSE_ERROR;

  tmp = thirdlinere.cap( 4 );
  QStringList valIds = QStringList::split( ' ', tmp );
  for ( QStringList::iterator i = valIds.begin();
        i != valIds.end(); ++i )
  {
    myobj.data.push_back( ( *i ).toDouble( &ok ) );
    if ( ! ok ) KIG_FILTER_PARSE_ERROR;
  }
//     kdDebug()
//       << k_funcinfo << endl
//       << "id = " << myobj.id << endl
//       << "type = " << myobj.type << endl
//       << "numberOfParents = " << myobj.numberOfParents << endl
//       << "color = " << myobj.color.name() << endl
//       << "fillcolor = " << myobj.fillColor.name() << endl
//       << "thick = " << myobj.thick << endl
//       << "lineseglength = " << myobj.lineSegLength << endl
//       << "linesegsplit = " << myobj.lineSegSplit << endl
//       << "specialAppearanceSwitch = " << myobj.specialAppearanceSwitch << endl
//       << "visible = " << visible << endl
//       << "fixed = " << myobj.fixed << endl
//       << "parents =" << endl;
//     for ( std::vector<int>::iterator i = myobj.parents.begin(); i != myobj.parents.end(); ++i )
//       kdDebug() << "	" << *i << endl;
//     kdDebug() << "vals = " << endl;
//     for ( std::vector<double>::iterator i = myobj.data.begin(); i != myobj.data.end(); ++i )
//       kdDebug() << "	" << *i << endl;

  return true;
}

KigDocument* KigFilterCabri::load( const QString& file )
{
  QFile f( file );
  if ( ! f.open( IO_ReadOnly ) )
  {
    fileNotFound( file );
    return 0;
  }

  KigDocument* ret = new KigDocument();

  QString s = readLine( f );
  QString a = s.left( 21 );
  QString b = s.mid( 21 );
  if( a != "FIGURE CabriII vers. " ||
      ( b != "DOS 1.0" && b != "MS-Windows 1.0" ) )
  {
    if ( s.left( 5 ) == "#FIG " )
    {
      notSupported( file, i18n( "This is a XFig file, not a Cabri figure." ) );
      return 0;
    }
    else
      KIG_FILTER_PARSE_ERROR;
  }

  // next we have:
  // line 2: empty line
  // line 3: window dimensions -> we don't need/use that...
  // line 4: empty line
  // line 5 through 8: center point
  // line 9 through 12: axes
  // so we skip 11 lines...
  for( int i = 0; i != 11; ++i)
    s = readLine( f );

  // all Cabri files seem to at least have these center and axes...
  if ( f.atEnd() )
    KIG_FILTER_PARSE_ERROR;

  std::vector<ObjectHolder*> holders;
  std::vector<ObjectCalcer*> calcers;

  const ObjectFactory* fact = ObjectFactory::instance();

  std::vector<ObjectCalcer*> args;
  ObjectCalcer* oc = 0;

  while ( ! f.atEnd() )
  {
    CabriObject obj;
    // we do one object each iteration..
    if ( !readObject( f, obj ) )
      return 0;

    args.clear();
    for ( std::vector<int>::iterator i = obj.parents.begin();
          i != obj.parents.end(); ++i )
      args.push_back( calcers[*i-3] );

    // two fake objects at the start ( origin and axes.. )
    if ( obj.id != calcers.size() + 3 ) KIG_FILTER_PARSE_ERROR;
    oc = 0;
    if ( obj.type == "Pt" )
    {
      // different sizes for points..
      obj.thick *= 2;
      if ( ! args.empty() ) KIG_FILTER_PARSE_ERROR;
      if ( obj.data.size() != 2 ) KIG_FILTER_PARSE_ERROR;
      oc = fact->fixedPointCalcer( Coordinate( obj.data[0], obj.data[1] ) );
    }
    else if ( obj.type == "Cir" )
    {
      if ( args.size() == 1 )
      {
        if ( obj.data.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        ObjectConstCalcer* radc =
          new ObjectConstCalcer( new DoubleImp( obj.data[0] ) );
        args.push_back( radc );
        oc = new ObjectTypeCalcer( CircleBPRType::instance(), args );
      }
      else if ( args.size() == 2 )
      {
        if ( ! obj.data.empty() ) KIG_FILTER_PARSE_ERROR;
        oc = new ObjectTypeCalcer( CircleBCPType::instance(), args );
      }
      else KIG_FILTER_PARSE_ERROR;
    }
    else if ( obj.type == "Line" || obj.type == "Ray" || obj.type == "Seg" ||
              obj.type == "Vec" )
    {
      if ( args.size() == 1 )
      {
        if ( obj.data.size() != 2 ) KIG_FILTER_PARSE_ERROR;
        Coordinate vect( obj.data[0], obj.data[1] );
        ObjectConstCalcer* vectorcalcer =
          new ObjectConstCalcer( new VectorImp( Coordinate( 0, 0 ), vect ) );
        args.push_back( vectorcalcer );
        ObjectTypeCalcer* secondpoint =
          new ObjectTypeCalcer( TranslatedType::instance(), args );
        secondpoint->calc( *ret );
        args[1] = secondpoint;
      }
      if ( args.size() != 2 ) KIG_FILTER_PARSE_ERROR;
      const ObjectType* t = 0;
      if ( obj.type == "Line" ) t = LineABType::instance();
      else if ( obj.type == "Ray" ) t = RayABType::instance();
      else if ( obj.type == "Seg" ) t = SegmentABType::instance();
      else if ( obj.type == "Vec" ) t = VectorType::instance();
      else assert( t );
      oc = new ObjectTypeCalcer( t, args );
    }
    else if ( obj.type == "Pt/" )
    {
      // different sizes for points..
      obj.thick *= 2;
      if ( args.size() != 1 || obj.data.size() != 2 )
        KIG_FILTER_PARSE_ERROR;
      ObjectCalcer* parent = args[0];
      if ( !parent->imp()->inherits( CurveImp::stype() ) )
        KIG_FILTER_PARSE_ERROR;
      const CurveImp* curve = static_cast<const CurveImp*>( parent->imp() );
      Coordinate pt = Coordinate( obj.data[0], obj.data[1] );
      double param = curve->getParam( pt, *ret );
      args.push_back( new ObjectConstCalcer( new DoubleImp( param ) ) );
      oc = new ObjectTypeCalcer( ConstrainedPointType::instance(), args );
    }
    else if ( obj.type == "Perp" || obj.type == "Par" )
    {
      if ( args.size() != 2 || obj.data.size() != 0 )
        KIG_FILTER_PARSE_ERROR;
      const ObjectType* t = 0;
      if ( obj.type == "Perp" ) t = LinePerpendLPType::instance();
      else if ( obj.type == "Par" ) t = LineParallelLPType::instance();
      else assert( false );
      oc = new ObjectTypeCalcer( t, args );
    }
    else if ( obj.type == "Arc" )
    {
      if ( args.size() != 3 || ! obj.data.empty() )
        KIG_FILTER_PARSE_ERROR;
      oc = new ObjectTypeCalcer( ArcBTPType::instance(), args );
    }
    else if ( obj.type == "Con" )
    {
      if ( args.size() != 5 || !obj.data.empty() )
        KIG_FILTER_PARSE_ERROR;
      oc = new ObjectTypeCalcer( ConicB5PType::instance(), args );
    }
    else if ( obj.type == "Mid" )
    {
      if ( args.size() == 2 )
      {
        ObjectCalcer* c =
          new ObjectTypeCalcer( SegmentABType::instance(), args );
        c->calc( *ret );
        args.clear();
        args.push_back( c );
      }
      // midpoint -> this can be the midpoint of a segment, two
      // points, or a vector...
      if ( args.size() != 1 || !obj.data.empty() )
        KIG_FILTER_PARSE_ERROR;
      ObjectCalcer* parent = args[0];
      if ( parent->imp()->inherits( SegmentImp::stype() ) )
        oc = fact->propertyObjectCalcer( parent, "mid-point" ) ;
      else if ( parent->imp()->inherits( VectorImp::stype() ) )
        oc = fact->propertyObjectCalcer( parent, "vect-mid-point" );
      else KIG_FILTER_PARSE_ERROR;
    }
    else if ( obj.type == "PBiss" )
    {
      if ( args.size() == 2 )
      {
        ObjectCalcer* c =
          new ObjectTypeCalcer( SegmentABType::instance(), args );
        c->calc( *ret );
        args.clear();
        args.push_back( c );
      }
      if ( args.size() != 1 || !obj.data.empty() )
        KIG_FILTER_PARSE_ERROR;
      ObjectCalcer* parent = args[0];
      ObjectCalcer* midpoint = 0;
      if ( parent->imp()->inherits( SegmentImp::stype() ) )
        midpoint = fact->propertyObjectCalcer( parent, "mid-point" ) ;
//       else if ( parent->imp()->inherits( VectorImp::stype() ) )
//         midpoint = fact->propertyObjectCalcer( parent, "vect-mid-point" );
      else KIG_FILTER_PARSE_ERROR;
      midpoint->calc( *ret );
      args.push_back( midpoint );
      oc = new ObjectTypeCalcer( LinePerpendLPType::instance(), args );
    }
    else
    {
      notSupported( file, i18n( "This Cabri file contains a \"%1\" object, "
                                "which Kig does not currently support." ).arg( obj.type ) );
      return 0;
    }

    if ( oc == 0 ) KIG_FILTER_PARSE_ERROR;

    oc->calc( *ret );
    calcers.push_back( oc );
    ObjectDrawer* d = new ObjectDrawer( obj.color, obj.thick, obj.visible );
    ObjectHolder* oh = new ObjectHolder( oc, d );
    holders.push_back( oh );

    oc = 0;
  }

  ret->addObjects( holders );
  ret->setGrid( false );
  ret->setAxes( false );
  return ret;
}

KigFilterCabri* KigFilterCabri::instance()
{
  static KigFilterCabri t;
  return &t;
}
