/***************************************************************************
**
**  This file is part of GeopsyCore.
**
**  GeopsyCore 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.
**
**  GeopsyCore 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2006-07-09
**  Copyright: 2006-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QGpCoreTools.h>

#include "CityScanner.h"
#include "CitySignal.h"

namespace GeopsyCore {

/*!
  \class CityScanner CityScanner.h
  \brief Brief description of class still missing

  Full description of class still missing
*/

/*!
  Description of constructor still missing
*/
CityScanner::CityScanner(QObject * parent) : DeviceCardScanner(parent)
{
  TRACE;
  _softwareVersion="????";
}

/*!
  Description of constructor still missing
*/
CityScanner::~CityScanner()
{
  TRACE;
  qDeleteAll(_signals);
}

/*!
  Set path for device card. If scanner is running, it stops it. After setting the path, scanner is
  automatically started.
*/
bool CityScanner::setPath(const QDir& cardPath)
{
  TRACE;
  clear();
  _cardPath=cardPath;
  return start();
}

QFile * CityScanner::stream(QIODevice::OpenModeFlag mode)
{
  TRACE;
  return fichbaseStream(mode);
}

void CityScanner::clear()
{
  TRACE;
  DeviceCardScanner::clear();
  emit beginSignalChange();
  qDeleteAll(_signals);
  _signals.clear();
  emit endSignalChange();
}


QFile * CityScanner::fichbaseStream(QIODevice::OpenModeFlag mode)
{
  TRACE;
  QFile * f=new QFile(_cardPath.absoluteFilePath("fichbase.dat"));
  if( !f->open(mode) ) {
    f->setFileName(_cardPath.absoluteFilePath("FICHBASE.DAT"));
    if( !f->open(mode) ) {
      App::log(this, tr(" # Error # Cannot open file %1 or %2.\n")
                             .arg(_cardPath.absoluteFilePath("fichbase.dat"))
                             .arg(_cardPath.absoluteFilePath("FICHBASE.DAT")));
      delete f;
      return 0;
    } else {
      App::log(this, tr("Opening file %1.\n").arg(_cardPath.absoluteFilePath("FICHBASE.DAT")));
    }
  } else {
    App::log(this, tr("Opening file %1.\n").arg(_cardPath.absoluteFilePath("fichbase.dat")));
  }
  return f;
}

QFile * CityScanner::sectcityStream(QIODevice::OpenModeFlag mode)
{
  TRACE;
  QFile * f=new QFile(_cardPath.absoluteFilePath("sectcity.hex"));
  if( !f->open(mode)) {
    f->setFileName(_cardPath.absoluteFilePath("SECTCITY.HEX"));
    if(!f->open(mode)) {
      App::log(this, tr(" # Error # Cannot open file %1 or %2.\n")
                             .arg(_cardPath.absoluteFilePath("sectcity.hex"))
                             .arg(_cardPath.absoluteFilePath("SECTCITY.HEX")));
      delete f;
      return 0;
    } else {
      App::log(this, tr("Opening file %1.\n").arg(_cardPath.absoluteFilePath("SECTCITY.HEX")));
    }
  } else {
    App::log(this, tr("Opening file %1.\n").arg(_cardPath.absoluteFilePath("sectcity.hex")));
  }
  return f;
}

void CityScanner::readSoftwareVersion()
{
  TRACE;
  qint64 pos=_stream->pos();
  char currentBlock[BLOCK_SIZE+1];
  currentBlock[BLOCK_SIZE]='\0';
  _stream->read(currentBlock, BLOCK_SIZE);
  _softwareVersion=QString::fromLatin1(currentBlock+5,4);
  bool ok=true;
  _softwareVersion.toInt(&ok);
  if(!ok) {
    _softwareVersion="????";
    _stream->seek(pos);
  }
}

/*!
  Scan flash card until reaching the end of file.
  The complex scan can recover partly over written data.
*/
void CityScanner::run()
{
  TRACE;
  if(!_stream) return;
  char currentBlock[BLOCK_SIZE+1];
  currentBlock[BLOCK_SIZE]='\0';
  // Read software version
  readSoftwareVersion();
  QElapsedTimer chrono;
  chrono.start();
  qint64 lastPos=0;
  while(!_stream->atEnd()) {
    //  DEB, DEB2, ERASED or ERASED2
    qint64 pos=_stream->pos();
    if(_stream->read(currentBlock, BLOCK_SIZE)==BLOCK_SIZE &&
       (strncmp(currentBlock,"DEB",3)==0 || strncmp(currentBlock,"ERASED",6)==0)) {
      _stream->seek(pos);
      emit beginSignalChange();
      normalScan();
      emit endSignalChange();
    } else {
      if(chrono.elapsed()>10000) {
        if(pos<lastPos+1024) { // Less than 0.1kb/s
          pos+=1024*1024;
          App::log(tr("         very slow access, skipping 1Mb, read %1 Mb\n").arg(pos/(1024.0*1024.0)) );
        }
        lastPos=pos;
        App::log(tr("         fine scan, read %1 Mb\n").arg(pos/(1024.0*1024.0)) );
        chrono.start();
      }
      _stream->seek(pos+1);
    }
    if(isStopRequested()) {
      return;
    }
  }
  stop();
  App::log(tr("         scan of flash card finished.\n") );
}

/*!
  Scan flash card until not finding anymore signal. Signals are suposed to be recorded one after
  the last one. No gap between files are accepted. This is the normal situation when recording with
  cityshark. The more complex scan achived by run() can recover partly over written data.
*/
void CityScanner::normalScan()
{
  TRACE;
  while(!_stream->atEnd()) {
    CitySignal * sig=new CitySignal;
    if(sig->readHeader(_stream, _softwareVersion.toInt())) {
      _signals.append(sig);
    } else {
      App::log(tr("         %1 files are read from flash card.\n").arg(_signals.count()) );
      delete sig;
      break;
    }
    if(isStopRequested()) {
      return;
    }
  }
}

/*!
  Set files as erased and init card for new files (flash card is modified)
*/
void CityScanner::erase()
{
  TRACE;
  QFile * f;
  f=fichbaseStream(QIODevice::ReadWrite);
  if(f) {
    emit beginSignalChange();
    int n=count();
    for(int i=0;i < n;i++ ) {
      CitySignal * sig=_signals.at(i);
      if(!sig->erase(f)) {
        App::log(tr("Cannot write to flash card\n"));
      }
    }
    delete f;
    emit endSignalChange();
  } else {
    App::log(tr("Cannot open fichbase in %1\n").arg(_cardPath.path()) );
  }
  f=sectcityStream(QIODevice::ReadWrite);
  if(f) {
    // Format:
    // 24 bytes   "CARTE FLASH NEUVE       "
    // 8 bytes    ASCII Hexadecimal for flash card size
    // 2016 bytes null characters
    f->write("CARTE FLASH NEUVE       ", 24);
    f->seek(32);
    char nullChars[2016];
    memset(nullChars, 0, 2016);
    f->write(nullChars, 2016);
    delete f;
  } else {
    App::log(tr("Cannot open sectcity in %1\n").arg(_cardPath.path()) );
  }
}

} // namespace GeopsyCore
