// SPDX-FileCopyrightText: 2003 Dominique Devriese <devriese@kde.org>

// SPDX-License-Identifier: GPL-2.0-or-later

#include "kseg-filter.h"

#include "filters-common.h"
#include "kseg-defs.h"

#include "../kig/kig_document.h"
#include "../kig/kig_part.h"
#include "../misc/coordinate.h"
#include "../objects/angle_type.h"
#include "../objects/arc_type.h"
#include "../objects/bogus_imp.h"
#include "../objects/circle_type.h"
#include "../objects/conic_imp.h"
#include "../objects/conic_types.h"
#include "../objects/intersection_types.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_imp.h"
#include "../objects/point_type.h"
#include "../objects/polygon_imp.h"
#include "../objects/polygon_type.h"
#include "../objects/transform_types.h"
#include "../objects/vector_type.h"

#include <QFont>
#include <QPen>
#include <QBrush>
#include <QFile>
#include <QDataStream>
#include <QBuffer>

KigFilterKSeg::KigFilterKSeg()
{
}

KigFilterKSeg::~KigFilterKSeg()
{
}

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

struct drawstyle
{
  qint8 pointstyle;
  QFont font;
  QPen pen;
  QBrush brush;
};

static Coordinate readKSegCoordinate( QDataStream& stream )
{
  // read the coord..
  float inx, iny;
  stream >> inx >> iny;
  // KSeg uses a coordinate system, where the topleft is (0,0), and
  // the bottom right is the widget coordinate in the window: if the
  // KSeg window had a width of 600 pixels and a height of 600, then
  // the bottom right will be at (600,600).  We assume a window of
  // such a height here, and transform it into Kig Coordinates.  This
  // function is quite similar to ScreenInfo::fromScreen, and it's
  // basically a simple modification of that code to floats..

  // invert the y-axis: 0 is at the bottom !
  Coordinate t( inx, 600 - iny );
  t *= 14;
  t /= 600;
  return t + Coordinate( -7, -7 );
}

static ObjectTypeCalcer* intersectionPoint( const std::vector<ObjectCalcer*>& parents, int which )
{
  if ( parents.size() != 2 ) return nullptr;
  int nlines = 0;
  int nconics = 0;
  int narcs = 0;
  for ( int i = 0; i < 2; ++i )
  {
    if ( parents[i]->imp()->inherits( AbstractLineImp::stype() ) ) ++nlines;
    else if ( parents[i]->imp()->inherits( ConicImp::stype() ) ) ++nconics;
    else if ( parents[i]->imp()->inherits( ArcImp::stype() ) ) ++narcs;
    else return nullptr;
  };
  if ( nlines == 2 )
    return which == -1 ? new ObjectTypeCalcer( LineLineIntersectionType::instance(), parents ) : nullptr;
  else if ( nlines == 1 && nconics == 1 )
  {
    std::vector<ObjectCalcer*> intparents( parents );
    intparents.push_back( new ObjectConstCalcer( new IntImp( which ) ) );
    return new ObjectTypeCalcer( ConicLineIntersectionType::instance(), intparents );
  }
  else if ( nlines == 0 && nconics == 2 )
  {
    std::vector<ObjectCalcer*> rparents( parents );
    rparents.push_back( new ObjectConstCalcer( new IntImp( 1 ) ) );
    rparents.push_back( new ObjectConstCalcer( new IntImp( 1 ) ) );
    rparents.push_back( new ObjectTypeCalcer( ConicRadicalType::instance(), rparents ) );
    std::vector<ObjectCalcer*> iparents;
    iparents.push_back( parents[0] );
    iparents.push_back( rparents.back() );
    iparents.push_back( new ObjectConstCalcer( new IntImp( which ) ) );
    return new ObjectTypeCalcer( ConicLineIntersectionType::instance(), iparents );
  }
  else if ( nlines == 1 && narcs == 1 )
  {
    std::vector<ObjectCalcer*> intparents( parents );
    intparents.push_back( new ObjectConstCalcer( new IntImp( which ) ) );
    return new ObjectTypeCalcer( ArcLineIntersectionType::instance(), intparents );
  }
  else return nullptr;
}

