/****************************************************************************
 *   Copyright (C) 2013-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 "categorizedcontactmodel.h"

//Qt
#include <QtCore/QDebug>
#include <QtCore/QDate>
#include <QtCore/QMimeData>
#include <QtCore/QCoreApplication>
#include <QtCore/QSortFilterProxyModel>

//Ring
#include "callmodel.h"
#include "categorizedhistorymodel.h"
#include "contactmethod.h"
#include "phonedirectorymodel.h"
#include "historytimecategorymodel.h"
#include "person.h"
#include "uri.h"
#include "mime.h"
#include "personmodel.h"
#include "individual.h"
#include "private/sortproxies.h"

class ContactTreeNode;

class ContactTreeNode final
{
public:
   friend class CategorizedContactModel;
   friend class CategorizedContactModelPrivate;
   friend class ContactTreeBinder;

   enum class NodeType {
      PERSON       ,
      CONTACTMETHOD,
      CATEGORY     ,
   };

   //Constructor
   ContactTreeNode( const Person* ct    , CategorizedContactModel* parent);
   ContactTreeNode( ContactMethod* cm   , CategorizedContactModel* parent);
   ContactTreeNode( const QString& name , CategorizedContactModel* parent);
   virtual ~ContactTreeNode();

   //Attributes
   const Person*             m_pContact      ;
   ContactMethod*            m_pContactMethod;
   uint                      m_Index         ;
   QString                   m_Name          ;
   NodeType                  m_Type          ;
   QVector<ContactTreeNode*> m_lChildren     ;
   CategorizedContactModel*  m_pModel        ;

   //Setter
   inline void setParent (ContactTreeNode* n);
   inline void setVisible( bool visible     );

   //Helpers
   void slotChanged                    ();
   void slotContactMethodsChanged      ();
   void slotContactMethodsAboutToChange();

private:
   ContactTreeNode*                 m_pParent       ;
   bool                             m_Visible       ;
   uint                             m_VisibleCounter;
   QVector<QMetaObject::Connection> m_lConections   ;
};

class CategorizedContactModelPrivate final : public QObject
{
   Q_OBJECT
public:
   CategorizedContactModelPrivate(CategorizedContactModel* parent);

   //Helpers
   QString category(const Person* ct) const;

   //Attributes
   QHash<Person*, time_t>               m_hContactByDate   ;
   QVector<ContactTreeNode*>            m_lCategoryCounter ;
   QHash<QString,ContactTreeNode*>      m_hCategories      ;
   int                                  m_Role             ;
   QStringList                          m_lMimes           ;
   bool                                 m_SortAlphabetical ;
   QString                              m_DefaultCategory  ;
   bool                                 m_UnreachableHidden;
   SortingCategory::ModelTuple*         m_pSortedProxy {nullptr};
   CategorizedContactModel::SortedProxy m_pProxies         ;

   //Helper
   ContactTreeNode* getContactTopLevelItem(const QString& category);
   QModelIndex getIndex(int row, int column, ContactTreeNode* parent);
   void reloadTreeVisibility               (ContactTreeNode*);

private:
   CategorizedContactModel* q_ptr;

public Q_SLOTS:
   void reloadCategories();
   void slotContactAdded(const Person* c);
   void slotContactRemoved(const Person* c);
};

ContactTreeNode::ContactTreeNode(const Person* ct, CategorizedContactModel* parent) :
   m_pContact(ct),m_Index(-1),m_pContactMethod(nullptr),m_Type(ContactTreeNode::NodeType::PERSON),m_pParent(nullptr),m_pModel(parent),m_Visible(true),
   m_VisibleCounter(0)
{
   m_Visible = m_pContact->isActive() && ((!parent->d_ptr->m_UnreachableHidden) || m_pContact->isReachable());
   m_lConections << QObject::connect(m_pContact,&Person::changed                  ,[this](){ slotChanged                    (); });
   m_lConections << QObject::connect(m_pContact->individual(),&Individual::phoneNumbersChanged      ,[this](){ slotContactMethodsChanged      (); });
   m_lConections << QObject::connect(m_pContact->individual(),&Individual::phoneNumbersAboutToChange,[this](){ slotContactMethodsAboutToChange(); });
}

ContactTreeNode::ContactTreeNode(ContactMethod* cm, CategorizedContactModel* parent) :
   m_pContactMethod(cm),m_Index(-1),m_pContact(nullptr),m_Type(ContactTreeNode::NodeType::CONTACTMETHOD),m_pParent(nullptr),m_pModel(parent),
   m_Visible(true),m_VisibleCounter(0)
{
   m_lConections << QObject::connect(m_pContactMethod,&ContactMethod::changed,[this](){ slotChanged(); });
}

ContactTreeNode::ContactTreeNode(const QString& name, CategorizedContactModel* parent) :
   m_pContactMethod(nullptr),m_Index(-1),m_pContact(nullptr),m_Type(ContactTreeNode::NodeType::CATEGORY),m_Name(name),m_pParent(nullptr),
   m_pModel(parent),m_Visible(false),m_VisibleCounter(0)
{
}

ContactTreeNode::~ContactTreeNode()
{
   for (auto c : m_lConections) {
      QObject::disconnect(c);
   }
   for (ContactTreeNode* c : m_lChildren) {
      delete c;
   }
}

QModelIndex CategorizedContactModelPrivate::getIndex(int row, int column, ContactTreeNode* parent)
{
   if (column)
      return {};

   return q_ptr->createIndex(row,column,parent);
}

void ContactTreeNode::slotChanged()
{
   const QModelIndex self = m_pModel->d_ptr->getIndex(m_Index,0,this);

   if (!self.isValid()) return;

   ContactTreeNode *n = static_cast<ContactTreeNode*>(self.internalPointer());
   if (n->m_Type == ContactTreeNode::NodeType::PERSON)
      n->m_Visible = n->m_pContact && n->m_pContact->isActive();
   emit m_pModel->dataChanged(self,self);

   if (n->m_lChildren.isEmpty())
       return;

   const auto tl = m_pModel->index(0,0,self);
   const auto br = m_pModel->index(n->m_lChildren.size()-1, 0, self);

   Q_ASSERT(tl.isValid() && br.isValid());

   emit m_pModel->dataChanged(tl, br);
}

void ContactTreeNode::slotContactMethodsChanged()
{
   const QModelIndex idx = m_pModel->d_ptr->getIndex(m_Index,0,this);

   const auto cms = m_pContact->individual()->phoneNumbers();

   //After discussion, it was decided that contacts with only 1 phone number should
   //be handled differently and the additional complexity isn't worth it
   if (cms.size() > 1) {
      m_pModel->beginInsertRows(idx,0, cms.size()-1);

      for (int i = 0; i < cms.size(); ++i) {
         ContactTreeNode* n2 = new ContactTreeNode(cms[i],m_pModel);
         n2->m_Index = m_lChildren.size();
         n2->setParent(this);
         m_lChildren << n2;
      }

      m_pModel->endInsertRows();
   }
   emit m_pModel->dataChanged(idx,idx);
}

void ContactTreeNode::slotContactMethodsAboutToChange()
{
   const QModelIndex idx = m_pModel->d_ptr->getIndex(m_Index,0,this);

   if (m_lChildren.size() > 0) {
      m_pModel->beginRemoveRows(idx,0,m_lChildren.size()-1);

      while (m_lChildren.size()) {
         auto node = m_lChildren.at(0);
         m_lChildren.removeAt(0);
         delete node;
      }

      m_pModel->endRemoveRows();
   }
}

void ContactTreeNode::setParent(ContactTreeNode* p)
{
   if (p && p != m_pParent && p->m_Type == NodeType::CATEGORY) {
      p->m_VisibleCounter += m_Visible ? 1 : 0;
      const bool hasChanged = p->m_Visible != (p->m_VisibleCounter > 0);
      p->m_Visible = p->m_VisibleCounter > 0;
      if (hasChanged) {
         const QModelIndex idx = m_pModel->index(p->m_Index,0);
         emit m_pModel->dataChanged(idx,idx);
      }
   }
   m_pParent = p;
}

void ContactTreeNode::setVisible(bool v)
{
   if (v != m_Visible) {
      if (m_pParent) {
         m_pParent->m_VisibleCounter += v ? 1 : -1;
         m_pParent->m_Visible = m_pParent->m_VisibleCounter > 0;
      }
      m_Visible = v;
      const QModelIndex idx = m_pModel->index(m_Index,0,m_pParent ?
         m_pModel->index(m_pParent->m_Index,0) : QModelIndex()
      );
      emit m_pModel->dataChanged(idx,idx);
   }
}

CategorizedContactModelPrivate::CategorizedContactModelPrivate(CategorizedContactModel* parent) : QObject(parent), q_ptr(parent),
m_lCategoryCounter(),m_Role(Qt::DisplayRole),m_SortAlphabetical(true),m_UnreachableHidden(false),m_pSortedProxy(nullptr)
{

}

//
CategorizedContactModel::CategorizedContactModel(int role) : QAbstractItemModel(QCoreApplication::instance()),d_ptr(new CategorizedContactModelPrivate(this))
{
   setObjectName(QStringLiteral("CategorizedContactModel"));
   d_ptr->m_Role    = role;
   d_ptr->m_lCategoryCounter.reserve(32);
   d_ptr->m_lMimes << RingMimes::PLAIN_TEXT << RingMimes::PHONENUMBER;

   connect(&PersonModel::instance(),&PersonModel::newPersonAdded,d_ptr.data(),&CategorizedContactModelPrivate::slotContactAdded);
   connect(&PersonModel::instance(),&PersonModel::personRemoved,d_ptr.data(),&CategorizedContactModelPrivate::slotContactRemoved);

   for(int i=0; i < PersonModel::instance().rowCount();i++) {
      Person* p = qvariant_cast<Person*>(PersonModel::instance().index(i,0).data((int)Ring::Role::Object));
      d_ptr->slotContactAdded(p);
   }

}

CategorizedContactModel::~CategorizedContactModel()
{
   foreach(ContactTreeNode* item,d_ptr->m_lCategoryCounter) {
      delete item;
   }

   if (d_ptr->m_pSortedProxy)
      delete d_ptr->m_pSortedProxy;
}

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

QHash<int,QByteArray> CategorizedContactModel::roleNames() const
{
    return PersonModel::instance().roleNames();
}

ContactTreeNode* CategorizedContactModelPrivate::getContactTopLevelItem(const QString& category)
{
   if (!m_hCategories[category]) {
      ContactTreeNode* item = new ContactTreeNode(category,q_ptr);
      m_hCategories[category] = item;
      item->m_Index = m_lCategoryCounter.size();

      q_ptr->beginInsertRows({}, m_lCategoryCounter.size(), m_lCategoryCounter.size());
      m_lCategoryCounter << item;
      q_ptr->endInsertRows();

   }
   ContactTreeNode* item = m_hCategories[category];
   return item;
}

void CategorizedContactModelPrivate::reloadCategories()
{
   //TODO This could be optimized
   q_ptr->beginResetModel();
   m_hCategories.clear();
   for (auto item : qAsConst(m_lCategoryCounter))
      delete item;
   m_lCategoryCounter.clear();

   q_ptr->endResetModel();

   for (int i=0; i < PersonModel::instance().rowCount();i++) {
      Person* cont = qvariant_cast<Person*>(PersonModel::instance().index(i,0).data((int)Ring::Role::Object));
      slotContactAdded(cont);
   }
}

void CategorizedContactModelPrivate::slotContactRemoved(const Person* c) {
    Q_UNUSED(c)

    reloadCategories();
}

void CategorizedContactModelPrivate::slotContactAdded(const Person* c)
{
   if (!c) return;

   const QString val = category(c);
   ContactTreeNode* item = getContactTopLevelItem(val);
   ContactTreeNode* contactNode = new ContactTreeNode(c,q_ptr);
   contactNode->setParent(item);
   contactNode->m_Index = item->m_lChildren.size();
   //emit layoutAboutToBeChanged();
   q_ptr->beginInsertRows(q_ptr->index(item->m_Index,0,QModelIndex()),item->m_lChildren.size(),item->m_lChildren.size());

   item->m_lChildren << contactNode;

   q_ptr->endInsertRows();

   reloadTreeVisibility(item);

   if (c->individual()->phoneNumbers().size() > 1) {
      const auto parentIdx = q_ptr->createIndex(contactNode->m_Index,0,contactNode);
      const int first(contactNode->m_lChildren.size()), last(contactNode->m_lChildren.size() + c->individual()->phoneNumbers().size() - 1);
      q_ptr->beginInsertRows(parentIdx, first, last);

      const auto cms = c->individual()->phoneNumbers();
      for (auto m : qAsConst(cms)) {
         //TODO check if this can be merged with slotContactMethodCountChanged
         ContactTreeNode* n2 = new ContactTreeNode(m,q_ptr);
         n2->m_Index = contactNode->m_lChildren.size();

         // Set it first to avoid `dataChanged()` being emitted within the critical section
         n2->m_pParent = contactNode;

         contactNode->m_lChildren << n2;
      }

      q_ptr->endInsertRows();

      for (int i = first; i <= last; i++) {
         contactNode->m_lChildren[i]->setParent(contactNode);
      }
   }

   //emit layoutChanged();
}

bool CategorizedContactModel::setData( const QModelIndex& index, const QVariant &value, int role)
{
   Q_UNUSED(index)
   Q_UNUSED(value)
   Q_UNUSED(role )

   return false;
}

QVariant CategorizedContactModel::data( const QModelIndex& index, int role) const
{
   if (!index.isValid())
      return QVariant();

   ContactTreeNode* modelItem = (ContactTreeNode*)index.internalPointer();
   switch (modelItem->m_Type) {
      case ContactTreeNode::NodeType::CATEGORY:
      switch (role) {
         case Qt::DisplayRole:
            return static_cast<const ContactTreeNode*>(modelItem)->m_Name;
         case (int)Person::Role::IndexedLastUsed:
            return index.child(0,0).data((int)Person::Role::IndexedLastUsed);
         default:
            break;
      }
      break;
   case ContactTreeNode::NodeType::PERSON:
      return modelItem->m_pContact->roleData(role);
   case ContactTreeNode::NodeType::CONTACTMETHOD: /* && (role == Qt::DisplayRole)) {*/
      return modelItem->m_pContactMethod->roleData(role);
   }
   return QVariant();
}

