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

namespace cryptrav
{


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


int            Mover::put(QString pathInHallway, QString pathInNominis, QString objName, bool isIgnoreTime, bool isPushNew)
{
 Pather        pather(contextP);

 Translator    translator(contextP, stateP);



 QFile         element(pather.getHallwayAbs(pathInHallway) + objName);

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

 QFileInfo     elementInfo(element);


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


   if ( !QDir(pather.getNominisAbs(pathInNominis) + objName).exists() )
    if ( !QDir(pather.getNominisAbs(pathInNominis)).mkdir(objName) ) // new dirs have to be created at some point...
     return CTERR_DIR_CANNOT_CREATE;


// This is one of only two places (for now; the other is at commander/ls)
// where we explicitly require Hidden and System files/folders to be included into list.
// (by default, they are not included, [.git] folder as an example)
// These attributes are lost inside crypt.
   QFileInfoList entryInfoList = QDir(pather.getHallwayAbs(pathInHallway) + objName).entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::DirsFirst);


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

     int err = mover.put(pathInHallway + objName + "/", pathInNominis + objName + "/", (entryInfoList.at(i)).fileName(), isIgnoreTime, isPushNew);

     if (err != CTERR_OK) { // for a time being, it may be CTERR_OBJECT_NOT_FOUND or CTERR_HASH_COLLISION
       return err;
      }
    }

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

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

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


   qint64 elementTime;

   if (TIMESS::getFileModTime(elementInfo.absoluteFilePath(), &elementTime) == CTERR_OK) // else we cannot compare file times
    {
     QFile       nomen(pather.getNominisAbs(pathInNominis) + objName);
     QFileInfo   nomenInfo(nomen);

     OMEN::NomenData nomenData;
     OMEN::clearNomenData(&nomenData); // fill it with zeroes --- remained memory content may incidentally be "correct" randomen

     OMEN::CryptomenData cryptomenData;
     OMEN::clearCryptomenData(&cryptomenData); // see above

     QString cryptomenStr;

     QFile target;


     if ( nomen.exists() ) // then rewrite encrypted file only if it's of different time than the one in hallway... cryptomen may change, too
      {
       PRINT::put("updating... ");

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


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

           if (cryptomenData.isNear) { // encrypted file exists in Crypt --- get its time
             translator.decryptTwoQwords( cryptomenData.nearSalt, &(cryptomenData.nearTime), &(cryptomenData.nearSize) );
            }

// Put encrypted (with new "random" salt) file from Hallway into Crypt
// if it doesn't exist there OR if it exists, but has different last modification time (hence we suggest its content to be different)

           if ( (!cryptomenData.isNear) || (cryptomenData.isNear && ((cryptomenData.nearTime != elementTime) || isIgnoreTime) ) )
            {
             translator.genSalt(nomenData.randomen, cryptomenData.nearSalt);

             cryptomenData.nearTime = elementTime;
             cryptomenData.nearSize = elementInfo.size();

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


             target.setFileName(pather.getHarbourAbs("/" + cryptomenStr));

             if (translator.encrypt(&element, &target, cryptomenData.nearSalt) == CTERR_OK)
              {
               cryptomenData.isNear = true;

               cryptomenData.action = ( isPushNew ) ? ACTION_PUSH : ACTION_NONE;

               if (isPushNew) PRINT::put("assigning push... ");

               if (OMEN::writeCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK) {
                 PRINT::put("done.");
                } else PRINT::put("cannot write cryptomen.");

              } else PRINT::put("encryption failed.");

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

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

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

      }

       else  // new file - create nomen, encrypted file and cryptomen...

      {
       PRINT::put("adding... ");


       translator.genRandomen(pather.getNominisRel(nomenInfo.absoluteFilePath()), nomenData.randomen);

       translator.genSalt(nomenData.randomen, cryptomenData.nearSalt);


       cryptomenStr = translator.makeCryptomenStr(nomenData.randomen);


// verify that there is no cryptomen with the same name in harbour
       if ( !QFile(pather.getHarbourAbs("/." + cryptomenStr)).exists() )
        {
         target.setFileName(pather.getHarbourAbs("/" + cryptomenStr));


         if (translator.encrypt(&element, &target, cryptomenData.nearSalt) == CTERR_OK)
          {
// filling the cryptomenData...
           cryptomenData.formatVer = CRYPTOMEN_FORMAT_VERSION;

           cryptomenData.isNear = true;

// nearSalt has been filled already

           cryptomenData.nearTime = elementTime;
           cryptomenData.nearSize = elementInfo.size();

// these are encrypted...
           translator.encryptTwoQwords( cryptomenData.nearSalt, &(cryptomenData.nearTime), &(cryptomenData.nearSize) );


// initially, the new file isn't stored on the cloud (it has to be pushed)
           cryptomenData.isFar = false;
           memset(cryptomenData.farSalt, 0, HASH_SIZE);
           cryptomenData.farTime = 0;
           cryptomenData.farSize = 0;

           cryptomenData.action = ( isPushNew ) ? ACTION_PUSH : ACTION_NONE;

           if (isPushNew) PRINT::put("assigning push... ");


           if (OMEN::writeCryptomen(pather.getHarbourAbs("/." + cryptomenStr), &cryptomenData) == CTERR_OK)
            {
             if (OMEN::writeNomen(pather.getNominisAbs(pathInNominis + objName), &nomenData) == CTERR_OK) {

             // doing it when we're sure that all data structures & files are made successfully
               PRINT::put("done.");

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

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

          } else PRINT::put("encryption failed.");

        }
         else
        {
// Congratulations! You've just found SHA-256 collision!! Fanfare and trumpets!!!
         PRINT::putLine("cryptomen name collision. Wow.");
         PRINT::putLine("Try again.");

         return CTERR_HASH_COLLISION;
        }


      }


     OMEN::clearNomenData(&nomenData);
     OMEN::clearCryptomenData(&cryptomenData);

     cryptomenStr.clear();

    } else PRINT::put("cannot get file time.");


   elementTime = 0;

   PRINT::LF();
  }


 return CTERR_OK;
}


int            Mover::get(QString pathInHallway, QString pathInNominis, QString objName, bool isIgnoreTime, bool isPullAbsent, bool isOnePass)
{
 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() + "]...");

   if ( !QDir(pather.getHallwayAbs(pathInHallway) + objName).exists() )
    if ( !QDir(pather.getHallwayAbs(pathInHallway)).mkdir(objName) ) // new dirs have to be created at some point...
     return CTERR_DIR_CANNOT_CREATE;


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

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

     mover.get(pathInHallway + objName + "/", pathInNominis + objName + "/", (entryInfoList.at(i)).fileName(), isIgnoreTime, isPullAbsent, isOnePass);
    }

   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)
        {
         QFile element(pather.getHallwayAbs(pathInHallway + objName));
         QFileInfo elementInfo(element);


         QFile source(pather.getHarbourAbs("/" + cryptomenStr));

         if ( element.exists() )  // rewrite unencrypted file only if it's of different time than the one in crypt...
          {
           PRINT::put("updating... ");

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

           qint64 elementTime;

           if (TIMESS::getFileModTime(elementInfo.absoluteFilePath(), &elementTime) == CTERR_OK)
            {
             if ( (cryptomenData.nearTime != elementTime) || isIgnoreTime )
              {

               if ( translator.decrypt(&source, &element, cryptomenData.nearSalt, isOnePass) == CTERR_OK)
                {
                 // nearTime is decrypted by now
                 if (TIMESS::setFileModTime(elementInfo.absoluteFilePath(), cryptomenData.nearTime) == CTERR_OK) {

                   PRINT::put("done.");

                  } else PRINT::put("cannot set file time.");

                } else PRINT::put("decryption failed.");

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

            } else PRINT::put("cannot get file time.");

           elementTime = 0;

          }
           else // make new file in hallway
          {
           PRINT::put("adding... ");

           if ( translator.decrypt(&source, &element, cryptomenData.nearSalt, isOnePass) == CTERR_OK)
            {
             translator.decryptTwoQwords( cryptomenData.nearSalt, &(cryptomenData.nearTime), &(cryptomenData.nearSize) );

             if (TIMESS::setFileModTime(elementInfo.absoluteFilePath(), cryptomenData.nearTime) == CTERR_OK) {

               PRINT::put("done.");

              } else PRINT::put("cannot set file time.");

            } else PRINT::put("decryption failed.");

          }

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

         if (isPullAbsent)
          {
           if (cryptomenData.isFar)
            {
             PRINT::put("assigning pull... ");

             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("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;
}



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


} // namespace "cryptrav"
