/**
 * @file DigDagData.cpp
 *
 * Created on 2010/05/18 by Anthony Nemoff
 * Last update 2010/05/28 by Anthony Nemoff
 *
 */

#include "DigDagData.hpp"
#include <QStringList>

//==============================================================================
// Constructor, destructor

/**
 * Constructor
 */
DigDagData::DigDagData()  /*:
    // XXXX Moved initialisation code to constructor (problem initialising QString here)
    _currentFileVersion(0),
    _commandLine(""),
    _inputFolder(""),
    _numberOfInputDags(0),
    _outputPrefix(""),
    _minSupport(0),
    _edgePruningThreshold(0),
    _numberOfDagPatterns(0),
    _minDagSupport(0),
    _maxDagSupport(0),
    _minDagSize(0),
    _maxDagSize(0),
    _minDagLength(0),
    _maxDagLength(0),
    PARSER_VERSION("1.2"),
    FILE_VERSION_SECTION("# DigDag File - File version"),
    HEADER_SECTION_START("# BEGIN HEADER"),
    HEADER_SECTION_END("# END HEADER"),
    INDEX_SECTION_START("# BEGIN INPUT FILES INDEX"),
    INDEX_SECTION_END("# END INPUT FILES INDEX"),
    PATTERNS_LIST_SECTION_START("# BEGIN PATTERNS LIST"),
    PATTERNS_LIST_SECTION_END("# END PATTERNS LIST"), // XXX Similar section names ...
    PATTERN_SECTION_START("# BEGIN PATTERN"),
    PATTERN_SECTION_END("# END PATTERN"), // XXX ... with this section
    DAG_SECTION_START("# BEGIN DAG"),
    DAG_SECTION_END("# END DAG"),
    TID_LIST_SECTION_START("# BEGIN TID LIST"),
    TID_LIST_SECTION_END("# END TID LIST")*/
{
  // Variables initialisation
  _currentFileVersion = "0";
  _commandLine = "";
  _inputFolder = "";
  _numberOfInputDags = 0;
  _outputPrefix = "";
  _minSupport = 0;
  _edgePruningThreshold = 0;
  _numberOfDagPatterns = 0;
  _minDagSupport = 0;
  _maxDagSupport = 0;
  _minDagSize = 0;
  _maxDagSize = 0;
  _minDagLength = 0;
  _maxDagLength = 0;
  PARSER_VERSION = "1.2";
  FILE_VERSION_SECTION = "# DigDag File - File version";
  HEADER_SECTION_START = "# BEGIN HEADER";
  HEADER_SECTION_END = "# END HEADER";
  INDEX_SECTION_START = "# BEGIN INPUT FILES INDEX";
  INDEX_SECTION_END = "# END INPUT FILES INDEX";
  PATTERNS_LIST_SECTION_START = "# BEGIN PATTERNS LIST";
  PATTERNS_LIST_SECTION_END = "# END PATTERNS LIST";
  PATTERN_SECTION_START = "# BEGIN PATTERN";
  PATTERN_SECTION_END = "# END PATTERN";
  DAG_SECTION_START = "# BEGIN DAG";
  DAG_SECTION_END = "# END DAG";
  TID_LIST_SECTION_START = "# BEGIN TID LIST";
  TID_LIST_SECTION_END = "# END TID LIST";


  createGraphvizContext();
}


/**
 *
 */
DigDagData::~DigDagData() {

  // QList::takeAt(i) : get item i and remove it from the list

  // Delete all DAG patterns
  for (int i = 0; i < _patternsList.size(); i++) {
    delete _patternsList.takeAt(i);
  }

  // Delete all input DAGs
  for (int i = 0; i < _inputDagsList.size(); i++) {
    delete _inputDagsList.takeAt(i);
  }

  freeGraphvizContext();
}


//==============================================================================
// Getters & setters

QList<DagPattern*>* DigDagData::getPatternsList() {
  return &_patternsList;
}


QList<InputDag*>* DigDagData::getInputDagsList() {
  return &_inputDagsList;
}


//==============================================================================
// Methods

/**
 *
 */
int DigDagData::openFile(QString fileName) {

  // TODO clean up data before reading a new file, if re-use of same object (?)

  // Set file name
  _file.setFileName(fileName);

  // Open file
  if (!_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    qCritical("Error opening %s", qPrintable(_file.fileName()));
    return DDV_OPEN_ERROR; // Open failed
  }

  return DDV_OPEN_SUCESS;

}


/**
 *
 */
