/****************************************************************************
 *   Copyright (C) 2014-2016 by Savoir-faire Linux                          *
 *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
 *                                                                          *
 *   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 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      *
 *   Lesser 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/>.  *
 ***************************************************************************/
#include "collectionmodel.h"

//Qt
#include <QtCore/QCoreApplication>
#include <QtCore/QSortFilterProxyModel>

//Ring
#include "collectionmanagerinterface.h"
#include "globalinstances.h"
#include "interfaces/itemmodelstateserializeri.h"
#include "collectionextensioninterface.h"
#include "private/collectionmodel_p.h"

class ManageableCollectionProxy final : public QSortFilterProxyModel
{
public:
   ManageableCollectionProxy(QAbstractItemModel* parent) : QSortFilterProxyModel(parent)
   {
      setSourceModel(parent);
   }
   virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
};

CollectionModelPrivate::CollectionModelPrivate(CollectionModel* parent) : QObject(parent),q_ptr(parent),
m_pManageableProxy(nullptr),m_NewCollectionMutex(QMutex::Recursive)
{}


CollectionModelPrivate::~CollectionModelPrivate()
{
}

CollectionModelPrivate::ProxyItem::~ProxyItem()
{
   foreach(ProxyItem* i, m_Children)
      delete i;
}

CollectionModel::CollectionModel(QObject* parent) : QAbstractTableModel(parent), d_ptr(new CollectionModelPrivate(this))
{
   load();
}

CollectionModel::~CollectionModel()
{
   while (d_ptr->m_lTopLevelBackends.size()) {
      CollectionModelPrivate::ProxyItem* item = d_ptr->m_lTopLevelBackends[0];
      d_ptr->m_lTopLevelBackends.remove(0);
      while (item->m_Children.size()) {
         //FIXME I don't think it can currently happen, but there may be
         //more than 2 levels.
         CollectionModelPrivate::ProxyItem* item2 = item->m_Children[0];
         item->m_Children.remove(0);
         delete item2;
      }
      delete item;
   }
}

CollectionModel& CollectionModel::instance()
{
    static auto instance = new CollectionModel(QCoreApplication::instance());
    return *instance;
}

QHash<int,QByteArray> CollectionModel::roleNames() const
{
   static QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
   static bool initRoles = false;
   if (!initRoles) {
      initRoles = true;
      roles[static_cast<int>(Role::count                ) ] = "count"                 ;
      roles[static_cast<int>(Role::supportNone          ) ] = "supportNone"           ;
      roles[static_cast<int>(Role::supportLoad          ) ] = "supportLoad"           ;
      roles[static_cast<int>(Role::supportSave          ) ] = "supportSave"           ;
      roles[static_cast<int>(Role::supportEdit          ) ] = "supportEdit"           ;
      roles[static_cast<int>(Role::supportProbe         ) ] = "supportProbe"          ;
      roles[static_cast<int>(Role::supportAdd           ) ] = "supportAdd"            ;
      roles[static_cast<int>(Role::supportSave_all      ) ] = "supportSave_all"       ;
      roles[static_cast<int>(Role::supportClear         ) ] = "supportClear"          ;
      roles[static_cast<int>(Role::supportRemove        ) ] = "supportRemove"         ;
      roles[static_cast<int>(Role::supportExport        ) ] = "supportExport"         ;
      roles[static_cast<int>(Role::supportImport        ) ] = "supportImport"         ;
      roles[static_cast<int>(Role::isEnableable         ) ] = "isEnableable"          ;
      roles[static_cast<int>(Role::isDisableable        ) ] = "isDisableable"         ;
      roles[static_cast<int>(Role::isManageable         ) ] = "isManageable"          ;
      roles[static_cast<int>(Role::hasManageableChildren) ] = "hasManageableChildren" ;

   }

   return roles;
}

QVariant CollectionModel::data (const QModelIndex& idx, int role) const
{
   if (idx.isValid()) {
      CollectionModelPrivate::ProxyItem* item = static_cast<CollectionModelPrivate::ProxyItem*>(idx.internalPointer());

      /*if (idx.column() > 0 && item->collection)
         return d_ptr->m_lExtensions[idx.column()-1]->data(item->collection,idx,role);*/

      if (item->collection) {
         switch(role) {
            case Qt::DisplayRole:
               return item->collection->name();
            case Qt::DecorationRole:
               return item->collection->icon();
            case Qt::UserRole: // and Role::count
               return 'x'+QString::number(item->collection->size());
            case Qt::CheckStateRole: {
               try {
                  GlobalInstances::itemModelStateSerializer(); //TODO do better than that
                  return item->collection->isEnabled()?Qt::Checked:Qt::Unchecked;
               } catch (...) {
                  break;
               }
            }
            case static_cast<int>(Role::hasManageableChildren):
               return item->manageableCount ? true : false;
         };

         //Retro-map to the SupportedFeatures //WARNING if SupportedFeatures change, this need to be updated
         if (role > Qt::UserRole && role <= Qt::UserRole+14) {
            return (bool) (role == Qt::UserRole+1 ? true : bool(item->collection->supportedFeatures() & ((CollectionInterface::SupportedFeatures)(0x01 << (role - Qt::UserRole - 2)))));
         }
      }
      else {
         switch(role) {
            case Qt::DisplayRole:
               return item->m_AltName;
            case static_cast<int>(Role::hasManageableChildren):
               return item->manageableCount ? true : false;
         }
      }
   }
   return QVariant();
}

