/// \file dtscmerge.cpp
/// Contains the code that will attempt to merge multiple files into a single DTSC file.

#include <string>
#include <vector>
#include <mist/config.h>
#include <mist/dtsc.h>

namespace Converters {
  int getNextFree( std::map<std::string,std::map<int,int> > mapping ){
    int result = 1;
    std::map<std::string,std::map<int,int> >::iterator mapIt;
    std::map<int,int>::iterator subIt;
    if (mapping.size()){
      for (mapIt = mapping.begin(); mapIt != mapping.end(); mapIt++){
        if (mapIt->second.size()){
          for (subIt = mapIt->second.begin(); subIt != mapIt->second.end(); subIt++){
            if (subIt->second >= result){
              result = subIt->second + 1;
            }
          }
        }
      }
    }
    return result;
  }

  struct keyframeInfo{
    std::string fileName;
    int trackID;
    int keyTime;
    int keyBPos;
    int keyNum;
    int keyLen;
    int endBPos;
  };//keyframeInfo struct

  int DTSCMerge(int argc, char ** argv){
    Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION);
    conf.addOption("output", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Filename of the output file.\"}"));
    conf.addOption("input", JSON::fromString("{\"arg_num\":2, \"arg\":\"string\", \"help\":\"Filename of the first input file.\"}"));
    conf.addOption("[additional_inputs ...]", JSON::fromString("{\"arg_num\":3, \"default\":\"\", \"arg\":\"string\", \"help\":\"Filenames of any number of aditional inputs.\"}"));
    conf.parseArgs(argc, argv);

    DTSC::File outFile;
    JSON::Value meta;
    JSON::Value newMeta;
    std::map<std::string,std::map<int, int> > trackMapping;

    bool fullSort = true;
    std::map<std::string, DTSC::File> inFiles;
    std::string outFileName = argv[1];
    std::string tmpFileName;
    for (int i = 2; i < argc; i++){
      tmpFileName = argv[i];
      if (tmpFileName == outFileName){
        fullSort = false;
      }else{
        inFiles.insert(std::pair<std::string,DTSC::File>(tmpFileName,DTSC::File(tmpFileName)));
      }
    }

    if (fullSort){
      outFile = DTSC::File(outFileName, true);
    }else{
      outFile = DTSC::File(outFileName);
      meta = outFile.getMeta();
      newMeta = meta;
      if (meta.isMember("tracks") && meta["tracks"].size() > 0){
        for (JSON::ObjIter trackIt = meta["tracks"].ObjBegin(); trackIt != meta["tracks"].ObjEnd(); trackIt++){
          trackMapping[argv[1]].insert(std::pair<int,int>(trackIt->second["trackid"].asInt(),getNextFree(trackMapping)));
          newMeta["tracks"][trackIt->first]["trackid"] = trackMapping[argv[1]][trackIt->second["trackid"].asInt()];
        }
      }
    }

    std::multimap<int,keyframeInfo> allSorted;

    for (std::map<std::string,DTSC::File>::iterator it = inFiles.begin(); it != inFiles.end(); it++){
      JSON::Value tmpMeta = it->second.getMeta();
      if (tmpMeta.isMember("tracks") && tmpMeta["tracks"].size() > 0){
        for (JSON::ObjIter trackIt = tmpMeta["tracks"].ObjBegin(); trackIt != tmpMeta["tracks"].ObjEnd(); trackIt++){
          long long int oldID = trackIt->second["trackid"].asInt();
          long long int mappedID = getNextFree(trackMapping);
          trackMapping[it->first].insert(std::pair<int,int>(oldID,mappedID));
          trackIt->second["trackid"] = mappedID;
          for (JSON::ArrIter keyIt = trackIt->second["keys"].ArrBegin(); keyIt != trackIt->second["keys"].ArrEnd(); keyIt++){
            keyframeInfo tmpInfo;
            tmpInfo.fileName = it->first;
            tmpInfo.trackID = oldID;
            tmpInfo.keyTime = (*keyIt)["time"].asInt();
            tmpInfo.keyBPos = (*keyIt)["bpos"].asInt();
            tmpInfo.keyNum = (*keyIt)["num"].asInt();
            tmpInfo.keyLen = (*keyIt)["len"].asInt();
            if ((keyIt + 1) != trackIt->second["keys"].ArrEnd()){
              tmpInfo.endBPos = (*(keyIt + 1))["bpos"].asInt();
            }else{
              tmpInfo.endBPos = it->second.getBytePosEOF();
            }
            allSorted.insert(std::pair<int,keyframeInfo>((*keyIt)["time"].asInt(),tmpInfo));
          }
          trackIt->second.removeMember("keys");
          trackIt->second.removeMember("frags");
          newMeta["tracks"][JSON::Value(mappedID).asString()] = trackIt->second;
        }
      }
    }

    if (fullSort){
      meta.null();
      meta["moreheader"] = 0ll;
      std::string tmpWrite = meta.toPacked();
      outFile.writeHeader(tmpWrite,true);
    }

    std::set<int> trackSelector;
    for (std::multimap<int,keyframeInfo>::iterator sortIt = allSorted.begin(); sortIt != allSorted.end(); sortIt++){
      trackSelector.clear();
      trackSelector.insert(sortIt->second.trackID);
      inFiles[sortIt->second.fileName].selectTracks(trackSelector);
      inFiles[sortIt->second.fileName].seek_time(sortIt->second.keyTime);
      inFiles[sortIt->second.fileName].seekNext();
      while (inFiles[sortIt->second.fileName].getJSON() && inFiles[sortIt->second.fileName].getBytePos() < sortIt->second.endBPos && !inFiles[sortIt->second.fileName].reachedEOF()){
        inFiles[sortIt->second.fileName].getJSON()["trackid"] = trackMapping[sortIt->second.fileName][inFiles[sortIt->second.fileName].getJSON()["trackid"].asInt()];
        outFile.writePacket(inFiles[sortIt->second.fileName].getJSON());
        inFiles[sortIt->second.fileName].seekNext();
      }
    }

    std::string writeMeta = newMeta.toPacked();
    meta["moreheader"] = outFile.addHeader(writeMeta);
    writeMeta = meta.toPacked();
    outFile.writeHeader(writeMeta);

    return 0;
  }
}

int main(int argc, char ** argv){
  return Converters::DTSCMerge(argc, argv);
}