int DigDagData::readFile() {

  // TODO bool dataIsComplete; // check data integrity ?

  // Testing chrono
  //QTime chrono;
  //chrono.start();

  // Check file version
  if (!findSection(FILE_VERSION_SECTION))
    return DDV_WRONG_FILE_VERSION;

  _currentFileVersion = extractValue(_currentLine);

  if (_currentFileVersion != PARSER_VERSION)
    return DDV_WRONG_FILE_VERSION;

  // Header
  findSection(HEADER_SECTION_START);
  parseHeader();

  // TODO check if input folder exists or is correctly located

  // Input DAG files index
  findSection(INDEX_SECTION_START);
  parseInputDags();

  // Patterns list
  findSection(PATTERNS_LIST_SECTION_START);
  parsePatterns();

  /* At this state all input DAGs have their supported patterns list complete,
   * but not sorted by index.
   * All lists are going to be sorted. This sould be done only once : now.
   */
  for (int i = 0; i < _inputDagsList.size(); i++) {
    _inputDagsList[i]->sortSupportedPatternsAscending();
  }

  // TODO indexation : lire la dernière partie du fichier et ajouter les structure de données
  /*
    les indexs des labels des noeuds correspondent aux indices de la liste _patternsList
    */

  //qDebug() << "time :" << chrono.elapsed();

  return DDV_PARSE_SUCCESS;

}


/**
 * Reads a line from the file and keeps track of the file position.
 */
QString DigDagData::readLine() {

  // Current position in file
  _filePosition = _file.pos();

  _currentLine = QString(_file.readLine()).trimmed();
  return _currentLine;

}


/**
 * Extracts a value from a line having the following syntax :
 * "value_name: value"
 *
 * @param line The line containing a value
 * @return The value as a string
 */
QString DigDagData::extractValue(QString line) {
  // index + 2 : 1 to skip ':' and 1 to skip ' '
  return line.mid(line.indexOf(":") + 2);
}


/**
 * Skips empty lines, if any, until finding the given section name and stops reading.
 *
 * @param sectionName
 * @return true if section was found, false if not
 */
bool DigDagData::findSection(QString sectionName) {

  do {
    _currentLine = readLine();
  } while (!_currentLine.contains(sectionName));

  if (!_file.atEnd()) {
    return true;
  } else {
    return false;
  }

}


/**
 *
 */
void DigDagData::parseHeader() {

  // currentLine contains HEADER_SECTION_START
  // We skip this line to get to the first line with information

  _commandLine = extractValue(readLine());
  _inputFolder = extractValue(readLine());
  _numberOfInputDags = extractValue(readLine()).toInt();
  readLine(); // TODO Temporary fix : Skip line "Input files sorted: true/false"
  _outputPrefix = extractValue(readLine());
  _minSupport = extractValue(readLine()).toInt();
  _edgePruningThreshold = extractValue(readLine()).toInt();
  _numberOfDagPatterns = extractValue(readLine()).toInt();

}


/**
 *
 */
void DigDagData::parseInputDags() {

  QString dagFileName;
  int stringIndex, dagIndex;

  for (int i = 0; i < _numberOfInputDags; i++) {
    readLine();

    // Extract index value and filename
    stringIndex = _currentLine.indexOf(' ');
    dagIndex = _currentLine.mid(0, stringIndex).toInt();
    dagFileName = _currentLine.mid(stringIndex + 1);

    // Inset new pair <index, filename> in map
    _inputDagsMap[dagIndex] = dagFileName;

    // Create new input DAG
    initInputDag(dagIndex, dagFileName);

  } // end for i

}


/**
 *
 */
void DigDagData::initInputDag(int index, QString dagFileName) {
  // Create input DAG
  InputDag* inputDag = new InputDag(index,
                                    dagFileName,
                                    QObject::tr("Input DAG %1").arg(index),
                                    _graphvizContext);

  // Tell that DAG structure has not been loaded :
  // at the current state no graph is built.
  inputDag->setDagStructureLoaded(false);

  // Add the input DAG to the list
  _inputDagsList.append(inputDag);
}


/**
 *
 */