int CollectionModel::rowCount (const QModelIndex& parent) const
{
   if (!parent.isValid()) {
      return d_ptr->m_lTopLevelBackends.size();
   }
   else {
      CollectionModelPrivate::ProxyItem* item = static_cast<CollectionModelPrivate::ProxyItem*>(parent.internalPointer());
      return item->m_Children.size();
   }
}

int CollectionModel::columnCount (const QModelIndex& parent) const
{
   Q_UNUSED(parent)
   return 1+d_ptr->m_lExtensions.size();
}

Qt::ItemFlags CollectionModel::flags(const QModelIndex& idx) const
{
   if (!idx.isValid())
      return 0;
   CollectionModelPrivate::ProxyItem* item = static_cast<CollectionModelPrivate::ProxyItem*>(idx.internalPointer());

   //Categories can only be displayed
   if (!item->collection)
      return Qt::ItemIsEnabled;

   /*if (idx.column() > 0) {
      //Make sure the cell is disabled if the row is
      Qt::ItemFlags f = d_ptr->m_lExtensions[idx.column()-1]->flags(item->collection,idx);
      return  (((f&Qt::ItemIsEnabled)&&(!item->collection->isEnabled()))?f^Qt::ItemIsEnabled:f);
   }*/
   const bool checkable = item->collection->supportedFeatures() & (CollectionInterface::SupportedFeatures::ENABLEABLE |
   CollectionInterface::SupportedFeatures::DISABLEABLE | CollectionInterface::SupportedFeatures::MANAGEABLE  );
   return Qt::ItemIsEnabled | Qt::ItemIsSelectable | (checkable?Qt::ItemIsUserCheckable:Qt::NoItemFlags);
}

bool CollectionModel::setData (const QModelIndex& idx, const QVariant &value, int role )
{
   Q_UNUSED(idx)
   Q_UNUSED(value)
   Q_UNUSED(role)
   /*if (idx.isValid() && idx.column() > 0) {
      CollectionModelPrivate::ProxyItem* item = static_cast<CollectionModelPrivate::ProxyItem*>(idx.internalPointer());
      return (!item->collection)?false : d_ptr->m_lExtensions[idx.column()-1]->setData(item->collection,idx,value,role);
   }*/

   if (role == Qt::CheckStateRole && idx.column() == 0) {
      CollectionModelPrivate::ProxyItem* item = static_cast<CollectionModelPrivate::ProxyItem*>(idx.internalPointer());
      if (item && item->collection) {
         const bool old = item->collection->isEnabled();
         try {
            GlobalInstances::itemModelStateSerializer().setChecked(item->collection,value==Qt::Checked);
            emit dataChanged(index(idx.row(),0),index(idx.row(),columnCount()-1));

            // The state change has been cached in the interface, but is not applied yet.
            // it is necessary to keep track of the collection states that changed
            if (d_ptr->m_hPendingChanges.contains(item->collection)) {
               d_ptr->m_hPendingChanges.remove(item->collection);
               emit checkStateChanged();
            }
            else if (old != (value==Qt::Checked)) {
               d_ptr->m_hPendingChanges[item->collection] = true;
               emit checkStateChanged();
            }
            return true;
         } catch (...) {
            qDebug() << "no ItemModelStateSerializer to set collection state";
         }
      }
   }
   return false;
}

QModelIndex CollectionModel::parent( const QModelIndex& idx ) const
{
   if (idx.isValid()) {
      CollectionModelPrivate::ProxyItem* item = static_cast<CollectionModelPrivate::ProxyItem*>(idx.internalPointer());
      if (!item->parent)
         return QModelIndex();
      return createIndex(item->row,item->col,item->parent);
   }
   return QModelIndex();
}

QModelIndex CollectionModel::index( int row, int column, const QModelIndex& parent ) const
{
   if (parent.isValid() && parent.model() == this && row < rowCount(parent)) {
      CollectionModelPrivate::ProxyItem* parentItem = static_cast<CollectionModelPrivate::ProxyItem*>(parent.internalPointer());
      CollectionModelPrivate::ProxyItem* item = nullptr;
      if (row < parentItem->m_Children.size())
         item = parentItem->m_Children[row];
      else {
         return QModelIndex();
      }
      item->row    = row; //FIXME dead code?
      item->col    = column;
      return createIndex(row,column,item);
   }
   else { //Top level
      CollectionModelPrivate::ProxyItem* item = nullptr;
      if (row < d_ptr->m_lTopLevelBackends.size())
         item = d_ptr->m_lTopLevelBackends[row];
      else {
         return QModelIndex();
      }
      item->row = row; //FIXME dead code?
      item->col = column;
      return createIndex(item->row,item->col,item);
   }
}