QVariant CategorizedContactModel::headerData(int section, Qt::Orientation orientation, int role) const
{
   Q_UNUSED(section)
   if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
      return QVariant(tr("Contacts"));
   return QVariant();
}

bool CategorizedContactModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
   Q_UNUSED( action )
   setData(parent,-1,static_cast<int>(Call::Role::DropState));
   if (data->hasFormat(RingMimes::CALLID)) {
      const QByteArray encodedCallId = data->data( RingMimes::CALLID    );
      const QModelIndex targetIdx    = index   ( row,column,parent );
      Call* call                     = CallModel::instance().fromMime ( encodedCallId        );
      if (call && targetIdx.isValid()) {
         ContactTreeNode* modelItem = (ContactTreeNode*)targetIdx.internalPointer();
         switch (modelItem->m_Type) {
            case ContactTreeNode::NodeType::PERSON: {
               const Person* ct = modelItem->m_pContact;
               if (ct) {
                  switch(ct->individual()->phoneNumbers().size()) {
                     case 0: //Do nothing when there is no phone numbers
                        return false;
                     case 1: //Call when there is one
                        CallModel::instance().transfer(call,ct->individual()->phoneNumbers().first());
                        break;
                     default:
                        //TODO
                        break;
                  };
               }
            } break;
            case ContactTreeNode::NodeType::CONTACTMETHOD: {
               const ContactMethod* nb  = modelItem->m_pContactMethod;
               if (nb) {
                  call->setTransferNumber(nb->uri());
                  CallModel::instance().transfer(call,nb);
               }
            } break;
            case ContactTreeNode::NodeType::CATEGORY:
               break;
         }
      }
   }
   return false;
}