ObjectCalcer* KigFilterKSeg::transformObject( KigDocument& kigdoc,
                                              std::vector<ObjectCalcer*>& parents,
                                              int subtype, bool& ok )
{
  ok = true;
  ObjectCalcer* retobj = nullptr;
  switch( subtype )
  {
    case G_TRANSLATED:
    {
      std::vector<ObjectCalcer*> vectorparents( parents.begin() + 1, parents.end() );
      ObjectTypeCalcer* vector = new ObjectTypeCalcer( VectorType::instance(), vectorparents );
      vector->calc( kigdoc );

      std::vector<ObjectCalcer*> transparents;
      transparents.push_back( parents[0] );
      transparents.push_back( vector );
      retobj = new ObjectTypeCalcer( TranslatedType::instance(), transparents );
      break;
    }
    case G_ROTATED:
    {
      std::vector<ObjectCalcer*> angleparents( parents.begin() + 2, parents.end() );
      ObjectTypeCalcer* angle = new ObjectTypeCalcer( AngleType::instance(), angleparents );
      angle->calc( kigdoc );

      std::vector<ObjectCalcer*> rotparents;
      rotparents.push_back( parents[0] );
      rotparents.push_back( parents[1] );
      rotparents.push_back( angle );
      retobj = new ObjectTypeCalcer( RotationType::instance(), rotparents );
      break;
    }
    case G_SCALED:
    {
      if ( parents.size() == 4 )
      {
        retobj = new ObjectTypeCalcer( ScalingOverCenter2Type::instance(), parents );
      }
      else
      {
        // TODO
        notSupported( i18n( "This KSeg document uses a scaling "
                            "transformation, which Kig currently "
                            "cannot import." ) );
        ok = false;
        return nullptr;
      }
      break;
    }
    case G_REFLECTED:
    {
      std::vector<ObjectCalcer*> mirparents( parents.begin(), parents.end() );
      retobj = new ObjectTypeCalcer( LineReflectionType::instance(), mirparents );
      break;
    }
  }

  return retobj;
}

