/*
  This file is part of CalendarViews.

  Copyright (c) 2007 Till Adam <adam@kde.org>
  Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
  Copyright (c) 2010 Andras Mantia <andras@kdab.com>

  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.

  As a special exception, permission is given to link this program
  with any edition of Qt, and distribute the resulting executable,
  without including the source code for Qt in the source distribution.
*/

#include "timelineview.h"
#include "timelineitem.h"
#include "timelineview_p.h"
#include "helper.h"

#include <kdgantt2/kdganttgraphicsitem.h>
#include <kdgantt2/kdganttgraphicsview.h>
#include <kdgantt2/kdganttabstractrowcontroller.h>
#include <kdgantt2/kdganttdatetimegrid.h>
#include <kdgantt2/kdganttitemdelegate.h>
#include <kdgantt2/kdganttstyleoptionganttitem.h>

#include <calendarsupport/calendar.h>
#include <calendarsupport/collectionselection.h>
#include <calendarsupport/utils.h>
#include <calendarsupport/kcalprefs.h>
#include <calendarsupport/incidencechanger.h>

#include <QApplication>
#include <QPainter>
#include <QLayout>
#include <QStandardItemModel>
#include <QResizeEvent>
#include <QSplitter>
#include <QTreeWidget>
#include <QHeaderView>
#include <QPointer>

using namespace KCalCore;
using namespace EventViews;

namespace EventViews {
class RowController : public KDGantt::AbstractRowController {
private:
    static const int ROW_HEIGHT ;
    QPointer<QAbstractItemModel> m_model;

public:
    RowController()
    {
      mRowHeight = 20;
    }

    void setModel( QAbstractItemModel* model )
    {
        m_model = model;
    }

    /*reimp*/int headerHeight() const { return 2*mRowHeight + 10; }

    /*reimp*/ bool isRowVisible( const QModelIndex& ) const { return true;}
    /*reimp*/ bool isRowExpanded( const QModelIndex& ) const { return false; }
    /*reimp*/ KDGantt::Span rowGeometry( const QModelIndex& idx ) const
    {
        return KDGantt::Span( idx.row()*mRowHeight, mRowHeight );
    }
    /*reimp*/ int maximumItemHeight() const {
        return mRowHeight/2;
    }
    /*reimp*/int totalHeight() const {
        return m_model->rowCount()* mRowHeight;
    }

    /*reimp*/ QModelIndex indexAt( int height ) const {
        return m_model->index( height/mRowHeight, 0 );
    }

    /*reimp*/ QModelIndex indexBelow( const QModelIndex& idx ) const {
        if ( !idx.isValid() )return QModelIndex();
        return idx.model()->index( idx.row()+1, idx.column(), idx.parent() );
    }
    /*reimp*/ QModelIndex indexAbove( const QModelIndex& idx ) const {
        if ( !idx.isValid() )return QModelIndex();
        return idx.model()->index( idx.row()-1, idx.column(), idx.parent() );
    }

    void setRowHeight( int height ) { mRowHeight = height; }

private:
    int mRowHeight;

};

class GanttHeaderView : public QHeaderView {
public:
    explicit GanttHeaderView( QWidget* parent=0 ) : QHeaderView( Qt::Horizontal, parent ) {
    }