void CollectionModelPrivate::slotUpdate()
{
   emit q_ptr->layoutChanged();
}

QVariant CollectionModel::headerData(int section, Qt::Orientation orientation, int role) const
{
   Q_UNUSED(section)
   if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
      /*if (section > 0)
         return d_ptr->m_lExtensions[section-1]->headerName();*/
      return QVariant(tr("Name"));
   }
   return QVariant();
}

bool CollectionModel::save()
{
   try {

      // Enable/disabled collections that changed
      for (QHash<CollectionInterface*,bool>::const_iterator i = d_ptr->m_hPendingChanges.begin(); i != d_ptr->m_hPendingChanges.end(); ++i) {
         i.key()->enable(GlobalInstances::itemModelStateSerializer().isChecked(i.key()));
      }

      d_ptr->m_hPendingChanges.clear();

      return GlobalInstances::itemModelStateSerializer().save();
   } catch (...) {
      qDebug() << "no ItemModelStateSerializer exists";
   }
   return false;
}

bool CollectionModel::load()
{
   try {
      return GlobalInstances::itemModelStateSerializer().load();
   } catch (...) {
      return false;
   }
}

///Return the collection at a given index
CollectionInterface* CollectionModel::collectionAt(const QModelIndex& index)
{
   if (!index.isValid())
      return nullptr;
   return static_cast<CollectionModelPrivate::ProxyItem*>(index.internalPointer())->collection;
}

void CollectionModel::addExtension(CollectionExtensionInterface* extension)
{
   emit layoutAboutToBeChanged();
   d_ptr->m_lExtensions << extension;
   connect(extension,SIGNAL(dataChanged(QModelIndex)),d_ptr.data(),SLOT(slotExtensionDataChanged(QModelIndex)));
   emit layoutChanged();
}

void CollectionModelPrivate::slotExtensionDataChanged(const QModelIndex& idx)
{
   emit q_ptr->dataChanged(idx,idx);
}

void CollectionModelPrivate::registerNew(CollectionInterface* col)
{
   if (!col)
      return;

   QMutexLocker l(&m_NewCollectionMutex);

   ProxyItem* cat = m_hCategories[col->category()];
   if (col->category().isEmpty()){
      //TODO implement a default category
   }

   if (!cat) {
      cat              = new ProxyItem();
      cat->parent      = nullptr;
      cat->row         = m_lTopLevelBackends.size();
      cat->col         = 0;
      cat->m_AltName   = col->category();
      cat->collection  = nullptr;

      m_hCategories[cat->m_AltName] = cat;
      m_hBackendsNodes[col] = cat;

      q_ptr->beginInsertRows(QModelIndex(),m_lTopLevelBackends.size(),m_lTopLevelBackends.size());
      m_lTopLevelBackends << cat;
      q_ptr->endInsertRows();
   }

   ProxyItem* item = new ProxyItem();
   const bool hasParent = col->parent();
   ProxyItem* par = hasParent?m_hBackendsNodes[col->parent()] : cat;

   item->parent = par;
   item->row    = par->m_Children.size();
   item->col    = 0;
   q_ptr->beginInsertRows(q_ptr->createIndex(par->row,par->col,par),par->m_Children.size(),par->m_Children.size());
   par->m_Children << item;
   q_ptr->endInsertRows();

   //Make sure the manageable proxy get noticed things changed
   if (col->supportedFeatures() & CollectionInterface::SupportedFeatures::MANAGEABLE) {
      item->manageableCount++;
      while(par) {
         par->manageableCount++;
         const QModelIndex& idx = q_ptr->createIndex(par->row,0,par);
         emit q_ptr->dataChanged(idx,idx);
         par = par->parent;
      }
   }

   item->collection      = col ;
   m_hBackendsNodes[col] = item;
}

bool ManageableCollectionProxy::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
   CollectionModelPrivate::ProxyItem* item = static_cast<CollectionModelPrivate::ProxyItem*>(sourceModel()->index(source_row,0,source_parent).internalPointer());
   return item ? item->manageableCount : false;
}

/**
 * Filter the CollectionModel to only keep the manageable collections. Those
 * collections can be exposed and configured in the UI
 */
QAbstractItemModel* CollectionModel::manageableCollections() const
{
   if (!d_ptr->m_pManageableProxy)
      d_ptr->m_pManageableProxy = new ManageableCollectionProxy(const_cast<CollectionModel*>(this));
   return d_ptr->m_pManageableProxy;
}

#include <collectionmodel.moc>