bool DigDagData::loadInputDag(int index) {

  // If input DAG structure is not loaded, then load it, else do nothing.
  InputDag *inputDag = _inputDagsList[index];
  DagStructure *dagStructure = inputDag->getStructure();
  if (!inputDag->isDagStructureLoaded()) {

    // DAG file
    QFile dagFile(_inputFolder + _inputDagsList[index]->getDagFileName());
//    QFileInfo fileInfo(dagFile);
//    qDebug() << "dagFile path :" << fileInfo.absoluteFilePath() << ", exists :" << fileInfo.exists();

    // Open .dag file
    if (!dagFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
      qCritical("Error opening %s", qPrintable(dagFile.fileName()));
      return false;
    }

    //----------------------------------------------------------------------------
    // Parse .dag file and build DagStructure
    int strIndex1, strIndex2;
    int nodeStart, nodeEnd;
    float edgeValue;
    QString nodeLabel;
    QString line;

    QString(_file.readLine()).trimmed();

    // SKIP BRNC line
    dagFile.readLine();

    // Read nb nodes and nb edges
    line = dagFile.readLine().trimmed();
    strIndex1 = line.indexOf('\t');
    dagStructure->setNumberOfNodes(line.mid(0, strIndex1).toInt());
    dagStructure->setNumberOfEdges(line.mid(strIndex1 + 1).toInt());

    // Parse nodes list
    for (int i = 0; i < dagStructure->getNumberOfNodes(); i++) {
      line = dagFile.readLine().trimmed();

      strIndex1 = line.indexOf('\t');
      nodeLabel = line.mid(strIndex1 + 1);

      dagStructure->addNode(i, nodeLabel);
    }

    // Parse edges list
    for (int i = 0; i < dagStructure->getNumberOfEdges(); i++) {
      line = dagFile.readLine().trimmed();

      strIndex1 = line.indexOf('\t');
      strIndex2 = line.indexOf('\t', strIndex1 + 1);
      nodeStart = line.mid(0, strIndex1).toInt() - 1;
      nodeEnd = line.mid(strIndex1 + 1, strIndex2 - strIndex1 - 1).toInt() - 1;
      edgeValue = line.mid(strIndex2 + 1).toFloat();

      // TODO store edge value
      dagStructure->addEdge(nodeStart, nodeEnd, i);
    }

    // DAG pattern is now completely loaded
    inputDag->setDagStructureLoaded(true);

  } // end if (!inputDag->isDagStructureLoaded())

  return true;

}


/**
 *
 */
void DigDagData::parsePatterns() {

  // currentLine contains PATTERNS_LIST_SECTION_START

  int patternIndex;
  int readPatterns = 0;

  // Parse all patterns
  for (int i = 0; i < _numberOfDagPatterns && !_file.atEnd(); i++, readPatterns++) {
    // Find pattern section start
    findSection(PATTERN_SECTION_START); // currentLine contains PATTERN_SECTION_START

    // Extract pattern number
    patternIndex = _currentLine.mid(PATTERN_SECTION_START.size() + 1).toInt();

    // Init DAG pattern (partial loading)
    initDagPattern(patternIndex);
  } // end for i

  // Corrupted data : missing dag patterns
  if (readPatterns < _numberOfDagPatterns) {
    // TODO handle error
    qCritical() << "Corrupted file : missing DAG patterns";
  }

}


/**
 * Initializess a DAG patterns without loading all the data. It reads and stores
 * the next values:
 * - pattern number
 * - support
 * - length
 * - number of nodes
 * - number of edges
 * - DAG description file position
 *
 * This avoids loading whole content at once when opening the file.
 *
 * @param patternIndex Pattern index
 */