    QSize sizeHint() const { QSize s = QHeaderView::sizeHint(); s.rheight() *= 2; return s; }
};

class GanttItemDelegate : public KDGantt::ItemDelegate {
    void paintGanttItem( QPainter* painter,
                         const KDGantt::StyleOptionGanttItem& opt,
                         const QModelIndex& idx )
    {
        painter->setRenderHints( QPainter::Antialiasing );
        if ( !idx.isValid() ) return;
        KDGantt::ItemType type = static_cast<KDGantt::ItemType>(
                            idx.model()->data( idx, KDGantt::ItemTypeRole ).toInt() );
        QString txt = idx.model()->data( idx, Qt::DisplayRole ).toString();
        QRectF itemRect = opt.itemRect;
        QRectF boundingRect = opt.boundingRect;
        boundingRect.setY( itemRect.y() );
        boundingRect.setHeight( itemRect.height() );

        QBrush brush = defaultBrush( type );
        if ( opt.state & QStyle::State_Selected ) {
            QLinearGradient selectedGrad( 0., 0., 0.,
                                          QApplication::fontMetrics().height() );
            selectedGrad.setColorAt( 0., Qt::red );
            selectedGrad.setColorAt( 1., Qt::darkRed );

            brush = QBrush( selectedGrad );
            painter->setBrush( brush );
        } else
          painter->setBrush( idx.model()->data( idx, Qt::DecorationRole ).value<QColor>() );

        painter->setPen( defaultPen( type ) );
        painter->setBrushOrigin( itemRect.topLeft() );

        switch( type ) {
        case KDGantt::TypeTask:
            if ( itemRect.isValid() ) {
                QRectF r = itemRect;
                painter->drawRect( r );

                Qt::Alignment ta;
                switch( opt.displayPosition ) {
                case KDGantt::StyleOptionGanttItem::Left: ta = Qt::AlignLeft; break;
                case KDGantt::StyleOptionGanttItem::Right: ta = Qt::AlignRight; break;
                case KDGantt::StyleOptionGanttItem::Center: ta = Qt::AlignCenter; break;
                }
                painter->drawText( boundingRect, ta, txt );
            }
            break;
        default:
            KDGantt::ItemDelegate::paintGanttItem( painter, opt, idx );
            break;
        }
    }
};

}

TimelineView::TimelineView( QWidget *parent )
  : EventView( parent ), d( new Private( this ) )
{
  QVBoxLayout *vbox = new QVBoxLayout( this );
  vbox->setMargin( 0 );
  QSplitter *splitter = new QSplitter( Qt::Horizontal, this );
  d->mLeftView = new QTreeWidget;
  d->mLeftView->setHeader( new GanttHeaderView );
  d->mLeftView->setHeaderLabel( i18n("Calendar") );
  d->mLeftView->setRootIsDecorated( false );
  d->mLeftView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );

  d->mGantt = new KDGantt::GraphicsView();
  splitter->addWidget( d->mLeftView );
  splitter->addWidget( d->mGantt );
  connect( splitter, SIGNAL( splitterMoved(int,int) ),
           d, SLOT( splitterMoved() ) );
  QStandardItemModel *model = new QStandardItemModel( this );

  d->mRowController = new RowController;
  d->mRowController->setRowHeight( fontMetrics().height() ); //TODO: detect

  d->mRowController->setModel( model );
  d->mGantt->setRowController( d->mRowController );

  KDGantt::DateTimeGrid *grid = new KDGantt::DateTimeGrid;
  grid->setScale( KDGantt::DateTimeGrid::ScaleHour );
  grid->setDayWidth( 800 );
  grid->setRowSeparators( true );
  d->mGantt->setGrid( grid );
  d->mGantt->setModel( model );
  d->mGantt->viewport()->setFixedWidth( 8000 );

  d->mGantt->viewport()->installEventFilter( this );

#if 0
  d->mGantt->setCalendarMode( true );
  d->mGantt->setShowLegendButton( false );
  d->mGantt->setFixedHorizon( true );
  d->mGantt->removeColumn( 0 );
  d->mGantt->addColumn( i18n( "Calendar" ) );
  d->mGantt->setHeaderVisible( true );
  if ( KGlobal::locale()->use12Clock() ) {
    d->mGantt->setHourFormat( KDGanttView::Hour_12 );
  } else {
    d->mGantt->setHourFormat( KDGanttView::Hour_24_FourDigit );
  }
#else
 kDebug() << "Disabled code, port to KDGantt2";
#endif
 d->mGantt->setItemDelegate( new GanttItemDelegate);


  vbox->addWidget( splitter );

#if 0
  connect( d->mGantt, SIGNAL(rescaling(KDGanttView::Scale)),
           SLOT(overscale(KDGanttView::Scale)) );
#else
 kDebug() << "Disabled code, port to KDGantt2";
