/*
*  Naamari - an AES/SHA-based manual file crypto-synchronizer for cloud transfers
*  Copyright (c) 2015-2017 Sunkware
*  Key FP = 6B6D C8E9 3438 6E9C 3D97  56E5 2CE9 A476 99EF 28F6
*
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  This program 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 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/>.
*
*  ----------------------------------------------------------------
*  WWW:       sunkware.org
*  E-mail:    sunkware@gmail.com
*  ----------------------------------------------------------------
*/

#include "actioner.h"

namespace cryptrav
{


Actioner::Actioner(ContextGlob* pContext, StateGlob* pState)
{
 contextP = pContext;
 stateP = pState;
}


int            Actioner::idle(QString pathInNominis, QString objName)
{
 Pather        pather(contextP);

 Translator    translator(contextP, stateP);


 QFile         nomen(pather.getNominisAbs(pathInNominis) + objName);

 if ( !nomen.exists() )
  return CTERR_OBJECT_NOT_FOUND;

 QFileInfo     nomenInfo(nomen);



 if ( nomenInfo.isDir() ) // Dir. Additional checks and recursion for list of its elements follow...
  {
   PRINT::putLine("[" + nomenInfo.fileName() + "]...");

   QFileInfoList entryInfoList = QDir(pather.getNominisAbs(pathInNominis) + objName).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::DirsFirst);

   for (int i = 0; i < entryInfoList.count(); i++)
    {
     Actioner actioner(contextP, stateP);

     actioner.idle(pathInNominis + objName + "/", (entryInfoList.at(i)).fileName());
    }

   PRINT::putLine("...[" + nomenInfo.fileName() + "]");
  }

   else // File. Checking times and states...

  {
   PRINT::put("\"" + nomenInfo.fileName() + "\": ");

   OMEN::NomenData nomenData;
   OMEN::clearNomenData(&nomenData);


   if ( OMEN::readNomen(nomenInfo.absoluteFilePath(), &nomenData) == CTERR_OK )
    {
     QString cryptomenStr = translator.makeCryptomenStr(nomenData.randomen);


     OMEN::CryptomenData cryptomenData;
     OMEN::clearCryptomenData(&cryptomenData);


     if ( OMEN::readCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK )
      {
       PRINT::put("assigning... ");

       cryptomenData.action = ACTION_NONE;

       if (OMEN::writeCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK) {

         PRINT::put("done.");

        } else PRINT::put("cannot write cryptomen.");

      } else PRINT::put("cannot read cryptomen.");


     OMEN::clearCryptomenData(&cryptomenData);

     cryptomenStr.clear();

    } else PRINT::put("cannot read nomen.");


   PRINT::LF();

   OMEN::clearNomenData(&nomenData);
  }


 return CTERR_OK;
}


