/*
    Copyright (C) 2010 Klarälvdalens Datakonsult AB,
        a KDAB Group company, info@kdab.net,
        author Stephen Kelly <stephen@kdab.com>

    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Library General Public License as published by
    the Free Software Foundation; either version 2 of the License, or (at your
    option) any later version.

    This library 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 Library General Public
    License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include "kviewstatesaver.h"

#include <QtGui/QAbstractScrollArea>
#include <QScrollBar>
#include <QTimer>
#include <QTreeView>

#include "kdebug.h"

#include "kconfiggroup.h"

static const char * selectionKey = "Selection";
static const char * expansionKey = "Expansion";
static const char * currentKey = "Current";
static const char * scrollStateHorizontalKey = "HorizontalScroll";
static const char * scrollStateVerticalKey = "VerticalScroll";

class KViewStateSaverPrivate
{
public:
  KViewStateSaverPrivate(KViewStateSaver *qq)
    : q_ptr(qq),
      m_treeView(0),
      m_view(0),
      m_selectionModel(0),
      m_scrollArea(0),
      m_horizontalScrollBarValue(-1),
      m_verticalScrollBarValue(-1)
  {

  }

  Q_DECLARE_PUBLIC(KViewStateSaver)
  KViewStateSaver * const q_ptr;

  QStringList getExpandedItems(const QModelIndex &index) const;

  void listenToPendingChanges();
  void processPendingChanges();

  inline void restoreScrollBarState()
  {
    if ( !m_scrollArea )
      return;

    if ( m_horizontalScrollBarValue >= 0 && m_horizontalScrollBarValue <= m_scrollArea->horizontalScrollBar()->maximum() ) {
      m_scrollArea->horizontalScrollBar()->setValue( m_horizontalScrollBarValue );
      m_horizontalScrollBarValue = -1;
    }
    if ( m_verticalScrollBarValue >= 0 && m_verticalScrollBarValue <= m_scrollArea->verticalScrollBar()->maximum() ) {
      m_scrollArea->verticalScrollBar()->setValue( m_verticalScrollBarValue );
      m_verticalScrollBarValue = -1;
    }
  }

  void restoreSelection();
  void restoreCurrentItem();
  void restoreExpanded();

  inline bool hasPendingChanges() const
  {
    return !m_pendingCurrent.isEmpty() || !m_pendingExpansions.isEmpty() || !m_pendingSelections.isEmpty();
  }

  const QAbstractItemModel* getModel()
  {
    if ( m_selectionModel && m_selectionModel->model() )
      return m_selectionModel->model();
    else if ( m_view && m_view->model() )
      return m_view->model();
    return 0;
  }

  void rowsInserted( const QModelIndex &/*index*/, int /*start*/, int /*end*/ )
  {
    Q_Q(KViewStateSaver);
    processPendingChanges();

    if ( !hasPendingChanges() )
    {
      q->disconnect( getModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
          q, SLOT(rowsInserted(QModelIndex,int,int)) );
      q->deleteLater();
    }
  }

  QTreeView *m_treeView;
  QAbstractItemView *m_view;
  QItemSelectionModel *m_selectionModel;
  QAbstractScrollArea *m_scrollArea;

  int m_horizontalScrollBarValue;
  int m_verticalScrollBarValue;
  QSet<QString> m_pendingSelections;
  QSet<QString> m_pendingExpansions;
  QString m_pendingCurrent;
};

KViewStateSaver::KViewStateSaver(QObject* parent)
  : QObject(0), d_ptr( new KViewStateSaverPrivate(this) )
{
  Q_UNUSED(parent);
  qRegisterMetaType<QModelIndex>( "QModelIndex" );
}

KViewStateSaver::~KViewStateSaver()
{
  delete d_ptr;
}

void KViewStateSaver::setView(QAbstractItemView* view)
{
  Q_D(KViewStateSaver);
  d->m_scrollArea = view;
  if (view) {
    d->m_selectionModel = view->selectionModel();
    d->m_treeView = qobject_cast<QTreeView*>(view);
  } else {
    d->m_selectionModel = 0;
    d->m_treeView = 0;
  }
  d->m_view = view;
}