#endif
  connect( model, SIGNAL(itemChanged(QStandardItem*)),
           d, SLOT(itemChanged(QStandardItem*)) );
  connect( d->mGantt, SIGNAL(doubleClicked(QModelIndex)),
           d, SLOT(itemDoubleClicked(QModelIndex)) );
  connect( d->mGantt, SIGNAL(activated(QModelIndex)),
           d, SLOT(itemSelected(QModelIndex)) );
  d->mGantt->setContextMenuPolicy( Qt::CustomContextMenu );
  connect( d->mGantt, SIGNAL(customContextMenuRequested(QPoint)),
           d, SLOT(contextMenuRequested(QPoint)) );

#if 0
  connect( d->mGantt, SIGNAL(dateTimeDoubleClicked(const QDateTime &)),
           d, SLOT(newEventWithHint(const QDateTime &)) );
#else
  kDebug() << "Disabled code, port to KDGantt2";
#endif
}

TimelineView::~TimelineView()
{
  delete d->mRowController;
  delete d;
}

/*virtual*/
Akonadi::Item::List TimelineView::selectedIncidences() const
{
  return d->mSelectedItemList;
}

/*virtual*/
KCalCore::DateList TimelineView::selectedIncidenceDates() const
{
  return KCalCore::DateList();
}

/*virtual*/
int TimelineView::currentDateCount() const
{
  return 0;
}

/*virtual*/
void TimelineView::showDates( const QDate &start, const QDate &end, const QDate &preferredMonth )
{
  Q_UNUSED( preferredMonth );
  Q_ASSERT_X( calendar(), "showDates()", "set a CalendarSupport::Calendar" );
  Q_ASSERT_X( start.isValid(), "showDates()", "start date must be valid" );
  Q_ASSERT_X( end.isValid(), "showDates()", "end date must be valid" );

  kDebug() << "start=" << start << "end=" << end;

  d->mStartDate = start;
  d->mEndDate = end;
  d->mHintDate = QDateTime();
  KDGantt::DateTimeGrid *grid = static_cast<KDGantt::DateTimeGrid*>(d->mGantt->grid());
  grid->setStartDateTime( QDateTime( start ) );
#if 0
  d->mGantt->setHorizonStart( QDateTime( start ) );
  d->mGantt->setHorizonEnd( QDateTime( end.addDays( 1 ) ) );
  d->mGantt->setMinorScaleCount( 1 );
  d->mGantt->setScale( KDGanttView::Hour );
  d->mGantt->setMinimumScale( KDGanttView::Hour );
  d->mGantt->setMaximumScale( KDGanttView::Hour );
  d->mGantt->zoomToFit();

  d->mGantt->setUpdateEnabled( false );
  d->mGantt->clear();
#else
 kDebug() << "Disabled code, port to KDGantt2";
#endif

  d->mLeftView->clear();
  uint index = 0;
  // item for every calendar
  TimelineItem *item = 0;
  CalendarSupport::Calendar *calres = calendar();
  if ( !calres ) {
    item = new TimelineItem( calendar(), index++, static_cast<QStandardItemModel*>( d->mGantt->model() ), d->mGantt );
    d->mLeftView->addTopLevelItem( new QTreeWidgetItem( QStringList() << i18n( "Calendar" ) ) );
    d->mCalendarItemMap.insert( -1, item );

  } else {
    const CalendarSupport::CollectionSelection *colSel = collectionSelection();
    const Akonadi::Collection::List collections = colSel->selectedCollections();

    Q_FOREACH ( const Akonadi::Collection &collection, collections ) {
      if ( collection.contentMimeTypes().contains( Event::eventMimeType() ) ) {
        item = new TimelineItem( calendar(), index++, static_cast<QStandardItemModel*>( d->mGantt->model() ), d->mGantt );
        d->mLeftView->addTopLevelItem(new QTreeWidgetItem( QStringList() << CalendarSupport::displayName( collection ) ) );
        const QColor resourceColor = EventViews::resourceColor( collection, preferences() );
        if ( resourceColor.isValid() ) {
          item->setColor( resourceColor );
        }
        kDebug() << "Created item " << item << " ( " <<  CalendarSupport::displayName( collection ) << " ) with index"
                 <<  index - 1 << " from collection " << collection.id();
        d->mCalendarItemMap.insert( collection.id(), item );
      }
    }
  }

  // add incidences

  /**
   * We remove the model from the view here while we fill it with items,
   * because every call to insertIncidence will cause the view to do an expensive
   * updateScene() call otherwise.
   */
  QAbstractItemModel *ganttModel = d->mGantt->model();
  d->mGantt->setModel( 0 );

  Akonadi::Item::List events;
  KDateTime::Spec timeSpec = CalendarSupport::KCalPrefs::instance()->timeSpec();
  for ( QDate day = start; day <= end; day = day.addDays( 1 ) ) {
    events = calendar()->events( day, timeSpec,
                                 CalendarSupport::EventSortStartDate,
                                 CalendarSupport::SortDirectionAscending );
    Q_FOREACH ( const Akonadi::Item &i, events ) {
      d->insertIncidence( i, day );
    }
  }
  d->mGantt->setModel( ganttModel );
  d->splitterMoved();
}