int CategorizedContactModel::rowCount( const QModelIndex& parent ) const
{
   if (!parent.isValid() || !parent.internalPointer())
      return d_ptr->m_lCategoryCounter.size();
   const ContactTreeNode* parentNode = static_cast<ContactTreeNode*>(parent.internalPointer());

   if (parentNode)
      return parentNode->m_lChildren.size();

   return 0;
}

Qt::ItemFlags CategorizedContactModel::flags( const QModelIndex& index ) const
{
   if (!index.isValid())
      return Qt::NoItemFlags;

   const ContactTreeNode* modelNode = static_cast<ContactTreeNode*>(index.internalPointer());

   return (modelNode->m_Visible) ? Qt::ItemIsEnabled | Qt::ItemIsSelectable | (
      modelNode->m_pParent? (Qt::ItemIsDragEnabled|Qt::ItemIsDropEnabled) : Qt::NoItemFlags
   ) : Qt::NoItemFlags;
}

int CategorizedContactModel::columnCount ( const QModelIndex& parent) const
{
   Q_UNUSED(parent)
   return 1;
}

QModelIndex CategorizedContactModel::parent( const QModelIndex& index) const
{
   if (!index.isValid() || !index.internalPointer())
      return {};

   const ContactTreeNode* modelItem = static_cast<ContactTreeNode*>(index.internalPointer());

   if (modelItem && modelItem->m_pParent)
      return createIndex(modelItem->m_pParent->m_Index,0,modelItem->m_pParent);

   return {};
}