QAbstractItemView* KViewStateSaver::view() const
{
  Q_D(const KViewStateSaver);
  return d->m_view;
}

QItemSelectionModel* KViewStateSaver::selectionModel() const
{
  Q_D(const KViewStateSaver);
  return d->m_selectionModel;
}

void KViewStateSaver::setSelectionModel(QItemSelectionModel* selectionModel)
{
  Q_D(KViewStateSaver);
  d->m_selectionModel = selectionModel;
}

void KViewStateSaverPrivate::listenToPendingChanges()
{
  Q_Q(KViewStateSaver);
  // watch the model for stuff coming in delayed
  if ( hasPendingChanges() )
  {
    const QAbstractItemModel *model = getModel();
    if ( model )
    {
      q->disconnect( model, SIGNAL(rowsInserted(QModelIndex,int,int)),
          q, SLOT(rowsInserted(QModelIndex,int,int)) );
      q->connect( model, SIGNAL(rowsInserted(QModelIndex,int,int)),
          SLOT(rowsInserted(QModelIndex,int,int)) );
      return;
    } else {
      q->deleteLater();
    }
  } else {
    q->deleteLater();
  }
}

void KViewStateSaverPrivate::processPendingChanges()
{
  Q_Q(KViewStateSaver);

  q->restoreCurrentItem(m_pendingCurrent);
  q->restoreSelection(m_pendingSelections.toList());
  q->restoreExpanded(m_pendingExpansions.toList());
  q->restoreScrollState(m_verticalScrollBarValue, m_horizontalScrollBarValue);
}

void KViewStateSaver::restoreState(const KConfigGroup& configGroup)
{
  Q_D(KViewStateSaver);

  // Delete myself if not finished after 60 seconds.
  QTimer::singleShot(60000, this, SLOT(deleteLater()));


  d->m_pendingCurrent = configGroup.readEntry( currentKey, QString() );
  d->m_pendingSelections = configGroup.readEntry( selectionKey, QStringList() ).toSet();
  d->m_pendingExpansions = configGroup.readEntry( expansionKey, QStringList() ).toSet();
  d->m_horizontalScrollBarValue = configGroup.readEntry( scrollStateHorizontalKey, -1 );
  d->m_verticalScrollBarValue = configGroup.readEntry( scrollStateVerticalKey, -1 );

  d->processPendingChanges();
  if (d->hasPendingChanges())
    d->listenToPendingChanges();
}

QStringList KViewStateSaverPrivate::getExpandedItems(const QModelIndex &index) const
{
  Q_Q(const KViewStateSaver);

  QStringList expansion;
  for ( int i = 0; i < m_treeView->model()->rowCount( index ); ++i ) {
    const QModelIndex child = m_treeView->model()->index( i, 0, index );

    // http://bugreports.qt.nokia.com/browse/QTBUG-18039
    if ( m_treeView->model()->hasChildren( child ) ) {
      if ( m_treeView->isExpanded( child ) )
        expansion << q->indexToConfigString( child );
      expansion << getExpandedItems( child );
    }
  }
  return expansion;
}

void KViewStateSaver::saveState(KConfigGroup& configGroup)
{
  Q_D(KViewStateSaver);

  if ( d->m_selectionModel )
  {
    configGroup.writeEntry( selectionKey, selectionKeys() );
    configGroup.writeEntry( currentKey, currentIndexKey() );
  }

  if ( d->m_treeView )
  {
    QStringList expansion = expansionKeys();

    configGroup.writeEntry( expansionKey, expansion );
  }

  if ( d->m_scrollArea )
  {
    QPair<int, int> _scrollState = scrollState();
    configGroup.writeEntry( scrollStateVerticalKey, _scrollState.first );
    configGroup.writeEntry( scrollStateHorizontalKey, _scrollState.second );
  }
}

