/************************************************************************************
 *   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 "localhistorycollection.h"

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

//Ring
#include "call.h"
#include "media/media.h"
#include "media/recording.h"
#include "media/avrecording.h"
#include "account.h"
#include "person.h"
#include "certificate.h"
#include "contactmethod.h"
#include "categorizedhistorymodel.h"
#include "globalinstances.h"
#include "interfaces/pixmapmanipulatori.h"

class LocalHistoryEditor final : public CollectionEditor<Call>
{
public:
   LocalHistoryEditor(CollectionMediator<Call>* m, LocalHistoryCollection* parent);
   virtual bool save       ( const Call* item ) override;
   virtual bool remove     ( const Call* item ) override;
   virtual bool edit       ( Call*       item ) override;
   virtual bool addNew     ( Call*       item ) override;
   virtual bool addExisting( const Call* item ) override;

   //Attributes
   QVector<Call*> m_lItems;
   QVector< std::function< void(LocalHistoryCollection*) > > m_lCallbacks;
   bool m_IsLoaded {false};
   LocalHistoryCollection* m_pCollection;

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

   //Helpers
   void saveCall(QTextStream& stream, const Call* call);
   bool regenFile(const Call* toIgnore);
};

LocalHistoryEditor::LocalHistoryEditor(CollectionMediator<Call>* m, LocalHistoryCollection* parent) :
CollectionEditor<Call>(m),m_pCollection(parent)
{

}

LocalHistoryCollection::LocalHistoryCollection(CollectionMediator<Call>* mediator) :
CollectionInterface(new LocalHistoryEditor(mediator,this))
{
//    setObjectName("LocalHistoryCollection");
}

LocalHistoryCollection::~LocalHistoryCollection()
{

}

void LocalHistoryEditor::saveCall(QTextStream& stream, const Call* call)
{
   if (!CategorizedHistoryModel::instance().isHistoryEnabled())
      return;

   stream.setCodec("UTF-8");
   const QString direction = (call->direction()==Call::Direction::INCOMING)?
      Call::HistoryStateName::INCOMING : Call::HistoryStateName::OUTGOING;

   const Account* a = call->account();

   if (a && a->protocol() == Account::Protocol::RING) {
       URI uri = call->peerContactMethod()->uri().format(URI::Section::SCHEME | URI::Section::USER_INFO | URI::Section::HOSTNAME | URI::Section::PORT);
       Q_ASSERT(uri.schemeType() == URI::SchemeType::RING);
   }

   stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::CALLID          ).arg(call->historyId()                       );
   stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::TIMESTAMP_START ).arg(call->startTimeStamp()                  );
   stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::TIMESTAMP_STOP  ).arg(call->stopTimeStamp()                   );
   stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::ACCOUNT_ID      ).arg(a?QString(a->id()):QLatin1String("")                   );
   stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::DISPLAY_NAME    ).arg(call->peerName()                        );
   stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::PEER_NUMBER     ).arg(call->peerContactMethod()->uri().full() );
   stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::DIRECTION       ).arg(direction                               );
   stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::MISSED          ).arg(call->isMissed()                        );
   stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::CONTACT_USED    ).arg(false                                   );//TODO

   //TODO handle more than one recording
   if (call->hasRecording(Media::Media::Type::AUDIO,Media::Media::Direction::IN)) {
      stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::RECORDING_PATH  ).arg(((Media::AVRecording*)call->recordings(Media::Media::Type::AUDIO,Media::Media::Direction::IN)[0])->path().path());
   }

   if (call->peerContactMethod()->contact()) {
      stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::CONTACT_UID  ).arg(
         QString(call->peerContactMethod()->contact()->uid())
      );
   }
   if (call->certificate())
      stream << QStringLiteral("%1=%2\n").arg(Call::HistoryMapFields::CERT_PATH).arg(call->certificate()->path());
   stream << "\n";
   stream.flush();
}

bool LocalHistoryEditor::regenFile(const Call* toIgnore)
{
   QDir dir(QString('/'));
   dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + QString());

   QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') +"history.ini");
   if ( file.open(QIODevice::WriteOnly | QIODevice::Text) ) {
      QTextStream stream(&file);
      for (const Call* c : CategorizedHistoryModel::instance().getHistoryCalls()) {
         if (c != toIgnore)
            saveCall(stream, c);
      }
      file.close();
      return true;
   }
   return false;
}

bool LocalHistoryEditor::save(const Call* call)
{
   if (call->collection()->editor<Call>() != this)
      return addNew(const_cast<Call*>(call));

   return regenFile(nullptr);
}

bool LocalHistoryEditor::remove(const Call* item)
{
   if (regenFile(item)) {
      mediator()->removeItem(item);
      return true;
   }
   return false;
}

bool LocalHistoryEditor::edit( Call* item)
{
   Q_UNUSED(item)
   return false;
}

bool LocalHistoryEditor::addNew( Call* call)
{
   QDir dir(QString('/'));
   dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + QString());

   if ((call->collection() && call->collection()->editor<Call>() == this)  || call->historyId().isEmpty()) return false;
   //TODO support \r and \n\r end of line
   QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/')+"history.ini");

   if ( file.open(QIODevice::Append | QIODevice::Text) ) {
      QTextStream streamFileOut(&file);
      saveCall(streamFileOut, call);
      file.close();

      const_cast<Call*>(call)->setCollection(m_pCollection);
      addExisting(call);
      return true;
   }
   else
      qWarning() << "Unable to save history";
   return false;
}

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

QVector<Call*> LocalHistoryEditor::items() const
{
   return m_lItems;
}

QString LocalHistoryCollection::name () const
{
   return QObject::tr("Legacy history");
}

QString LocalHistoryCollection::category () const
{
   return QObject::tr("History");
}

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

bool LocalHistoryCollection::isEnabled() const
{
   return true;
}

bool LocalHistoryCollection::load()
{
   if (!CategorizedHistoryModel::instance().isHistoryEnabled())
      return false;

   QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') +"history.ini");
   if ( file.open(QIODevice::ReadOnly | QIODevice::Text) ) {
      QMap<QStringRef,QStringRef> hc;

      // QByteArray -> QString since there is no QByteArrayRef and the
      // data is UTF anyway
      const QString data = file.readAll();
      file.close();

      //TODO support windows again
      QVector<QStringRef> lines = QStringRef(&data).trimmed().split('\n');

      const bool      isLimited = CategorizedHistoryModel::instance().isHistoryLimited();
      const long long dayLimit  = CategorizedHistoryModel::instance().historyLimit() * 24 * 3600;

      time_t now = time(0); // get time now

      for (const QStringRef& line : qAsConst(lines)) {
         //The item is complete
         if ((line.isEmpty() || !line.size()) && hc.size()) {
            Call* pastCall = Call::buildHistoryCall(hc);

            if (!pastCall)
               continue;

            if (!isLimited || ( (now - pastCall->startTimeStamp()) < dayLimit) ) {
               pastCall->setCollection(this);
               editor<Call>()->addExisting(pastCall);
            }

            hc.clear();
         }
         // Add to the current set
         else {
            const int idx = line.indexOf('=');
            if (idx >= 0)
               hc[line.left(idx)] = line.right(line.size()-idx-1);
         }
      }

      for (auto cb :  static_cast<LocalHistoryEditor*>(editor<Call>())->m_lCallbacks) {
          cb(this);
      }

      static_cast<LocalHistoryEditor*>(editor<Call>())->m_IsLoaded = true;

      return true;
   }
   else
      qWarning() << "History doesn't exist or is not readable";
   return false;
}

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

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

bool LocalHistoryCollection::clear()
{
   QFile::remove(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') +"history.ini");
   return true;
}

QByteArray LocalHistoryCollection::id() const
{
   return "mhb";
}

void LocalHistoryCollection::addCompletionCallback(std::function<void(LocalHistoryCollection*)> cb)
{
    if (static_cast<LocalHistoryEditor*>(editor<Call>())->m_IsLoaded)
        cb(this);
    else
        static_cast<LocalHistoryEditor*>(editor<Call>())->m_lCallbacks << cb;
}