QModelIndex CategorizedContactModel::index( int row, int column, const QModelIndex& parent) const
{
   if (column || row == -1)
      return {};

   if ((!parent.isValid()) && row < d_ptr->m_lCategoryCounter.size())
      return createIndex(row,column,d_ptr->m_lCategoryCounter[row]);

   if (!parent.isValid())
       return {};

   ContactTreeNode* parentNode = static_cast<ContactTreeNode*>(parent.internalPointer());

   if (parentNode && row < parentNode->m_lChildren.size())
      return createIndex(row,column,parentNode->m_lChildren[row]);

   return {};
}

QStringList CategorizedContactModel::mimeTypes() const
{
   return d_ptr->m_lMimes;
}

QMimeData* CategorizedContactModel::mimeData(const QModelIndexList &indexes) const
{
   QMimeData *mimeData = new QMimeData();
   foreach (const QModelIndex &index, indexes) {
      if (index.isValid()) {
         const ContactTreeNode* modelItem = static_cast<ContactTreeNode*>(index.internalPointer());
         switch(modelItem->m_Type) {
            case ContactTreeNode::NodeType::PERSON: {
               //Contact
               const Person* ct = modelItem->m_pContact;
               if (ct) {
                  if (ct->individual()->phoneNumbers().size() == 1) {
                     mimeData->setData(RingMimes::PHONENUMBER , ct->individual()->phoneNumbers().first()->toHash().toUtf8());
                  }
                  mimeData->setData(RingMimes::CONTACT , ct->uid());
               }
               return mimeData;
               }
            case ContactTreeNode::NodeType::CONTACTMETHOD: {
               //Phone number
               const QString text = data(index, Qt::DisplayRole).toString();
               const ContactTreeNode* n = static_cast<ContactTreeNode*>(index.internalPointer());
               if (n->m_pContactMethod) {
                  mimeData->setData(RingMimes::PLAIN_TEXT , text.toUtf8());
                  mimeData->setData(RingMimes::PHONENUMBER, n->m_pContactMethod->toHash().toUtf8());
                  return mimeData;
               }

               } break;
            case ContactTreeNode::NodeType::CATEGORY:
               return nullptr;
         };
      }
   }
   return mimeData;
}

