/************************************************************************************
 *   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 Lesser General Public               *
 *   License along with this library; if not, write to the Free Software            *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA *
 ***********************************************************************************/
#include "fallbackpersoncollection.h"

//Qt
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QHash>
#include <QtCore/QTimer>
#include <QtCore/QUrl>
#include <QtCore/QCryptographicHash>
#include <QtCore/QStandardPaths>

//Ring
#include "person.h"
#include "personmodel.h"
#include "private/vcardutils.h"
#include "contactmethod.h"
#include "collectioneditor.h"
#include "globalinstances.h"
#include "individual.h"
#include "interfaces/pixmapmanipulatori.h"
#include "interfaces/actionextenderi.h"
#include "interfaces/itemmodelstateserializeri.h"
#include "private/threadworker.h"

class FallbackPersonBackendEditor final : public CollectionEditor<Person>
{
public:
   FallbackPersonBackendEditor(CollectionMediator<Person>* m, const QString& path) : CollectionEditor<Person>(m),m_Path(path) {}
   virtual bool save       ( const Person* item ) override;
   virtual bool remove     ( const Person* item ) override;
   virtual bool edit       ( Person*       item ) override;
   virtual bool addNew     ( Person*       item ) override;
   virtual bool addExisting( const Person* item ) override;

   QVector<Person*>             m_lItems;
   QString                      m_Path  ;
   QHash<const Person*,QString> m_hPaths;

private:
   virtual QVector<Person*> items() const override;
};

class FallbackPersonCollectionPrivate final : public QObject
{
   Q_OBJECT
public:
   FallbackPersonCollectionPrivate(FallbackPersonCollection* parent, CollectionMediator<Person>* mediator, const QString& path);
   CollectionMediator<Person>*  m_pMediator   ;
   QString                      m_Path        ;
   QString                      m_Name        ;
   bool                         m_Async {true};

   FallbackPersonCollection* q_ptr;

public Q_SLOTS:
   void loadAsync();
};

FallbackPersonCollectionPrivate::FallbackPersonCollectionPrivate(FallbackPersonCollection* parent, CollectionMediator<Person>* mediator, const QString& path) : q_ptr(parent), m_pMediator(mediator), m_Path(path)
{
   //Default to somewhere ~/.local/share
   if (m_Path.isEmpty()) {
      m_Path = (QStandardPaths::writableLocation(QStandardPaths::DataLocation)) + "/vCard/";
      static_cast<FallbackPersonBackendEditor*>(q_ptr->editor<Person>())->m_Path = m_Path;
   }
   //Make sure the directory exists so that saving new contacts there doesn't fail
   if (!QDir().mkpath(m_Path))
      qWarning() << "cannot create path for fallbackcollection: " << m_Path;

   m_Name = path.split('/').last();
   if (m_Name.size())
      m_Name[0] = m_Name[0].toUpper();
   else
      m_Name = QLatin1String("vCard");
}

FallbackPersonCollection::FallbackPersonCollection(CollectionMediator<Person>* mediator, const QString& path, bool async, FallbackPersonCollection* parent) :
CollectionInterface(new FallbackPersonBackendEditor(mediator,path),parent),d_ptr(new FallbackPersonCollectionPrivate(this,mediator,path))
{
    d_ptr->m_Async = async;
}

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

bool FallbackPersonBackendEditor::save(const Person* item)
{
   if (!item)
      return false;

   //An UID is required
   if (item->uid().isEmpty()) {
      QCryptographicHash hash(QCryptographicHash::Sha1);
      const auto cms = item->individual()->phoneNumbers();
      for (ContactMethod* n : qAsConst(cms))
         hash.addData(n->uri().toLatin1());
      hash.addData(item->formattedName().toLatin1());
      QByteArray random;

      for (int i=0;i<5;i++)
         random.append(QChar((char)(rand()%255)));

      hash.addData(random);

      const_cast<Person*>(item)->setUid(hash.result().toHex());
   }

   const QString path = m_Path+'/'+item->uid()+".vcf";

   QFile file(path);

   if (Q_UNLIKELY(!file.open(QIODevice::WriteOnly))) {
      qWarning() << "Can't write to" << path;
      return false;
   }

   file.write(item->toVCard({}));
   file.close();
   return true;
}