int            Actioner::push(QString pathInNominis, QString objName, bool isIgnoreTime)
{
 Pather        pather(contextP);

 Translator    translator(contextP, stateP);


 QFile         nomen(pather.getNominisAbs(pathInNominis) + objName);

 if ( !nomen.exists() )
  return CTERR_OBJECT_NOT_FOUND;

 QFileInfo     nomenInfo(nomen);



 if ( nomenInfo.isDir() ) // Dir. Additional checks and recursion for list of its elements follow...
  {
   PRINT::putLine("[" + nomenInfo.fileName() + "]...");

   QFileInfoList entryInfoList = QDir(pather.getNominisAbs(pathInNominis) + objName).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::DirsFirst);

   for (int i = 0; i < entryInfoList.count(); i++)
    {
     Actioner actioner(contextP, stateP);

     actioner.push(pathInNominis + objName + "/", (entryInfoList.at(i)).fileName(), isIgnoreTime);
    }

   PRINT::putLine("...[" + nomenInfo.fileName() + "]");
  }

   else // File. Checking times and states...

  {
   PRINT::put("\"" + nomenInfo.fileName() + "\": ");

   OMEN::NomenData nomenData;
   OMEN::clearNomenData(&nomenData);


   if ( OMEN::readNomen(nomenInfo.absoluteFilePath(), &nomenData) == CTERR_OK )
    {
     QString cryptomenStr = translator.makeCryptomenStr(nomenData.randomen);


     OMEN::CryptomenData cryptomenData;
     OMEN::clearCryptomenData(&cryptomenData);


     if ( OMEN::readCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK )
      {

       if (cryptomenData.isNear)
        {
         translator.decryptTwoQwords( cryptomenData.nearSalt, &(cryptomenData.nearTime), &(cryptomenData.nearSize) );

         if (cryptomenData.isFar) {
           translator.decryptTwoQwords( cryptomenData.farSalt, &(cryptomenData.farTime), &(cryptomenData.farSize) );
          }


         if ( (!cryptomenData.isFar) || (cryptomenData.isFar && ((cryptomenData.nearTime != cryptomenData.farTime) || isIgnoreTime)) )
          {
           PRINT::put("assigning... ");

           translator.encryptTwoQwords( cryptomenData.nearSalt, &(cryptomenData.nearTime), &(cryptomenData.nearSize) );

           if (cryptomenData.isFar) {
             translator.encryptTwoQwords( cryptomenData.farSalt, &(cryptomenData.farTime), &(cryptomenData.farSize) );
            }


           cryptomenData.action = ACTION_PUSH;


           if (OMEN::writeCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK) {

             PRINT::put("done");

            } else PRINT::put("cannot write cryptomen.");

          } else PRINT::put("already far.");

        } else PRINT::put("not near.");

      } else PRINT::put("cannot read cryptomen.");


     OMEN::clearCryptomenData(&cryptomenData);

     cryptomenStr.clear();

    } else PRINT::put("cannot read nomen.");


   PRINT::LF();

   OMEN::clearNomenData(&nomenData);
  }


 return CTERR_OK;
}


int            Actioner::pull(QString pathInNominis, QString objName, bool isIgnoreTime)
{
 Pather        pather(contextP);

 Translator    translator(contextP, stateP);


 QFile         nomen(pather.getNominisAbs(pathInNominis) + objName);

 if ( !nomen.exists() )
  return CTERR_OBJECT_NOT_FOUND;

 QFileInfo     nomenInfo(nomen);



 if ( nomenInfo.isDir() ) // Dir. Additional checks and recursion for list of its elements follow...
  {
   PRINT::putLine("[" + nomenInfo.fileName() + "]...");

   QFileInfoList entryInfoList = QDir(pather.getNominisAbs(pathInNominis) + objName).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::DirsFirst);

   for (int i = 0; i < entryInfoList.count(); i++)
    {
     Actioner actioner(contextP, stateP);

     actioner.pull(pathInNominis + objName + "/", (entryInfoList.at(i)).fileName(), isIgnoreTime);
    }

   PRINT::putLine("...[" + nomenInfo.fileName() + "]");
  }

   else // File. Checking times and states...

  {
   PRINT::put("\"" + nomenInfo.fileName() + "\": ");

   OMEN::NomenData nomenData;
   OMEN::clearNomenData(&nomenData);


   if ( OMEN::readNomen(nomenInfo.absoluteFilePath(), &nomenData) == CTERR_OK )
    {
     QString cryptomenStr = translator.makeCryptomenStr(nomenData.randomen);


     OMEN::CryptomenData cryptomenData;
     OMEN::clearCryptomenData(&cryptomenData);


     if ( OMEN::readCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK )
      {

       if (cryptomenData.isFar)
        {
         translator.decryptTwoQwords( cryptomenData.farSalt, &(cryptomenData.farTime), &(cryptomenData.farSize) );

         if (cryptomenData.isNear) {
           translator.decryptTwoQwords( cryptomenData.nearSalt, &(cryptomenData.nearTime), &(cryptomenData.nearSize) );
          }


         if ( (!cryptomenData.isNear) || (cryptomenData.isNear && ((cryptomenData.farTime != cryptomenData.nearTime) || isIgnoreTime)) )
          {
           PRINT::put("assigning... ");

           translator.encryptTwoQwords( cryptomenData.farSalt, &(cryptomenData.farTime), &(cryptomenData.farSize) );

           if (cryptomenData.isNear) {
             translator.encryptTwoQwords( cryptomenData.nearSalt, &(cryptomenData.nearTime), &(cryptomenData.nearSize) );
            }


           cryptomenData.action = ACTION_PULL;


           if (OMEN::writeCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK) {

             PRINT::put("done");

            } else PRINT::put("cannot write cryptomen.");

          } else PRINT::put("already near.");

        } else PRINT::put("not far.");

      } else PRINT::put("cannot read cryptomen.");


     OMEN::clearCryptomenData(&cryptomenData);

     cryptomenStr.clear();

    } else PRINT::put("cannot read nomen.");


   PRINT::LF();

   OMEN::clearNomenData(&nomenData);
  }


 return CTERR_OK;
}