///Return valid payload types
int CategorizedContactModel::acceptedPayloadTypes()
{
   return CallModel::DropPayloadType::CALL;
}


/*****************************************************************************
 *                                                                           *
 *                                  Helpers                                  *
 *                                                                           *
 ****************************************************************************/


QString CategorizedContactModelPrivate::category(const Person* ct) const {
   if (!ct)
      return QString();

   QString cat = ct->roleData(m_Role).toString();

   if (cat.size() && m_SortAlphabetical)
      cat = cat[0].toUpper();
   else if (!cat.size())
      cat = m_DefaultCategory;

   return cat;
}

void CategorizedContactModel::setRole(int role)
{
   if (role != d_ptr->m_Role) {
      d_ptr->m_Role = role;
      d_ptr->reloadCategories();
   }
}

void CategorizedContactModel::setSortAlphabetical(bool alpha)
{
   d_ptr->m_SortAlphabetical = alpha;
}

bool CategorizedContactModel::isSortAlphabetical() const
{
   return d_ptr->m_SortAlphabetical;
}

void CategorizedContactModel::setDefaultCategory(const QString& cat)
{
   d_ptr->m_DefaultCategory = cat;
}

QString CategorizedContactModel::defaultCategory() const
{
   return d_ptr->m_DefaultCategory;
}