/*virtual*/
void TimelineView::showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date )
{
  Q_UNUSED( incidenceList );
  Q_UNUSED( date );
}

/*virtual*/
void TimelineView::updateView()
{
  if ( d->mStartDate.isValid() && d->mEndDate.isValid() ) {
    showDates( d->mStartDate, d->mEndDate );
  }
}

/*virtual*/
void TimelineView::changeIncidenceDisplay( const Akonadi::Item &incidence, int mode )
{
  switch ( mode ) {
  case CalendarSupport::IncidenceChanger::INCIDENCEADDED:
    d->insertIncidence( incidence );
    break;
  case CalendarSupport::IncidenceChanger::INCIDENCEEDITED:
    d->removeIncidence( incidence );
    d->insertIncidence( incidence );
    break;
  case CalendarSupport::IncidenceChanger::INCIDENCEDELETED:
    d->removeIncidence( incidence );
    break;
  default:
    updateView();
  }
}

bool TimelineView::eventDurationHint( QDateTime &startDt, QDateTime &endDt,
                                      bool &allDay ) const
{
  startDt = QDateTime( d->mHintDate );
  endDt = QDateTime( d->mHintDate.addSecs( 2 * 60 * 60 ) );
  allDay = false;
  return d->mHintDate.isValid();
}

// void TimelineView::overscale( KDGanttView::Scale scale )
// {
//   Q_UNUSED( scale );
//   /* Disabled, looks *really* bogus:
//      this triggers and endless rescaling loop; we want to set
//      a fixed scale, the Gantt view doesn't like it and rescales
//      (emitting a rescaling signal that leads here) and so on...
//   //set a relative zoom factor of 1 (?!)
//   d->mGantt->setZoomFactor( 1, false );
//   d->mGantt->setScale( KDGanttView::Hour );
//   d->mGantt->setMinorScaleCount( 12 );
//   */
// }

QDate TimelineView::startDate() const
{
  return d->mStartDate;
}

QDate TimelineView::endDate() const
{
  return d->mEndDate;
}

bool TimelineView::eventFilter( QObject *object, QEvent *event )
{
  if ( event->type() == QEvent::ToolTip ) {
    QHelpEvent *helpEvent = static_cast<QHelpEvent*>( event );
    QGraphicsItem *item = d->mGantt->itemAt( helpEvent->pos() );
    if ( item ) {
      if ( item->type() == KDGantt::GraphicsItem::Type ) {
        KDGantt::GraphicsItem *graphicsItem = static_cast<KDGantt::GraphicsItem*>( item );
        const QModelIndex itemIndex = graphicsItem->index();

        QStandardItemModel *itemModel = qobject_cast<QStandardItemModel*>( d->mGantt->model() );
        TimelineSubItem *timelineItem = dynamic_cast<TimelineSubItem*>( itemModel->item( itemIndex.row(), itemIndex.column() ) );
        if ( timelineItem )
          timelineItem->updateToolTip();
      }
    }
  }

  return EventView::eventFilter( object, event );
}

#include "timelineview.moc"
