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

#include "kgeo-filter.h"

#include "kgeo-resource.h"
#include "filters-common.h"

#include "../kig/kig_part.h"
#include "../kig/kig_document.h"
#include "../objects/angle_type.h"
#include "../objects/bogus_imp.h"
#include "../objects/circle_imp.h"
#include "../objects/circle_type.h"
#include "../objects/intersection_types.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_type.h"
#include "../objects/point_imp.h"
#include "../objects/point_type.h"
#include "../objects/text_type.h"
#include "../objects/transform_types.h"
#include "../objects/vector_type.h"

#include <KConfig>
#include <KConfigGroup>

#include <algorithm>
#include <functional>

bool KigFilterKGeo::supportMime( const QString& mime )
{
  return mime == QLatin1String("application/x-kgeo");
}

KigDocument* KigFilterKGeo::load( const QString& sFrom )
{
  // kgeo uses a KConfig to save its contents...
  KConfig config ( sFrom, KConfig::SimpleConfig );

  loadMetrics ( &config );
  return loadObjects ( &config );
}

void KigFilterKGeo::loadMetrics(KConfig* c )
{
  KConfigGroup grp = c->group("Main");
  xMax = grp.readEntry("XMax", 16);
  yMax = grp.readEntry("YMax", 11);
  grid = grp.readEntry( "Grid", true );
  axes = grp.readEntry( "Axes", true );
  // the rest is not relevant to us (yet ?)...
}

struct KGeoHierarchyElement
{
  int id;
  std::vector<int> parents;
};

static void visitElem( std::vector<KGeoHierarchyElement>& ret,
                       const std::vector<KGeoHierarchyElement>& elems,
                       std::vector<bool>& seen,
                       int i )
{
  if ( !seen[i] )
  {
    for ( uint j = 0; j < elems[i].parents.size(); ++j )
      visitElem( ret, elems, seen, elems[i].parents[j] );
    ret.push_back( elems[i] );
    seen[i] = true;
  };
}

static std::vector<KGeoHierarchyElement> sortElems( const std::vector<KGeoHierarchyElement> &elems )
{
  std::vector<KGeoHierarchyElement> ret;
  std::vector<bool> seenElems( elems.size(), false );
  for ( uint i = 0; i < elems.size(); ++i )
    visitElem( ret, elems, seenElems, i );
  return ret;
}