bool CategorizedContactModel::areUnreachableHidden() const
{
   return d_ptr->m_UnreachableHidden;
}

/**
 * Show/hide contacts that are currently unreachable
 *
 * @warning This method will reloaded person reachability status,
 * this require large amount of CPU when many contacts are present
 */
void CategorizedContactModel::setUnreachableHidden(bool val)
{
   if (d_ptr->m_UnreachableHidden != val) {
      d_ptr->m_UnreachableHidden = val;
      d_ptr->reloadTreeVisibility(nullptr);
   }
}

void CategorizedContactModelPrivate::reloadTreeVisibility( ContactTreeNode* node )
{
   if (!node) {
      for(ContactTreeNode* n : m_hCategories)
         reloadTreeVisibility(n);
      return;
   }

   switch(node->m_Type) {
      case ContactTreeNode::NodeType::PERSON       :
         node->setVisible(node->m_pContact->isActive() && ((!m_UnreachableHidden) || node->m_pContact->isReachable()));
         break;
      case ContactTreeNode::NodeType::CONTACTMETHOD:
         //Nothing to do
         break;
      case ContactTreeNode::NodeType::CATEGORY     :
         for (ContactTreeNode* n : node->m_lChildren)
            reloadTreeVisibility(n);
         break;
   };
}

QAbstractItemModel* CategorizedContactModel::SortedProxy::model() const
{
    if (!CategorizedContactModel::instance().d_ptr->m_pSortedProxy)
        CategorizedContactModel::instance().d_ptr->m_pSortedProxy = SortingCategory::getContactProxy();

    return CategorizedContactModel::instance().d_ptr->m_pSortedProxy->model;
}

QAbstractItemModel* CategorizedContactModel::SortedProxy::categoryModel() const
{
    if (!CategorizedContactModel::instance().d_ptr->m_pSortedProxy)
        CategorizedContactModel::instance().d_ptr->m_pSortedProxy = SortingCategory::getContactProxy();

    return CategorizedContactModel::instance().d_ptr->m_pSortedProxy->categories;
}

QItemSelectionModel* CategorizedContactModel::SortedProxy::categorySelectionModel() const
{
    if (!CategorizedContactModel::instance().d_ptr->m_pSortedProxy)
        CategorizedContactModel::instance().d_ptr->m_pSortedProxy = SortingCategory::getContactProxy();

    return CategorizedContactModel::instance().d_ptr->m_pSortedProxy->selectionModel;
}

CategorizedContactModel::SortedProxy& CategorizedContactModel::SortedProxy::instance()
{
    return CategorizedContactModel::instance().d_ptr->m_pProxies;
}

#include <categorizedcontactmodel.moc>