bool FallbackPersonBackendEditor::remove(const Person* item)
{
   if (!item)
      return false;

   QString path = m_hPaths[item];

   if (path.isEmpty())
      path = m_Path+'/'+item->uid()+".vcf";

   bool ret = QFile::remove(path);

   if (ret) {
      ret &= mediator()->removeItem(item);
   }
   else
      qWarning() << "Failed to delete" << path;

   return ret;
}

bool FallbackPersonBackendEditor::edit( Person* item)
{
   GlobalInstances::actionExtender().editPerson(item);
   return true;
}

bool FallbackPersonBackendEditor::addNew( Person* item)
{
   item->ensureUid();
   bool ret = save(item);

   if (ret) {
      addExisting(item);
   }

   return ret;
}

bool FallbackPersonBackendEditor::addExisting(const Person* item)
{
   m_lItems << const_cast<Person*>(item);
   mediator()->addItem(item);
   return true;
}

QVector<Person*> FallbackPersonBackendEditor::items() const
{
   return m_lItems;
}

QString FallbackPersonCollection::name () const
{
   return d_ptr->m_Name;
}

QString FallbackPersonCollection::category () const
{
   return QObject::tr("Contact");
}

QVariant FallbackPersonCollection::icon() const
{
   return GlobalInstances::pixmapManipulator().collectionIcon(this,Interfaces::PixmapManipulatorI::CollectionIconHint::CONTACT);
}

bool FallbackPersonCollection::isEnabled() const
{
    /* if ItemModelStateSerializer exists, check if collectin is enabled, else assume it is */
    try {
        return GlobalInstances::itemModelStateSerializer().isChecked(this);
    } catch (...) {
        return true;
     }
}

bool FallbackPersonCollection::load()
{
   auto l = [this]() {
      bool ok;
      Q_UNUSED(ok)
      const QList< Person* > ret =  VCardUtils::loadDir(QUrl(d_ptr->m_Path),ok,static_cast<FallbackPersonBackendEditor*>(editor<Person>())->m_hPaths);
      for(Person* p : ret) {
         p->setCollection(this);
         editor<Person>()->addExisting(p);
      }
   };

   if (d_ptr->m_Async)
      new ThreadWorker(l);
   else
      l();

   //Add all sub directories as new backends
   QTimer::singleShot(0,d_ptr,SLOT(loadAsync()));

   return true;
}

bool FallbackPersonCollection::reload()
{
   return false;
}

FlagPack<CollectionInterface::SupportedFeatures> FallbackPersonCollection::supportedFeatures() const
{
   return
      CollectionInterface::SupportedFeatures::NONE       |
      CollectionInterface::SupportedFeatures::LOAD       |
      CollectionInterface::SupportedFeatures::CLEAR      |
      CollectionInterface::SupportedFeatures::MANAGEABLE |
      CollectionInterface::SupportedFeatures::REMOVE     |
      CollectionInterface::SupportedFeatures::EDIT       |
      CollectionInterface::SupportedFeatures::SAVE       |
      CollectionInterface::SupportedFeatures::ADD        ;
}

bool FallbackPersonCollection::clear()
{
   QDir dir(d_ptr->m_Path);
   for (const QString& file : dir.entryList({"*.vcf"},QDir::Files))
      dir.remove(file);
   return true;
}

QByteArray FallbackPersonCollection::id() const
{
   return "fpc2"+d_ptr->m_Path.toLatin1();
}

QString FallbackPersonCollection::path() const
{
    return d_ptr->m_Path.toLatin1();
}

void FallbackPersonCollectionPrivate::loadAsync()
{
   QDir d(m_Path);
   for (const QString& dir : d.entryList(QDir::AllDirs)) {
      if (dir != QString('.') && dir != QLatin1String("..")) {
         CollectionInterface* col = PersonModel::instance().addCollection<FallbackPersonCollection,QString,FallbackPersonCollection*>(m_Path+'/'+dir,q_ptr);
         if (col->isEnabled()) {
            col->load();
         }
      }
   }
}

#include "fallbackpersoncollection.moc"