KigDocument* KigFilterKGeo::loadObjects( KConfig* c )
{
  KigDocument* ret = new KigDocument();

  using namespace std;

  QString group;
  bool ok = true;
  KConfigGroup grp = c->group("Main");
  int number = grp.readEntry ("Number",0);

  // first we determine the parent relationships, and sort the
  // elements in an order that we can be sure all of an object's
  // parents will have been handled before it is handled itself..
  // ( aka topological sort of the parent relations graph..
  std::vector<KGeoHierarchyElement> elems;
  elems.reserve( number );

  for ( int i = 0; i < number; ++i )
  {
    KGeoHierarchyElement elem;
    elem.id = i;
    group.setNum( i + 1 );
    group.prepend( "Object " );

    KConfigGroup grp = c->group(group);
    QStringList parents = grp.readEntry( "Parents", QVariant( QVariant::StringList ) ).toStringList();
    elems.push_back( elem );
    for ( QStringList::ConstIterator parent = parents.constBegin(); parent != parents.constEnd(); ++parent )
    {
      int parentIndex = ( *parent ).toInt( &ok );
      if ( ! ok ) KIG_FILTER_PARSE_ERROR;
      if ( parentIndex != 0 )
        elems[i].parents.push_back( parentIndex - 1 );
    };
  };

  std::vector<KGeoHierarchyElement> sortedElems = sortElems( elems );
  std::vector<ObjectHolder*> os;
  os.resize( number, 0 );
  const ObjectFactory* factory = ObjectFactory::instance();

  // now we iterate over the elems again in the newly determined
  // order..
  for ( uint i = 0; i < sortedElems.size(); ++i )
  {
    const KGeoHierarchyElement& e = sortedElems[i];
    int id = e.id;
    group.setNum( id + 1 );
    group.prepend( "Object " );
    KConfigGroup grp = c->group(group );
    int objID = grp.readEntry( "Geo",0 );

    std::vector<ObjectCalcer*> parents;
    for ( uint j = 0; j < e.parents.size(); ++j )
    {
      int parentid = e.parents[j];
      parents.push_back( os[parentid]->calcer() );
    };

    ObjectCalcer* o = 0;
    switch (objID)
    {
    case ID_point:
    {
      // fetch the coordinates...
      QString strX = grp.readEntry("QPointX");
      QString strY = grp.readEntry("QPointY");
      double x = strX.toDouble(&ok);
      if (!ok) KIG_FILTER_PARSE_ERROR;
      double y = strY.toDouble(&ok);
      if (!ok) KIG_FILTER_PARSE_ERROR;
      Coordinate m( x, y );
      uint nparents = parents.size();
      if ( nparents == 0 )
        o = factory->fixedPointCalcer( m );
      else if ( nparents == 1 )
        o = factory->constrainedPointCalcer( parents[0], m, *ret );
      else
        KIG_FILTER_PARSE_ERROR;
      break;
    }
    case ID_segment:
    {
      o = new ObjectTypeCalcer( SegmentABType::instance(), parents );
      break;
    }
    case ID_circle:
    {
      o = new ObjectTypeCalcer( CircleBCPType::instance(), parents );
      break;
    }
    case ID_line:
    {
      o = new ObjectTypeCalcer( LineABType::instance(), parents );
      break;
    }
    case ID_bisection:
    {
      // if this is the bisection of two points, first build a segment
      // between them..
      if ( parents.size() == 2 )
      {
        ObjectTypeCalcer* seg = new ObjectTypeCalcer( SegmentABType::instance(), parents );
        parents.clear();
        parents.push_back( seg );
      }
      if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
      o = factory->propertyObjectCalcer( parents[0], "mid-point" );
      break;
    };
    case ID_perpendicular:
    {
      o = new ObjectTypeCalcer( LinePerpendLPType::instance(), parents );
      break;
    }
    case ID_parallel:
    {
      o = new ObjectTypeCalcer( LineParallelLPType::instance(), parents );
      break;
    }
    case ID_vector:
    {
      o = new ObjectTypeCalcer( VectorType::instance(), parents );
      break;
    }
    case ID_ray:
    {
      o = new ObjectTypeCalcer( RayABType::instance(), parents );
      break;
    }
    case ID_move:
    {
      o = new ObjectTypeCalcer( TranslatedType::instance(), parents );
      break;
    }
    case ID_mirrorPoint:
    {
      o = new ObjectTypeCalcer( PointReflectionType::instance(), parents );
      break;
    }
    case ID_pointOfConc:
    {
      o = new ObjectTypeCalcer( LineLineIntersectionType::instance(), parents );
      break;
    }
    case ID_text:
    {
      bool frame = grp.readEntry( "Frame",true );
      double x = grp.readEntry( "TextRectCenterX",0.0 );
      double y = grp.readEntry( "TextRectCenterY",0.0 );
      QString text = grp.readEntry( "TextRectEntry" );
      double height = grp.readEntry( "TextRectHeight",0.0 );
      double width  = grp.readEntry( "TextRectWidth",0.0 );
      // we don't want the center, but the top left..
      x -= width / 80;
      y -= height / 80;
      o = factory->labelCalcer(
        text, Coordinate( x, y ), frame, std::vector<ObjectCalcer*>(), *ret );
      break;
    }
    case ID_fixedCircle:
    {
      double r = grp.readEntry( "Radius",0.0 );
      parents.push_back( new ObjectConstCalcer( new DoubleImp( r ) ) );
      o = new ObjectTypeCalcer( CircleBPRType::instance(), parents );
      break;
    }
    case ID_angle:
    {
      if ( parents.size() == 3 )
      {
        ObjectTypeCalcer* ao = new ObjectTypeCalcer( AngleType::instance(), parents );
        ao->calc( *ret );
        parents.clear();
        parents.push_back( ao );
      };
      if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
      ObjectCalcer* angle = parents[0];
      parents.clear();
      const Coordinate c =
        static_cast<const PointImp*>( angle->parents()[1]->imp() )->coordinate();
      o = filtersConstructTextObject( c, angle, "angle-degrees", *ret, true );
      break;
    }
    case ID_distance:
    {
      if ( parents.size() != 2 ) KIG_FILTER_PARSE_ERROR;
      ObjectTypeCalcer* segment = new ObjectTypeCalcer( SegmentABType::instance(), parents );
      segment->calc( *ret );
      Coordinate m = ( static_cast<const PointImp*>( parents[0]->imp() )->coordinate() +
                       static_cast<const PointImp*>( parents[1]->imp() )->coordinate() ) / 2;
      o = filtersConstructTextObject( m, segment, "length", *ret, true );
      break;
    }
    case ID_arc:
    {
      o = new ObjectTypeCalcer( AngleType::instance(), parents );
      break;
    }
    case ID_area:
    {
      if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
      const CircleImp* circle = static_cast<const CircleImp*>( parents[0]->imp() );
      const Coordinate c = circle->center() + Coordinate( circle->radius(), 0 );
      o = filtersConstructTextObject( c, parents[0], "surface", *ret, true );
      break;
    }
    case ID_slope:
    {
      // if parents contains a segment, line, vector or whatever, we
      // take its parents cause we want points..
      if ( parents.size() == 1 ) parents = parents[0]->parents();
      if ( parents.size() != 2 ) KIG_FILTER_PARSE_ERROR;
      const Coordinate c = (
        static_cast<const PointImp*>( parents[0]->imp() )->coordinate() +
        static_cast<const PointImp*>( parents[1]->imp() )->coordinate() ) / 2;
      ObjectTypeCalcer* line = new ObjectTypeCalcer( LineABType::instance(), parents );
      line->calc( *ret );
      o = filtersConstructTextObject( c, line, "slope", *ret, true );
      break;
    }
    case ID_circumference:
    {
      if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
      const CircleImp* c = static_cast<const CircleImp*>( parents[0]->imp() );
      const Coordinate m = c->center() + Coordinate( c->radius(), 0 );
      o = filtersConstructTextObject( m, parents[0], "circumference", *ret, true );
      break;
    }
    case ID_rotation:
    {
      // in kig, the rotated object should be last..
      ObjectCalcer* t = parents[2];
      parents[2] = parents[0];
      parents[0] = t;
      o = new ObjectTypeCalcer( RotationType::instance(), parents );
      break;
    }
    default:
      KIG_FILTER_PARSE_ERROR;
    };

    // set the color...
    QColor co = grp.readEntry( "Color",QColor() );
    if( !co.isValid() )
      co = Qt::blue;
    ObjectDrawer* d = new ObjectDrawer( co );

    os[i] = new ObjectHolder( o, d );
    os[i]->calc( *ret );
  }; // for loop (creating KGeoHierarchyElements..

  ret->addObjects( os );
  ret->setGrid( grid );
  ret->setAxes( axes );
  return ret;
}

KigFilterKGeo::KigFilterKGeo()
{
}

KigFilterKGeo::~KigFilterKGeo()
{
}

KigFilterKGeo* KigFilterKGeo::instance()
{
  static KigFilterKGeo f;
  return &f;
}
