/************************************************************************
*
* Copyright 2010 Jakob Leben (jakob.leben@gmail.com)
*
* This file is part of SuperCollider Qt GUI.
*
* 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, see <http://www.gnu.org/licenses/>.
*
************************************************************************/

#pragma once

#include "../Common.h"
#include "../QcHelper.h"
#include "../style/style.hpp"

#include <QWidget>

struct QcGraphElement {

  friend class QcGraphModel;

  enum CurveType {
    Step = 0,
    Linear,
    Sine,
    Welch,
    Exponential,
    Quadratic,
    Cubic,
    Curvature,
    Hold
  };

  QcGraphElement( const QSize & sz ) :
    size( sz ),
    curveType( Linear ),
    curvature( 0.0 ),
    editable( true ),
    selected( false ),
    _prev(0),
    _next(0)
  {}

  void setCurveType( CurveType type, double curve = 0.0 ) {
    curveType = type;
    curvature = curve;
  }

  QcGraphElement *prev() { return _prev; }
  QcGraphElement *next() { return _next; }

  QPointF value;
  QSize size;
  QString text;
  QColor fillColor;
  CurveType curveType;
  double curvature;
  bool editable;
  bool selected;

private:
  QcGraphElement *_prev;
  QcGraphElement *_next;
};

class QcGraphModel : public QObject
{
  Q_OBJECT

  public:

    QcGraphModel( QObject * parent = 0 ) : QObject(parent) {}

    struct Connection {
      Connection( QcGraphElement * a_ = 0, QcGraphElement * b_ = 0 ) : a(a_), b(b_) {}
      QcGraphElement * a;
      QcGraphElement * b;
    };

    inline QList<QcGraphElement*> elements() const { return _elems; }

    inline QcGraphElement *elementAt( int i ) const { return _elems[i]; }

    inline int elementCount() const { return _elems.count(); }

    void append( QcGraphElement * e );

    void removeAt( int i );

    inline QList<Connection> connections() const { return _conns; }

    void connect( int xi, int yi ) {
      QcGraphElement *x = _elems[xi];
      QcGraphElement *y = _elems[yi];
      int i;
      for( i = 0; i < _conns.count(); ++i ) {
        Connection c = _conns[i];
        if( (c.a==x && c.b==y) || (c.a==y && c.b==x) ) return;
      }
      _conns.append( Connection(x,y) );
    }

  Q_SIGNALS:
    void appended( QcGraphElement * );
    void removed( QcGraphElement * );

  private:

    QList<Connection> _conns;
    QList<QcGraphElement*> _elems;
};

class QcGraph : public QWidget, QcHelper, QtCollider::Style::Client
{
  Q_OBJECT
  Q_ENUMS( ElementStyle )
  Q_PROPERTY( QVariantList value READ value WRITE setValue )
  Q_PROPERTY( QVariantList strings READ dummyVariantList WRITE setStrings );
  Q_PROPERTY( int index READ index WRITE setIndex );
  Q_PROPERTY( int lastIndex READ lastIndex );
  Q_PROPERTY( QVariantList selectionIndexes READ selectionIndexes);
  Q_PROPERTY( int thumbSize READ dummyInt WRITE setThumbSize );
  Q_PROPERTY( int thumbWidth READ dummyInt WRITE setThumbWidth );
  Q_PROPERTY( int thumbHeight READ dummyInt WRITE setThumbHeight );
  Q_PROPERTY( QColor background READ background WRITE setBackground );
  Q_PROPERTY( QColor strokeColor READ strokeColor WRITE setStrokeColor );
  Q_PROPERTY( QColor fillColor READ dummyColor WRITE setFillColor );
  Q_PROPERTY( QColor gridColor READ gridColor WRITE setGridColor );
  Q_PROPERTY( QColor focusColor READ focusColor WRITE setFocusColor );
  Q_PROPERTY( QColor selectionColor READ selectionColor WRITE setSelectionColor );
  Q_PROPERTY( bool drawLines READ dummyBool WRITE setDrawLines );
  Q_PROPERTY( bool drawRects READ dummyBool WRITE setDrawRects );
  Q_PROPERTY( ElementStyle style READ elementStyle WRITE setElementStyle );
  Q_PROPERTY( bool editable READ dummyBool WRITE setEditable );
  Q_PROPERTY( double step READ step WRITE setStep );
  Q_PROPERTY( int selectionForm READ selectionForm WRITE setSelectionForm );
  Q_PROPERTY( int horizontalOrder READ horizontalOrder WRITE setHorizontalOrder );
  Q_PROPERTY( float x READ currentX WRITE setCurrentX );
  Q_PROPERTY( float y READ currentY WRITE setCurrentY );
  Q_PROPERTY( QPointF grid READ grid WRITE setGrid );
  Q_PROPERTY( bool gridOn READ dummyBool WRITE setGridOn );

  public:
    Q_INVOKABLE void connectElements( int, QVariantList );
    Q_INVOKABLE void setStringAt( int, const QString & );
    Q_INVOKABLE void setFillColorAt( int, const QColor & );
    Q_INVOKABLE void setEditableAt( int, bool );
    Q_INVOKABLE void setThumbHeightAt( int, int );
    Q_INVOKABLE void setThumbWidthAt( int, int );
    Q_INVOKABLE void setThumbSizeAt( int, int );
    Q_INVOKABLE void setCurves( double curvature );
    Q_INVOKABLE void setCurves( int type );
    Q_INVOKABLE void setCurves( const QVariantList & curves );

  public Q_SLOTS:
    Q_INVOKABLE void select( int index, bool exclusive = true );
    Q_INVOKABLE void deselect( int index );
    Q_INVOKABLE void deselectAll();

  Q_SIGNALS:
    void action();
    void metaAction();