KigDocument* KigFilterKSeg::load( const QString& file )
{
  QFile ffile( file );
  if ( ! ffile.open( QIODevice::ReadOnly ) )
  {
    fileNotFound( file );
    return nullptr;
  };

  KigDocument* retdoc = new KigDocument();

  QDataStream fstream( &ffile );

  QString versionstring;
  fstream >> versionstring;
  if ( !versionstring.startsWith( QLatin1String( "KSeg Document Version " ) ) )
    KIG_FILTER_PARSE_ERROR;

  QByteArray array;
  fstream >> array;
  QBuffer buf( &array );
  buf.open( QIODevice::ReadOnly );
  QDataStream stream( &buf );

  stream.setVersion( 3 );

  // G_drawstyles:
  short numstyles;
  stream >> numstyles;
  std::vector<drawstyle> drawstyles( numstyles );
  for ( short i = 0; i < numstyles; ++i )
  {
    stream >> drawstyles[i].pointstyle;
    stream >> drawstyles[i].font;
    stream >> drawstyles[i].pen;
    stream >> drawstyles[i].brush;
  };

  std::vector<ObjectHolder*> ret;
  std::vector<ObjectHolder*> ret2;

  // G_refs
  unsigned int count;
  stream >> count;

  ret.resize( count, nullptr );
  const ObjectFactory* fact = ObjectFactory::instance();

  // KSeg topologically sorts the objects before saving, that means we
  // can read the entire file in one iteration..
  for ( uint i = 0; i < count; ++i )
  {
    short styleid;
    stream >> styleid;
    short nparents;
    stream >> nparents;
    std::vector<ObjectCalcer*> parents( nparents, nullptr );
    for ( short j = 0; j < nparents; ++j )
    {
      int parent;
      stream >> parent;
      parents[j] = ret[parent]->calcer();
    };

    // read the object..
    short info;
    stream >> info;
    int type = 1 << (info & 31);
    info >>= 5;
    int descendtype = (info & 15);
    info >>= 4;
    bool visible = info & 1;
    bool labelVisible = info & 2;
    bool given = info & 4;
    bool final = info & 8;

    // avoid g++ warnings about unused vars..
    // this doesn't really do anything..
    (void) given;
    (void) final;

    drawstyle style = drawstyles[styleid];

    if ( type == G_LOOP ) continue;
    // read the label..
    QString labeltext;
    stream >> labeltext;
    Coordinate relcoord = readKSegCoordinate( stream );
    // shut up gcc
    (void) relcoord;
    if ( type & G_CURVE )
    {
      Coordinate relcurvecoord = readKSegCoordinate( stream );
      // shut up gcc
      (void) relcurvecoord;
    };

    // now load the object data..
    ObjectHolder* object = nullptr;
    ObjectCalcer* o = nullptr;
    bool ok = true;

    QColor color = style.pen.color();
    int width = style.pen.width();

/*
    qDebug() << "type: " << type
              << "descendtype: " << descendtype << endl
              << "label: " << labeltext << endl;
//*/

    switch ( type )
    {
      case G_POINT:
      {
        switch( descendtype )
        {
        case G_TRANSLATED:
        case G_ROTATED:
        case G_SCALED:
        case G_REFLECTED:
        {
          o = transformObject(  *retdoc, parents, descendtype, ok );
          break;
        }
        case G_FREE_POINT:
        {
          // fixed point
          if ( nparents != 0 ) KIG_FILTER_PARSE_ERROR;
          Coordinate c = readKSegCoordinate( stream );
          o = fact->fixedPointCalcer( c );
          break;
        }
        case G_CONSTRAINED_POINT:
        {
          // constrained point
          double p;
          stream >> p;
          if ( nparents != 1 ) KIG_FILTER_PARSE_ERROR;
          ObjectCalcer* parent = parents[0];
          assert( parent );
          o = fact->constrainedPointCalcer( parent, p );
          break;
        }
        case G_INTERSECTION_POINT:
        {
          // KSeg has somewhat weird intersection objects..
          // for all objects G_INTERSECTION_POINT gets the
          // first intersection of its parents, G_INTERSECTION2_POINT
          // represents the second, if present.
          o = intersectionPoint( parents, -1 );
          if ( ! o ) KIG_FILTER_PARSE_ERROR;
          break;
        }
        case G_INTERSECTION2_POINT:
        {
          o = intersectionPoint( parents, 1 );
          if ( ! o ) KIG_FILTER_PARSE_ERROR;
          break;
        }
        case G_MID_POINT:
        {
          // midpoint of a segment..
          if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
          if ( !parents[0]->imp()->inherits( SegmentImp::stype() ) )
            KIG_FILTER_PARSE_ERROR;
//          int index = parents[0]->imp()->propertiesInternalNames().indexOf( "mid-point" );
//          assert( index != -1 );
          o = new ObjectPropertyCalcer( parents[0], "mid-point" );
          break;
        }
        default:
          KIG_FILTER_PARSE_ERROR;
        };
        width = style.pointstyle == SMALL_CIRCLE ? 2 : style.pointstyle == MEDIUM_CIRCLE ? 3 : 5;
        color = style.brush.color();
        break;
      };
      case G_SEGMENT:
      {
        switch( descendtype )
        {
        case G_TRANSLATED:
        case G_ROTATED:
        case G_SCALED:
        case G_REFLECTED:
        {
          o = transformObject(  *retdoc, parents, descendtype, ok );
          break;
        }
        case G_ENDPOINTS_SEGMENT:
        {
          if ( nparents != 2 ) KIG_FILTER_PARSE_ERROR;
          o = new ObjectTypeCalcer( SegmentABType::instance(), parents );
          break;
        }
        default:
          KIG_FILTER_PARSE_ERROR;
        }
        break;
      };
      case G_RAY:
      {
        switch( descendtype )
        {
        case G_TRANSLATED:
        case G_ROTATED:
        case G_SCALED:
        case G_REFLECTED:
        {
          o = transformObject(  *retdoc, parents, descendtype, ok );
          break;
        }
        case G_TWOPOINTS_RAY:
        {
          if ( nparents != 2 ) KIG_FILTER_PARSE_ERROR;
          o = new ObjectTypeCalcer( RayABType::instance(), parents );
          break;
        }
        case G_BISECTOR_RAY:
        {
          ObjectTypeCalcer* angle = new ObjectTypeCalcer( HalfAngleType::instance(), parents );
          angle->calc( *retdoc );
          o = fact->propertyObjectCalcer( angle, "angle-bisector" );
          break;
        }
        default:
          KIG_FILTER_PARSE_ERROR;
        };
        break;
      };
      case G_LINE:
      {
        switch( descendtype )
        {
        case G_TRANSLATED:
        case G_ROTATED:
        case G_SCALED:
        case G_REFLECTED:
        {
          o = transformObject(  *retdoc, parents, descendtype, ok );
          break;
        }
        case G_TWOPOINTS_LINE:
        {
          if ( nparents != 2 ) KIG_FILTER_PARSE_ERROR;
          o = new ObjectTypeCalcer( LineABType::instance(), parents );
          break;
        }
        case G_PARALLEL_LINE:
        {
          if ( nparents != 2 ) KIG_FILTER_PARSE_ERROR;
          o = new ObjectTypeCalcer( LineParallelLPType::instance(), parents );
          break;
        }
        case G_PERPENDICULAR_LINE:
        {
          if ( nparents != 2 ) KIG_FILTER_PARSE_ERROR;
          o = new ObjectTypeCalcer( LinePerpendLPType::instance(), parents );
          break;
        }
        default:
          KIG_FILTER_PARSE_ERROR;
        };
        break;
      };
      case G_CIRCLE:
      {
        switch( descendtype )
        {
        case G_TRANSLATED:
        case G_ROTATED:
        case G_SCALED:
        case G_REFLECTED:
        {
          o = transformObject(  *retdoc, parents, descendtype, ok );
          break;
        }
        case G_CENTERPOINT_CIRCLE:
        {
          if ( nparents != 2 ) KIG_FILTER_PARSE_ERROR;
          o = new ObjectTypeCalcer( CircleBCPType::instance(), parents );
          break;
        }
        case G_CENTERRADIUS_CIRCLE:
        {
          ObjectCalcer* point;
          ObjectCalcer* segment;
          if ( parents[0]->imp()->inherits( PointImp::stype() ) )
          {
            point = parents[0];
            segment = parents[1];
          }
          else
          {
            point = parents[1];
            segment = parents[0];
          };
          int index = segment->imp()->propertiesInternalNames().indexOf( "length" );
          if ( index == -1 ) KIG_FILTER_PARSE_ERROR;
          ObjectPropertyCalcer* length = new ObjectPropertyCalcer( segment, "length" );
          length->calc( *retdoc );
          std::vector<ObjectCalcer*> cparents;
          cparents.push_back( point );
          cparents.push_back( length );
          o = new ObjectTypeCalcer( CircleBPRType::instance(), cparents );
          break;
        }
        default:
          KIG_FILTER_PARSE_ERROR;
        };
        break;
      };
      case G_ARC:
      {
        switch( descendtype )
        {
        case G_TRANSLATED:
        case G_ROTATED:
        case G_SCALED:
        case G_REFLECTED:
        {
          o = transformObject(  *retdoc, parents, descendtype, ok );
          break;
        }
        case G_THREEPOINTS_ARC:
        {
          if ( nparents != 3 ) KIG_FILTER_PARSE_ERROR;
          o = new ObjectTypeCalcer( ArcBTPType::instance(), parents );
          break;
        }
        default:
          KIG_FILTER_PARSE_ERROR;
        }
        break;
      };
      case G_POLYGON:
      {
        switch( descendtype )
        {
        case G_TRANSLATED:
        case G_ROTATED:
        case G_SCALED:
        case G_REFLECTED:
        {
          o = transformObject(  *retdoc, parents, descendtype, ok );
          break;
        }
        default:
        {
          if ( nparents < 3 ) KIG_FILTER_PARSE_ERROR;
          o = new ObjectTypeCalcer( PolygonBNPType::instance(), parents );
        }
        }
//        default:
//          KIG_FILTER_PARSE_ERROR;
        break;
      };
      case G_CIRCLEINTERIOR:
      {
        notSupported( i18n( "This KSeg file contains a filled circle, "
                            "which Kig does not currently support." ) );
        return nullptr;
      };
      case G_ARCSECTOR:
      {
        notSupported( i18n( "This KSeg file contains an arc sector, "
                            "which Kig does not currently support." ) );
        return nullptr;
      };
      case G_ARCSEGMENT:
      {
        notSupported( i18n( "This KSeg file contains an arc segment, "
                            "which Kig does not currently support." ) );
        return nullptr;
      };
      case G_LOCUS:
      {
        switch( descendtype )
        {
        case G_TRANSLATED:
        case G_ROTATED:
        case G_SCALED:
        case G_REFLECTED:
        {
          o = transformObject(  *retdoc, parents, descendtype, ok );
          break;
        }
        case G_OBJECT_LOCUS:
        {
          if ( nparents != 2 ) KIG_FILTER_PARSE_ERROR;
          o = fact->locusCalcer( parents[0], parents[1] );
          break;
        }
        default:
          KIG_FILTER_PARSE_ERROR;
        }
        break;
      };
      case G_MEASURE:
      {
        QByteArray prop;
        Coordinate txtcoords = readKSegCoordinate( stream );
        switch( descendtype )
        {
        case G_DISTANCE_MEASURE:
        {
          if ( nparents != 2 ) KIG_FILTER_PARSE_ERROR;
          ObjectCalcer* c =
            new ObjectTypeCalcer( SegmentABType::instance(), parents );
          c->calc( *retdoc );
          parents.clear();
          parents.push_back( c );
          prop = "length";
          break;
        }
        case G_LENGTH_MEASURE:
        {
          if ( nparents != 1 ) KIG_FILTER_PARSE_ERROR;
          if ( parents[0]->imp()->inherits( SegmentImp::stype() ) )
            prop = "length";
          else if ( parents[0]->imp()->inherits( CircleImp::stype() ) )
            prop = "circumference";
          else KIG_FILTER_PARSE_ERROR;
          break;
        }
        case G_RADIUS_MEASURE:
        {
          if ( nparents != 1 ) KIG_FILTER_PARSE_ERROR;
          if ( !parents[0]->imp()->inherits( CircleImp::stype() ) )
            KIG_FILTER_PARSE_ERROR;
          prop = "radius";
          break;
        }
        case G_ANGLE_MEASURE:
        {
          if ( nparents != 3 ) KIG_FILTER_PARSE_ERROR;
          ObjectTypeCalcer* ao =
            new ObjectTypeCalcer( AngleType::instance(), parents );
          ao->calc( *retdoc );
          parents.clear();
          parents.push_back( ao );
          prop = "angle-degrees";
          break;
        }
//        case G_RATIO_MEASURE: // TODO
        case G_SLOPE_MEASURE:
        {
          if ( nparents != 1 ) KIG_FILTER_PARSE_ERROR;
          if ( !parents[0]->imp()->inherits( AbstractLineImp::stype() ) )
            KIG_FILTER_PARSE_ERROR;
          prop = "slope";
          break;
        }
        case G_AREA_MEASURE:
        {
          if ( nparents != 1 ) KIG_FILTER_PARSE_ERROR;
          if ( parents[0]->imp()->inherits( FilledPolygonImp::stype() )
               || parents[0]->imp()->inherits( FilledPolygonImp::stype3() )
               || parents[0]->imp()->inherits( FilledPolygonImp::stype4() ) )
            prop = "polygon-surface";
          else KIG_FILTER_PARSE_ERROR;
          break;
        }
        default:
          KIG_FILTER_PARSE_ERROR;
        }
        if ( parents.size() != 1 ) KIG_FILTER_PARSE_ERROR;
        o = filtersConstructTextObject(
                txtcoords, parents[0], prop, *retdoc, false );
        break;
      }
      case G_CALCULATE:
        KIG_FILTER_PARSE_ERROR;
      case G_ANNOTATION:
        KIG_FILTER_PARSE_ERROR;
      case G_LOOP:
        KIG_FILTER_PARSE_ERROR;
      default:
        KIG_FILTER_PARSE_ERROR;

    }

    // checking if the object was correctly created
    if ( ! o )
    {
      if ( ok )
        KIG_FILTER_PARSE_ERROR
      else
        return nullptr;
    }

    ObjectDrawer* d = new ObjectDrawer( color, width, visible, style.pen.style() );
    if ( !labeltext.isEmpty() )
    {
      ObjectConstCalcer* name = new ObjectConstCalcer( new StringImp( labeltext ) );
      object = new ObjectHolder( o, d, name );
    }
    else
    {
      object = new ObjectHolder( o, d );
    }

    assert( object );
    ret[i] = object;
    object->calc( *retdoc );
    if ( !labeltext.isEmpty() && labelVisible && object->imp()->inherits( PointImp::stype() ) )
    {
      std::vector<ObjectCalcer*> args2;
      args2.push_back( object->nameCalcer() );
      ObjectCalcer* oc2 = fact->attachedLabelCalcer(
               QStringLiteral( "%1" ), object->calcer(),
               static_cast<const PointImp*>( object->imp() )->coordinate(),
               false, args2, *retdoc );
      oc2->calc( *retdoc );
      ObjectDrawer* d2 = new ObjectDrawer( style.pen.color() );
      ObjectHolder* o2 = new ObjectHolder( oc2, d2 );
      ret2.push_back( o2 );
    }
  };

  // selection groups ( we ignore them, but we pretend to read them
  // out anyway, so we can find what comes after them.. )
  int selgroupcount;
  stream >> selgroupcount;
  for ( int i = 0; i < selgroupcount; ++i )
  {
    QString name;
    stream >> name;
    int size;
    stream >> size;
    for ( int i = 0; i < size; ++i )
    {
      short object;
      stream >> object;
      (void) object;
    };
  };

  // no more data in the file.
  retdoc->addObjects( ret );
  retdoc->addObjects( ret2 );
  retdoc->setAxes( false );
  retdoc->setGrid( false );
  return retdoc;
}

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