/***************************************************************************
**
**  This file is part of SciFigs.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2003-10-16
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "CircleViewer.h"
#include "AxisWindow.h"
#include "GraphContentLayerFactory.h"
#include "XMLSciFigs.h"
#include "LayerPainterRequest.h"
#include "LayerLocker.h"

namespace SciFigs {

  /*!
    \class CircleViewer CircleViewer.h
    \brief A CircleViewer is a layer to plot colored ellipses.

  */

  const QString CircleViewer::xmlCircleViewerTag="CircleViewer";

  REGISTER_GRAPHCONTENTLAYER(CircleViewer, "CircleViewer")

  CircleViewer::CircleViewer(AxisWindow * parent) :
      GraphContentLayer(parent)
  {
    TRACE;
    _lineWeight=0.05;
  }

  void CircleViewer::setLineWeight (double lw)
  {
    LayerLocker ll(this);
    _lineWeight=lw;
  }

  /*!
    \a phi is the rotation angle of the main axis in radians (along \a a).
  */
  void CircleViewer::set(int index, double x, double y, double a, double b, double phi, QColor col)
  {
    TRACE;
    Item& item=_items[index];
    QGpCoreMath::Ellipse& ell=item._ellipse;
    LayerLocker ll(this);
    ell.setCenter(Point2D(x, y));
    ell.setMajorRadius(a);
    ell.setMinorRadius(b);
    ell.setOrientation(phi);
    item._color=col;
  }

  /*!
    \a phi is the rotation angle of the main axis in radians (along \a a).
  */
  void CircleViewer::add(double x, double y, double a, double b, double phi, QColor col)
  {
    TRACE;
    Item item;
    QGpCoreMath::Ellipse& ell=item._ellipse;
    ell.setCenter(Point2D(x, y));
    ell.setMajorRadius(a);
    ell.setMinorRadius(b);
    ell.setOrientation(phi);
    item._color=col;
    LayerLocker ll(this);
    _items.push_back(item);
  }

  /*!
    Add a circle from the text contained in \a Line

    3 x y rx               black circle
    4 x y rx ry            black ellipse
    5 x y rx ry rot        black rotated ellipse
    6 x y rx r g b         colored circle
    7 x y rx ry r g b      colored ellipse
    8 x y rx ry rot r g b  colored rotated ellipse
  */
  bool CircleViewer::add(const QString& line)
  {
    TRACE;
    LineParser parser(line);
    bool ok=true;
    switch(parser.count()) {
    case 0: // Ignore blank lines positively
      return true;
    case 3: {
        double r=parser.toDouble(2, ok);
        add(parser.toDouble(0, ok), parser.toDouble(1, ok), r, r, 0.0, Qt::black);
      }
      return ok;
    case 4:
      add(parser.toDouble(0, ok), parser.toDouble(1, ok),
          parser.toDouble(2, ok), parser.toDouble(3, ok), 0.0, Qt::black);
      return ok;
    case 5:
      add(parser.toDouble(0, ok), parser.toDouble(1, ok),
          parser.toDouble(2, ok), parser.toDouble(3, ok), parser.toDouble(4, ok)/180.0*M_PI,Qt::black);
      return ok;
    case 6: {
        double r=parser.toDouble(2, ok);
        QColor c;
        c.setRgb(parser.toInt(3, ok),
                 parser.toInt(4, ok),
                 parser.toInt(5, ok));
        add(parser.toDouble(0, ok), parser.toDouble(1, ok), r, r, 0.0, c);
      }
      return ok;
    case 7: {
        QColor c;
        c.setRgb(parser.toInt(4, ok),
                 parser.toInt(5, ok),
                 parser.toInt(6, ok));
        add(parser.toDouble(0, ok), parser.toDouble(1, ok),
            parser.toDouble(2, ok), parser.toDouble(3, ok), 0.0, c);
      }
      return ok;
    case 8: {
        QColor c;
        c.setRgb(parser.toInt(5, ok),
                 parser.toInt(6, ok),
                 parser.toInt(7, ok));
        add(parser.toDouble(0, ok), parser.toDouble(1, ok),
            parser.toDouble(2, ok), parser.toDouble(3, ok), parser.toDouble(4, ok)/180.0*M_PI, c);
      }
      return ok;
    default:
      App::log(tr("Error parsing circle (%1 already added): %2 columns encountered instead of 3, 4, 6 or 7.\n"
                  "\"%3\"\n").arg(count()).arg(parser.count()).arg(line));
      return false;
    }
  }

  void CircleViewer::insert(int index, double x, double y, double a, double b, double phi, QColor col)
  {
    TRACE;
    Item item;
    QGpCoreMath::Ellipse& ell=item._ellipse;
    ell.setCenter(Point2D(x, y));
    ell.setMajorRadius(a);
    ell.setMinorRadius(b);
    ell.setOrientation(phi);
    item._color=col;
    LayerLocker ll(this);
    _items.insert(index, item);
  }

  void CircleViewer::remove(int index)
  {
    TRACE;
    LayerLocker ll(this);
    _items.remove(index);
  }

  void CircleViewer::clear()
  {
    TRACE;
    LayerLocker ll(this);
    _items.clear();
  }

  void CircleViewer::resize(int n)
  {
    TRACE;
    LayerLocker ll(this);
    _items.resize(n);
  }

  void CircleViewer::paintData(const LayerPainterRequest& lp, QPainter& p, double dotpercm) const
  {
    TRACE;
    const GraphContentOptions& gc=lp.options();
    Limits limits(gc);
    double startPhi, stopPhi;
    int lineWeight=(int)floor(_lineWeight*dotpercm+0.5);
    if(lineWeight<1) {
      lineWeight=1;
    }
    for(int i=0; i<_items.count(); i++) {
      if(lp.terminated()) return;
      const Item& item=_items[i];
      const QGpCoreMath::Ellipse& ell=item._ellipse;
      if(limits.polarLimits(ell, startPhi, stopPhi)) {
        p.setPen(QPen(item._color, lineWeight));
        int rx=abs((int)(ell.majorRadius()* gc.ax()));
        int ry=abs((int)(ell.minorRadius()*gc.ay()));
        int orx=gc.xr2s(ell.center().x())-rx;
        int ory=gc.yr2s(ell.center().y())-ry;
        int minphi=(int)floor(Angle::radiansToDegrees(startPhi)*16.0);
        int dphi=(int)ceil(Angle::radiansToDegrees(stopPhi)*16.0)-minphi;
        //printf("Circle %i is plotted phi %lg %lg\n",i,startPhi,stopPhi);
        if(ell.orientation().isNull()) {
          p.drawArc(orx, ory, rx + rx, ry + ry, minphi, dphi);
        } else {
          p.save();
          p.translate(orx+rx, ory+ry);
          p.rotate(-ell.orientation().degrees());
          p.drawArc(-rx, -ry, rx + rx, ry + ry, minphi, dphi);
          p.restore();
        }
      }
      //else printf("Circle %i is not plotted\n",i);
    }
  }

  Rect CircleViewer::boundingRect() const
  {
    TRACE;
    Rect r;
    if(_items.count()>0) {
      const QGpCoreMath::Ellipse& ell=_items[0]._ellipse;
      const Point2D& c=ell.center();
      double xRadius=ell.xRadius();
      double yRadius=ell.yRadius();
      r.setLimits(c.x()-xRadius, c.y()-yRadius, c.x()+xRadius, c.y()+yRadius);
      for(int i=1; i<_items.count(); i++) {
        const QGpCoreMath::Ellipse& ell=_items[i]._ellipse;
        const Point2D& c=ell.center();
        r.add(c.x()-xRadius, c.y()-yRadius);
        r.add(c.x()+xRadius, c.y()+yRadius);
      }
    }
    return r;
  }

  CircleViewer::Limits::Limits(const GraphContentOptions& gc)
  {
    TRACE;
    // We call _a,_b,_c,_d the four corners of the visible area
    _a.setX(gc.xVisMin());
    _a.setY(gc.yVisMin());
    _b.setX(gc.xVisMin());
    _b.setY(gc.yVisMax());
    _c.setX(gc.xVisMax());
    _c.setY(gc.yVisMax());
    _d.setX(gc.xVisMax());
    _d.setY(gc.yVisMin());
    _ab.set(_a, _b);
    _bc.set(_b, _c);
    _cd.set(_c, _d);
    _da.set(_d, _a);
    _visLimits.setLimits(_a.x(), _a.y(), _c.x(), _c.y());
  }

  bool CircleViewer::Limits::polarLimits(const QGpCoreMath::Ellipse& ell, double& startphi, double& stopphi) const
  {
    TRACE;
    startphi=0;
    stopphi=2*M_PI;
    // TODO: include elliptical shape and orientation
    double r=ell.majorRadius();
    // First test if at least one arc is visible
    double minR, maxR, dist;
    // look for the max distance to corners
    maxR=_a.distanceTo(ell.center());
    dist=_b.distanceTo(ell.center());
    if(dist > maxR) maxR=dist;
    dist=_c.distanceTo(ell.center());
    if(dist > maxR) maxR=dist;
    dist=_d.distanceTo(ell.center());
    if(dist > maxR) maxR=dist;
    minR=maxR;
    if(_visLimits.includes(ell.center()) ) {
      if(r>maxR) return false; else return true;
    } else {
      // look for min and max to segments
      dist=_ab.distanceTo(ell.center());
      if(dist<minR) minR=dist; else if(dist > maxR) maxR=dist;
      dist=_bc.distanceTo(ell.center());
      if(dist<minR) minR=dist; else if(dist > maxR) maxR=dist;
      dist=_cd.distanceTo(ell.center());
      if(dist<minR) minR=dist; else if(dist > maxR) maxR=dist;
      dist=_da.distanceTo(ell.center());
      if(dist<minR) minR=dist; else if(dist > maxR) maxR=dist;
      //printf ("r=%lg rmin=%lg rmax=%lg\n",r,minR, maxR);
      if(r < minR || r > maxR) return false;
      // Find angular limits
      // init with point a
      double az0=ell.center().azimuthTo(_a);
      startphi=az0;
      stopphi=az0;
      // add point b
      double az=ell.center().azimuthTo(_b);
      double daz=az - az0;
      if(daz > M_PI) daz=2 * M_PI - daz;
      if(daz < -M_PI) daz=2 * M_PI + daz;
      az=az0 + daz;
      if(az > stopphi) stopphi=az;
      else if(az < startphi) startphi=az;
      // add point c
      az=ell.center().azimuthTo(_c);
      daz=az - az0;
      if(daz > M_PI) daz=2 * M_PI - daz;
      if(daz < -M_PI) daz=2 * M_PI + daz;
      az=az0 + daz;
      if(az > stopphi) stopphi=az;
      else if(az < startphi) startphi=az;
      // add point d
      az=ell.center().azimuthTo(_d);
      daz=az - az0;
      if(daz > M_PI) daz=2 * M_PI - daz;
      if(daz < -M_PI) daz=2 * M_PI + daz;
      az=az0 + daz;
      if(az > stopphi) stopphi=az;
      else if(az < startphi) startphi=az;
    }
    return true;
  }

  void CircleViewer::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
  {
    // TODO: have a more xml format for recording
    TRACE;
    GraphContentLayer::xml_writeProperties(s, context);
    XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
    if(scifigsContext->data()) {
      QString tmp;
      tmp+=s.indent();
      tmp+="<data>\n";
      int n=_items.count();
      for(int i=0;i<n;i++) {
        const Item& item=_items[i];
        const QGpCoreMath::Ellipse& ell=item._ellipse;
        const QColor& col=item._color;
        tmp+=s.indent();
        tmp+="  ";
        tmp+=QString::number(ell.center().x(),'g',10);
        tmp+=" ";
        tmp+=QString::number(ell.center().y(),'g',10);
        tmp+=" ";
        tmp+=QString::number(ell.majorRadius(),'g',10);
        tmp+=" ";
        tmp+=QString::number(ell.minorRadius(),'g',10);
        tmp+=" ";
        tmp+=QString::number(ell.orientation().degrees(),'g',10);
        tmp+=" ";
        tmp+=QString::number(col.red());
        tmp+=" ";
        tmp+=QString::number(col.green());
        tmp+=" ";
        tmp+=QString::number(col.blue());
        tmp+="\n";
      }
      tmp+=s.indent();
      tmp+="</data>\n";
      s << tmp;
    }
  }

  XMLMember CircleViewer::xml_member(XML_MEMBER_ARGS)
  {
    TRACE;
    XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
    if(scifigsContext->data()) {
      if(tag=="data") return XMLMember(0);
      else if(tag=="nCircles") return XMLMember(1); // For compatibility
    }
    return GraphContentLayer::xml_member(tag, attributes, context)+2;
  }

  bool CircleViewer::xml_setProperty(XML_SETPROPERTY_ARGS)
  {
    TRACE;
    Q_UNUSED(tag)
    switch (memberID) {
    case 0: {
        const QChar * ptr=0;
        //int n=_items.count();
        //if(n==0) return true;
        StringSection l=content.readLine(ptr);
        while(l.isValid()) {
          if(!add(l.toString())) {
            App::log(tr("Error parsing circle (%1 already added), incomplete line\n").arg(count()) );
            return false;
          }
          l=content.readLine(ptr);
        }
      }
      return true;
    case 1: { // For compatibility
        return true;
      }
    default:
      break;
    }
    return GraphContentLayer::xml_setProperty(memberID-2, tag, attributes, content, context);
  }

} // namespace SciFigs
