/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  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 file 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
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2006-06-08
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/


#include "PathTranslator.h"
#include "Message.h"
#include "File.h"
#include "Trace.h"
#include "TraceBug.h"
#include "MemoryChecker.h"

namespace QGpCoreTools {

/*!
  \class PathTranslator
  \brief

  Utility to ease access to file which path is stored as absolute. It can translate obsolete
  paths into new ones, for instance, if the user change the organisation of his file tree.
  The new path is only asked one time. For all other requests, the obsolete path is searched
  in a map and if found, the file path is modified.
*/

/*!
  Returns a translated path (even file name can be translated for lower or upper letters)

  \a fileType must contain '%1' to be replaced by file name
*/
QString PathTranslator::translate(const QString& originalFile, const PathTranslatorOptions& options)
{
  TRACE;
  //TRACE_BUG;
  QChar sep=pathSeparator(originalFile);
  QFileInfo file(originalFile);
  QString path=file.path();
  if(!options.forceAsk()) {
    QString relPath;
    iterator it;
    //TRACE_BUG_PRINT(QString("translate(%1,%2,%3)").arg(path).arg(options.title()).arg(options.fileType()));
    // Decompose path upwards and check if they exist in path dictionnary
    while(true) {
      it=find(path);
      if(it!=end()) {
        path=it.value();
        //TRACE_BUG_PRINT(QString("path replaced by %1").arg(path));
        path+=relPath;
        //TRACE_BUG_PRINT(QString("definitive path %1").arg(path));
        QDir d(path);
        if(d.exists()) {
          QFileInfo fi(d.absoluteFilePath(file.fileName()));
          if(fileExists(fi)) {
            //qWarning("Debugging PathTranslator for Windows");
            return fi.absoluteFilePath();
          }
        }
        return askPath(options, file, sep, d.absolutePath());
      } else {
        if(isRoot(path)) break;
        QString name=File::fileName(path, sep);
        path.chop(name.count()+1);
        relPath=sep+name+relPath;
        //TRACE_BUG_PRINT(QString("choped path %1").arg(path));
      }
    }
  }

  // Some heuristics were once available here to guess the modified path.
  // They were removed on 2016-09-08 because it may lead to undesirable results.
  // Better to leave the full control to the user.

  path=askPath(options, file, sep);
  //TRACE_BUG_PRINT(QString("translated path %1").arg(path));
  //qWarning("Debugging PathTranslator for Windows");
  return path;
}

/*!
  Add the different root between \a obsoletePath and \a newPath to this translator
*/
void PathTranslator::addPath(QString obsoletePath, QString newPath, QChar sep)
{
  TRACE;
  //qDebug("Entering PathTranslator::addPath( %s, %s)",obsoletePath.toLatin1().data(),newPath.toLatin1().data());
  while(!isRoot(obsoletePath) && !isRoot(newPath)) {
    QString obsName=File::fileName(obsoletePath, sep);
    QString newName=File::fileName(newPath, sep);
    if(obsName!=newName) {
      //qDebug("Adding translation for %s to %s", obsoletePath.toLatin1().data(), newPath.toLatin1().data());
      insert (obsoletePath, newPath);
      return;
    }
    obsoletePath.chop(obsName.count()+1);
    newPath.chop(newName.count()+1);
    //qDebug("Choped obsoletePath: %s",obsoletePath.toLatin1().data());
    //qDebug("Choped newPath: %s",newPath.toLatin1().data());
  }
  if(obsoletePath!=newPath) {
    //qDebug("Adding translation for %s to %s", obsoletePath.toLatin1().data(), newPath.toLatin1().data());
    insert (obsoletePath, newPath);
  }
}

/*!
  Ask for \a file from directory \a d
*/
QString PathTranslator::askPath (const PathTranslatorOptions& options, const QFileInfo& file,
                                 QChar sep, const QString& directory)
{
  TRACE;
  if(Message::warning(MSG_ID, options.title(), options.userMessage(file.absoluteFilePath()),
                           Message::yes(), Message::no(), true)==Message::Answer0) {
    QString newFileName=Message::getOpenFileName(options.title(),
                                                 options.fileType().arg(file.fileName()),
                                                 directory,
                                                 tr("Please select file '%1' in its new location.").arg(file.filePath()));
    if(!newFileName.isEmpty()) {
      QFileInfo finew(newFileName);
      if(finew.fileName().toLower()==file.fileName().toLower()) {
        addPath(file.path(), finew.path(), sep);
        return newFileName;
      } else {
        Message::warning(MSG_ID, options.title(), tr("Selected file is not the correct file. Aborting."), Message::ok());
      }
    }
  }
  return QString();
}

/*!
  Returns true if \a fi exists ignoring case.
  If file exists with a slightly different case, \a fi is modified.
*/
bool PathTranslator::fileExists(QFileInfo& fi)
{
  TRACE;
  if(fi.exists()) return true;
  QDir d(fi.dir());
  QStringList files=d.entryList(QDir::Files | QDir::NoDotAndDotDot);
  QString fileLower=fi.fileName().toLower();
  foreach(QString f, files) {
    if(f.toLower()==fileLower) {
      fi.setFile(d, f);
      return true;
    }
  }
  return false;
}

/*!
  Return the separator of \a path: '/' for unix paths or '\' for windows paths.
  This is based on some heuristics and success is not guaranteed in all cases.
*/
QChar PathTranslator::pathSeparator(QString path)
{
  TRACE;
  if(path.count()<3) {
    if(path.contains('\\')) {
      return '\\';
    } else {
      return '/';
    }
  } else {
    QRegExp drive("^[a-zA-Z]:");
    if(drive.indexIn(path)==0) {
      if(path[2]=='/') {
        return '/';
      } else if(path[2]=='\\') {
        return '\\';
      }
    }
    if(path[0]=='/') {
      return '/';
    }
    if(path.count(QRegExp("[^\\]\\[^\\]"))>path.count(QRegExp("[^/]/[^/]"))) {
      return '\\';
    } else {
      return '/';
    }
  }
}

/*!
  Returns true if root or empty path.
*/
bool PathTranslator::isRoot(QString path)
{
  TRACE;
  if(path.isEmpty()) {
    return true;
  }
#ifdef Q_OS_WIN
  if(path.count()==2 && path[1]==':') {
    return true;
  }
#endif
  return false;
}

} // namespace QGpCoreTools