int            Actioner::shade(QString pathInNominis, QString objName)
{
 Pather        pather(contextP);

 Translator    translator(contextP, stateP);


 QFile         nomen(pather.getNominisAbs(pathInNominis) + objName);

 if ( !nomen.exists() )
  return CTERR_OBJECT_NOT_FOUND;

 QFileInfo     nomenInfo(nomen);



 if ( nomenInfo.isDir() ) // Dir. Additional checks and recursion for list of its elements follow...
  {
   PRINT::putLine("[" + nomenInfo.fileName() + "]...");

   QFileInfoList entryInfoList = QDir(pather.getNominisAbs(pathInNominis) + objName).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::DirsFirst);

   for (int i = 0; i < entryInfoList.count(); i++)
    {
     Actioner actioner(contextP, stateP);

     actioner.shade(pathInNominis + objName + "/", (entryInfoList.at(i)).fileName());
    }

   PRINT::putLine("...[" + nomenInfo.fileName() + "]");
  }

   else // File. Checking times and states...

  {
   PRINT::put("\"" + nomenInfo.fileName() + "\": ");

   OMEN::NomenData nomenData;
   OMEN::clearNomenData(&nomenData);


   if ( OMEN::readNomen(nomenInfo.absoluteFilePath(), &nomenData) == CTERR_OK )
    {
     QString cryptomenStr = translator.makeCryptomenStr(nomenData.randomen);


     OMEN::CryptomenData cryptomenData;
     OMEN::clearCryptomenData(&cryptomenData);


     if ( OMEN::readCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK )
      {

       if (cryptomenData.isFar)
        {
         translator.decryptTwoQwords( cryptomenData.farSalt, &(cryptomenData.farTime), &(cryptomenData.farSize) );

         if (cryptomenData.isNear)
          {
           translator.decryptTwoQwords( cryptomenData.nearSalt, &(cryptomenData.nearTime), &(cryptomenData.nearSize) );

           if ( cryptomenData.nearTime == cryptomenData.farTime ) // suggest the files to be equal then
            {
             PRINT::put("shading... ");

             translator.encryptTwoQwords( cryptomenData.farSalt, &(cryptomenData.farTime), &(cryptomenData.farSize) ); // restore encryption

             cryptomenData.isNear = false;
             memset(cryptomenData.nearSalt, 0, HASH_SIZE);
             cryptomenData.nearTime = 0;
             cryptomenData.nearSize = 0;

             cryptomenData.action = ACTION_NONE; // in case there was any

             if ( OMEN::writeCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK )
              {
               if ( QFile(pather.getHarbourAbs("/" + cryptomenStr)).remove() ) {

                 PRINT::put("done.");

                } else PRINT::put("cannot remove near.");

              } else PRINT::put("cannot write cryptomen.");

            } else PRINT::put("near and far aren't equal.");

          } else PRINT::put("already not near.");

        } else PRINT::put("not far.");

      } else PRINT::put("cannot read cryptomen.");


     OMEN::clearCryptomenData(&cryptomenData);

     cryptomenStr.clear();

    } else PRINT::put("cannot read nomen.");


   PRINT::LF();

   OMEN::clearNomenData(&nomenData);
  }


 return CTERR_OK;
}