void DigDagData::initDagPattern(int index) {

  int stringIndex = 0;
  int support = 0;
  int length = 0;
  int numberOfNodes = 0;
  int numberOfEdges = 0;
  qint64 dagPos = 0;

  // Entry point : currentLine contains PATTERN_SECTION_START

  // Read support
  support = extractValue(readLine()).toInt();

  // Update min & max support
  if (index == 0)
    _minDagSupport = support;
  else
    _minDagSupport = support < _minDagSupport ? support : _minDagSupport;

  _maxDagSupport = support > _maxDagSupport ? support : _maxDagSupport;

  // Read length
  length = extractValue(readLine()).toInt();

  // Update min & max length
  if (index == 0)
    _minDagLength = length;
  else
    _minDagLength = length < _minDagLength ? length : _minDagLength;

  _maxDagLength = length > _maxDagLength ? length : _maxDagLength;

  // Find DAG_SECTION_START
  findSection(DAG_SECTION_START);

  // Skip DAG_SECTION_START tag and move to BNRC tag
  readLine();

  // Skip BRNC tag and move to next line (containing number of nodes and number of edges)
  readLine();

  // Extract values
  stringIndex = _currentLine.indexOf('\t');
  numberOfNodes = _currentLine.mid(0, stringIndex).toInt();
  numberOfEdges = _currentLine.mid(stringIndex + 1).toInt();

  // Update min & max size
  if (index == 0)
    _minDagSize = numberOfNodes;
  else
    _minDagSize = numberOfNodes < _minDagSize ? numberOfNodes : _minDagSize;

  _maxDagSize = numberOfNodes > _maxDagSize ? numberOfNodes : _maxDagSize;

  // Move to next line (beginning of nodes declaration), and store file position
  readLine();
  dagPos = _filePosition;


  // Create pattern object, initialize it and store it in the list
  DagPattern* pattern = new DagPattern(index,
                                       QObject::tr("DAG pattern %1").arg(index),
                                       numberOfNodes,
                                       numberOfEdges,
                                       _graphvizContext);

  pattern->setSupport(support);
  pattern->getStructure()->setLength(length);
  pattern->setDagDescriptionPos(dagPos);
  pattern->setLoaded(false); // Partially loaded

  _patternsList.append(pattern);


  // Parse TID list
  findSection(TID_LIST_SECTION_START); // currentLine contains TID_LIST_SECTION_START

  // Move to next line (containing the TID list) and store file position
  readLine();
  parseDagPatternTidList(pattern);


  // At this state the DAG pattern is partially loaded

}


/**
 *
 */
void DigDagData::loadDagPattern(int index) {

  // TODO changer approche : tester à l'extérieur si loaded ?

  if (!_patternsList[index]->isLoaded()) {

    // Read and parse DAG description
    parseDagPatternStructure(index);

  }

}


/**
 *
 */
void DigDagData::parseDagPatternStructure(int index) {

  DagPattern *pattern;
  DagStructure *dagStructure;
  int nbNodes;
  int nbEdges;
  int strIndex1, strIndex2;
  int nodeStart, nodeEnd;
  float edgeValue;
  QString nodeLabel;

  pattern = _patternsList[index];
  dagStructure = pattern->getStructure();
  nbNodes = dagStructure->getNumberOfNodes();
  nbEdges = dagStructure->getNumberOfEdges();

  // Set file position to DAG description beginning
  _file.seek(pattern->getDagDescriptionPos());

  // Skip 1 line
  readLine();

  // Parse nodes
  for (int i = 0; i < nbNodes; i++) {

    strIndex1 = _currentLine.indexOf('\t');
    nodeLabel = _currentLine.mid(strIndex1 + 1);

    dagStructure->addNode(i, nodeLabel);
    readLine();

  }

  // Parse edges
  for (int i = 0; i < nbEdges; i++) {

    strIndex1 = _currentLine.indexOf('\t');
    strIndex2 = _currentLine.indexOf('\t', strIndex1 + 1);
    nodeStart = _currentLine.mid(0, strIndex1).toInt() - 1;
    nodeEnd = _currentLine.mid(strIndex1 + 1, strIndex2 - strIndex1 - 1).toInt() - 1;
    edgeValue = _currentLine.mid(strIndex2 + 1).toFloat();

    dagStructure->addEdge(nodeStart, nodeEnd, i);

    readLine();
  }

  // DAG pattern is now completely loaded
  pattern->setLoaded(true);

}


/**
 *
 */
void DigDagData::parseDagPatternTidList(DagPattern* pattern) {

  int indexValue;

  // Split line and retrieve int values
  QStringList stringList = _currentLine.split(' ');
  QString token;
  for (int i = 0; i < stringList.size(); i++) {

    token = stringList.at(i);
    indexValue = token.toInt();

    // Add input index to the pattern's TID list
    pattern->appendInputDagIndex(indexValue);

    // Add pattern index to the input's supported patternss list
    // XXX This implementation assumes that inputs have numeric names (eg. 123.dag)
    _inputDagsList[i]->appendPatternIndex(indexValue);
  }

}


/**
 *
 */
void DigDagData::createGraphvizContext() {

  _graphvizContext = gvContext();

}


/**
 *
 */
int DigDagData::freeGraphvizContext() {

  return gvFreeContext(_graphvizContext);
}


/**
 *
 */
void DigDagData::layoutDagStructure(DagStructure *dagStructure, QString layoutAlgorithm) {

  dagStructure->calculateLayout(layoutAlgorithm);

}

/**
 *
 */
void DigDagData::renderToFileDagPattern(int patternIndex, QString format, QString fileName) {

  _patternsList[patternIndex]->getStructure()->renderTofile(format, fileName);

}