  public:
    enum SelectionForm {
      ElasticSelection,
      RigidSelection
    };

    enum Order {
      NoOrder,
      RigidOrder
    };

    enum ElementStyle {
      DotElements,
      RectElements,
    };

  public:
    QcGraph();

    QVariantList value() const;
    QcGraphElement *currentElement() const;
    int index() const;

    /** \brief The last node selected, regardless of current selection status.
     *
     * If no nodes have been selected yet, defaults to -1.
     */
    int lastIndex() const { return _lastIndex; }
    QVariantList selectionIndexes() const;
    float currentX() const;
    float currentY() const;
    QPointF grid() const { return _gridMetrics; }

    void setValue( const QVariantList & );
    void setStrings( const QVariantList &list );

    void setIndex( int i );
    void setCurrentX( float );
    void setCurrentY( float );
    void setThumbSize( int f );
    void setThumbWidth( int f );
    void setThumbHeight( int f );

    const QColor & background() const
      { return _bkg.isValid() ? _bkg : palette().color(QPalette::Base); }
    void setBackground( const QColor &c ) { _bkg = c; update(); }

    const QColor & selectionColor() const
      { return _selectColor.isValid() ? _selectColor : palette().color(QPalette::Highlight); }
    void setSelectionColor( const QColor &c ) { _selectColor = c; update(); }

    const QColor & strokeColor() const
      { return _strokeColor.isValid() ? _strokeColor :  palette().color(QPalette::Text); }
    void setStrokeColor( const QColor & c ) { _strokeColor = c; update(); }

    void setFillColor( const QColor & c );

    QColor gridColor() const
    {
      if(_gridColor.isValid())
        return _gridColor;
      else {
        QColor c = palette().color(QPalette::Text);
        c.setAlpha(40);
        return c;
      }
    }
    void setGridColor( const QColor & c ) { _gridColor = c; update(); }

    ElementStyle elementStyle() const { return _style; }
    void setElementStyle(ElementStyle s) { _style = s; _geometryDirty = true; update(); }
    void setDrawLines( bool b ) { _drawLines = b; update(); }
    void setDrawRects( bool b ) { _drawRects = b; update(); }
    void setEditable( bool b ) { _editable = b; update(); }

    double step() const { return _step; }
    void setStep( double );
    int selectionForm() const { return (int)_selectionForm; }
    void setSelectionForm( int i ) { _selectionForm = (SelectionForm) i; }
    int horizontalOrder() const { return (int)_xOrder; }
    void setHorizontalOrder( int i );
    void setGrid( const QPointF &pt ) { _gridMetrics = pt; update(); }
    void setGridOn( bool b ) { _gridOn = b; update(); }
    QSize sizeHint() const { return QSize( 200,200 ); }
    QSize minimumSizeHint() const { return QSize( 50,50 ); }

  private Q_SLOTS:
    void onElementRemoved( QcGraphElement *e );

  private:
    struct SelectedElement {
      SelectedElement( QcGraphElement *e ) : elem(e) {}
      bool operator == (const SelectedElement & other) {
        return elem == other.elem;
      }

      QcGraphElement *elem;
      QPointF moveOrigin; // in data domain
    };

  private:
    void setAllDeselected();

    /** \brief Sets a node as being either selected or unselected.
     *
     * \c update() is called at the end of the function.
     *
     * \param index the index of the node to update
     * \param selected whether to mark it as selected or not
     * \post \c lastIndex() returns \c index if the node was newly selected.
     */
    void setIndexSelected( int index, bool selected );

    void restrictValue( QPointF & );
    void orderRestrictValue( QcGraphElement *, QPointF &, bool selected );
    void setValue( QcGraphElement *, const QPointF & );
    void ensureOrder();
    void moveFree( QcGraphElement *, const QPointF & );
    void moveOrderRestricted( QcGraphElement *, const QPointF & );
    void moveSelected( const QPointF & dValue, SelectionForm, bool fromCache );
    void addCurve( QPainterPath &, QcGraphElement *e1, QcGraphElement *e2 );
    QSize drawnElementSize( QcGraphElement *e );
    QRect valueRect();
    QRectF labelRect( QcGraphElement *, const QPointF &, const QRect &, const QFontMetrics & );
    void drawDotElement( QcGraphElement *, const QRectF &, const QRect & bounds,
                         const QColor & dotColor, const QColor & circleColor,
                         const QColor & textColor, const QColor & selectColor,
                         const QPalette &, const QFontMetrics &, QPainter * );
    void drawRectElement( QcGraphElement *, const QRectF &,
                          const QColor & fillColor,
                          const QColor & textColor,
                          const QColor & selectColor,
                          const QPalette &, QPainter * );
    void paintEvent( QPaintEvent * );
    void mousePressEvent( QMouseEvent * );
    void mouseMoveEvent( QMouseEvent * );
    void keyPressEvent( QKeyEvent * );
    void doAction( Qt::KeyboardModifiers );

    QcGraphModel _model;

    QColor _bkg;
    QColor _strokeColor;
    QColor _gridColor;
    QColor _selectColor;

    QSize _defaultThumbSize;
    QPointF _gridMetrics;
    bool _gridOn;

    ElementStyle _style;
    bool _drawLines;
    bool _drawRects;

    bool _editable;
    double _step;
    SelectionForm _selectionForm;
    Order _xOrder;

    bool _geometryDirty;
    QSize _largestThumbSize;

    struct Selection {
      Selection () : cached(false), shallMove(false) {}
      int size() const { return elems.size(); }
      int count() const { return elems.count(); }

      QList<SelectedElement> elems;
      bool cached;
      bool shallMove;
      QPointF moveOrigin; // in data domain
    } _selection;

    int _lastIndex; ///< The last node selected, regardless of the current state of selection
};