int            Actioner::erase(QString pathInNominis, QString objName, EraseType eraseType)
{
 Pather        pather(contextP);

 Translator    translator(contextP, stateP);


 QFile         nomen(pather.getNominisAbs(pathInNominis) + objName);

 if ( !nomen.exists() )
  return CTERR_OBJECT_NOT_FOUND;

 QFileInfo     nomenInfo(nomen);



 if ( nomenInfo.isDir() ) // Dir. Additional checks and recursion for list of its elements follow...
  {
   PRINT::putLine("[" + nomenInfo.fileName() + "]...");

   QFileInfoList entryInfoList = QDir(pather.getNominisAbs(pathInNominis) + objName).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDir::DirsFirst);

   for (int i = 0; i < entryInfoList.count(); i++)
    {
     Actioner actioner(contextP, stateP);

     actioner.erase(pathInNominis + objName + "/", (entryInfoList.at(i)).fileName(), eraseType);
    }


   if (eraseType == EraseEntire)
    {
// By now, the objName dir should be empty

   if ( !QDir(pather.getNominisAbs(pathInNominis)).rmdir(objName) )
     PRINT::put("cannot remove dir.");
    }


   PRINT::putLine("...[" + nomenInfo.fileName() + "]");
  }

   else // File. Checking times and states...

  {
   PRINT::put("\"" + nomenInfo.fileName() + "\": ");

   OMEN::NomenData nomenData;
   OMEN::clearNomenData(&nomenData);


   if ( OMEN::readNomen(nomenInfo.absoluteFilePath(), &nomenData) == CTERR_OK )
    {
     QString cryptomenStr = translator.makeCryptomenStr(nomenData.randomen);

     OMEN::CryptomenData cryptomenData;
     OMEN::clearCryptomenData(&cryptomenData);


     if ( OMEN::readCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK )
      {
       switch (eraseType)
        {
         case EraseNear:

           PRINT::put("cleaning... ");

           if (cryptomenData.isNear)
            {

             if ( QFile(pather.getHarbourAbs("/" + cryptomenStr)).remove() )
              {
               cryptomenData.isNear = false;
               memset(cryptomenData.nearSalt, 0, HASH_SIZE);
               cryptomenData.nearTime = 0;
               cryptomenData.nearSize = 0;

               if (OMEN::writeCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK) {

                 PRINT::put("done.");

                } else PRINT::put("cannot write cryptomen.");

              } else PRINT::put("cannot remove near.");

            } else PRINT::put("not near.");

         break;


         case EraseFar:

           PRINT::put("assigning... ");

           if (cryptomenData.isFar)
            {
             cryptomenData.action = ACTION_UNCLOUD;

             if (OMEN::writeCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK) {

               PRINT::put("done.");

              } else PRINT::put("cannot write cryptomen.");

            } else PRINT::put("not far.");

         break;


         case EraseEntire:

           PRINT::put("cleaning and assigning... ");

           if ( nomen.remove() ) // don't need it anymore. Remove it now, when we're sure the cryptomen of randomen is correct
            {

             if (cryptomenData.isNear)
              {

               if ( !QFile(pather.getHarbourAbs("/" + cryptomenStr)).remove() ) {
                 PRINT::put("cannot remove near. ");
                }

               cryptomenData.isNear = false;
               memset(cryptomenData.nearSalt, 0, HASH_SIZE);
               cryptomenData.nearTime = 0;
               cryptomenData.nearSize = 0;
              }


             if (cryptomenData.isFar)
              {
               cryptomenData.action = ACTION_ANNIHILATE;

               if (OMEN::writeCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK) {

                 PRINT::put("done.");

                } else PRINT::put("cannot write cryptomen.");

              }
               else // no need to keep cryptomen anymore
              {
               if ( QFile(pather.getHarbourAbs("/." + cryptomenStr)).remove() ) {

                 PRINT::put("done.");

                } else PRINT::put("cannot remove cryptomen.");

              }

            } else PRINT::put("cannot remove nomen. ");

         break;


         default: PRINT::put("dunno..."); break;
        }


      } else PRINT::put("cannot read cryptomen.");


     OMEN::clearCryptomenData(&cryptomenData);

     cryptomenStr.clear();

    } else PRINT::put("cannot read nomen.");


   PRINT::LF();

   OMEN::clearNomenData(&nomenData);
  }


 return CTERR_OK;
}


Actioner::~Actioner()
{
 contextP = NULL;
 stateP = NULL;
}


} // namespace "cryptrav"