void KViewStateSaverPrivate::restoreCurrentItem()
{
  Q_Q(KViewStateSaver);

  QModelIndex currentIndex = q->indexFromConfigString(m_selectionModel->model(), m_pendingCurrent);
  if ( currentIndex.isValid() )
  {
    if (m_treeView)
      m_treeView->setCurrentIndex(currentIndex);
    else
      m_selectionModel->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
    m_pendingCurrent.clear();
  }
}

void KViewStateSaver::restoreCurrentItem(const QString& indexString)
{
  Q_D(KViewStateSaver);
  if (!d->m_selectionModel || !d->m_selectionModel->model())
      return;

  if (indexString.isEmpty())
  {
    return;
  }
  d->m_pendingCurrent = indexString;
  d->restoreCurrentItem();

  if (d->hasPendingChanges())
    d->listenToPendingChanges();
}

void KViewStateSaverPrivate::restoreExpanded()
{
  Q_Q(KViewStateSaver);

  QSet<QString>::iterator it = m_pendingExpansions.begin();
  for ( ; it != m_pendingExpansions.end(); )
  {
    QModelIndex idx = q->indexFromConfigString( m_treeView->model(), *it);
    if ( idx.isValid() )
    {
      m_treeView->expand( idx );
      it = m_pendingExpansions.erase( it );
    } else {
      ++it;
    }
  }
}

void KViewStateSaver::restoreExpanded(const QStringList& indexStrings)
{
  Q_D(KViewStateSaver);
  if (!d->m_treeView || !d->m_treeView->model())
      return;

  if (indexStrings.isEmpty())
    return;

  d->m_pendingExpansions.unite(indexStrings.toSet());
  d->restoreExpanded();
  if (d->hasPendingChanges())
    d->listenToPendingChanges();
}

void KViewStateSaver::restoreScrollState(int verticalScoll, int horizontalScroll)
{
  Q_D(KViewStateSaver);

  if ( !d->m_scrollArea )
    return;

  d->m_verticalScrollBarValue = verticalScoll;
  d->m_horizontalScrollBarValue = horizontalScroll;

  QTimer::singleShot( 0, this, SLOT(restoreScrollBarState()) );
}

void KViewStateSaverPrivate::restoreSelection()
{
  Q_Q(KViewStateSaver);

  QSet<QString>::iterator it = m_pendingSelections.begin();
  for ( ; it != m_pendingSelections.end(); )
  {
    QModelIndex idx = q->indexFromConfigString( m_selectionModel->model(), *it);
    if ( idx.isValid() )
    {
      m_selectionModel->select( idx, QItemSelectionModel::Select );
      it = m_pendingSelections.erase( it );
    } else {
      ++it;
    }
  }
}

void KViewStateSaver::restoreSelection(const QStringList& indexStrings)
{
  Q_D(KViewStateSaver);

  if (!d->m_selectionModel || !d->m_selectionModel->model())
      return;

  if (indexStrings.isEmpty())
    return;

  d->m_pendingSelections.unite(indexStrings.toSet());
  d->restoreSelection();
  if (d->hasPendingChanges())
    d->listenToPendingChanges();
}

QString KViewStateSaver::currentIndexKey() const
{
  Q_D(const KViewStateSaver);
  if (!d->m_selectionModel)
      return QString();
  return indexToConfigString(d->m_selectionModel->currentIndex());
}

QStringList KViewStateSaver::expansionKeys() const
{
  Q_D(const KViewStateSaver);
  if (!d->m_treeView || !d->m_treeView->model())
      return QStringList();

  return d->getExpandedItems(QModelIndex());
}

QStringList KViewStateSaver::selectionKeys() const
{
  Q_D(const KViewStateSaver);
  if (!d->m_selectionModel)
      return QStringList();

  QModelIndexList selectedIndexes = d->m_selectionModel->selectedRows();
  QStringList selection;
  foreach ( const QModelIndex &index, selectedIndexes )
    selection << indexToConfigString( index );

  return selection;
}

QPair<int, int> KViewStateSaver::scrollState() const
{
  Q_D(const KViewStateSaver);
  return qMakePair(d->m_scrollArea->verticalScrollBar()->value(), d->m_scrollArea->horizontalScrollBar()->value());
}

#include "kviewstatesaver.moc"

