/*
 * checkpoint.cpp
 *
 *  Created on: Jun 12, 2014
 *      Author: minh
 */

#include "checkpoint.h"
#include "tools.h"
#include "timeutil.h"
#include "gzstream.h"
#include <cstdio>

const char* CKP_HEADER =     "--- # IQ-TREE Checkpoint ver >= 1.6";
const char* CKP_HEADER_OLD = "--- # IQ-TREE Checkpoint";

Checkpoint::Checkpoint() {
	filename = "";
    prev_dump_time = 0;
    dump_interval = 60; // dumping at most once per 60 seconds
    struct_name = "";
    compression = true;
    header = CKP_HEADER;
}


Checkpoint::~Checkpoint() {
}


void Checkpoint::setFileName(string filename) {
	this->filename = filename;
}


void Checkpoint::load(istream &in) {
    string line;
    string struct_name;
    size_t pos;
    int listid = 0;
    while (!in.eof()) {
        safeGetline(in, line);
        pos = line.find('#');
        if (pos != string::npos)
            line.erase(pos);
        line.erase(line.find_last_not_of("\n\r\t")+1);
//            trimString(line);
        if (line.empty()) continue;
        if (line[0] != ' ') {
            struct_name = "";
        }
//            trimString(line);
        line.erase(0, line.find_first_not_of(" \n\r\t"));
        if (line.empty()) continue;
        pos = line.find(": ");
        if (pos != string::npos) {
            // mapping
            (*this)[struct_name + line.substr(0, pos)] = line.substr(pos+2);
        } else if (line[line.length()-1] == ':') {
            // start a new struct
            line.erase(line.length()-1);
            trimString(line);
            struct_name = line + CKP_SEP;
            listid = 0;
            continue;
        } else {
            // collection
            (*this)[struct_name + convertIntToString(listid)] = line;
            listid++;
        }
    }
}


bool Checkpoint::load() {
	ASSERT(filename != "");
    if (!fileExists(filename)) return false;
    try {
        igzstream in;
        // set the failbit and badbit
        in.exceptions(ios::failbit | ios::badbit);
        in.open(filename.c_str());
        // remove the failbit
        in.exceptions(ios::badbit);
        string line;
        if (!safeGetline(in, line)) {
            in.close();
            return false;
        }
        if (line == CKP_HEADER_OLD)
            throw "Incompatible checkpoint file from version 1.5.X or older.\nEither overwrite it with -redo option or run older version";
        if (line != header)
        	throw ("Invalid checkpoint file " + filename);
        // call load from the stream
        load(in);
        in.clear();
        // set the failbit again
        in.exceptions(ios::failbit | ios::badbit);
        in.close();
        return true;
    } catch (ios::failure &) {
        outError(ERR_READ_INPUT);
    } catch (const char *str) {
        outError(str);
    } catch (string &str) {
        outError(str);
    }
    return false;
}

void Checkpoint::setCompression(bool compression) {
    this->compression = compression;
}

/**
    set the header line to overwrite the default header
    @param header header line
*/
void Checkpoint::setHeader(string header) {
    this->header = "--- # " + header;
}

void Checkpoint::setDumpInterval(double interval) {
    dump_interval = interval;
}

void Checkpoint::dump(ostream &out) {
    string struct_name;
    size_t pos;
    int listid = 0;
    for (iterator i = begin(); i != end(); i++) {
        if ((pos = i->first.find(CKP_SEP)) != string::npos) {
            if (struct_name != i->first.substr(0, pos)) {
                struct_name = i->first.substr(0, pos);
                out << struct_name << ':' << endl;
                listid = 0;
            }
            // check if key is a collection
            out << ' ' << i->first.substr(pos+1) << ": " << i->second << endl;
        } else
            out << i->first << ": " << i->second << endl;
    }
}

void Checkpoint::dump(bool force) {
    if (filename == "")
        return;
        
    if (!force && getRealTime() < prev_dump_time + dump_interval) {
        return;
    }
    prev_dump_time = getRealTime();
    string filename_tmp = filename + ".tmp";
    if (fileExists(filename_tmp)) {
        outWarning("IQ-TREE was killed while writing temporary checkpoint file " + filename_tmp);
        outWarning("You should increase checkpoint interval from the default 60 seconds");
        outWarning("via -cptime option to avoid too frequent checkpoint for large datasets");
    }
    try {
        ostream *out;
        if (compression) 
            out = new ogzstream(filename_tmp.c_str());
        else
            out = new ofstream(filename_tmp.c_str());
        out->exceptions(ios::failbit | ios::badbit);
        *out << header << endl;
        // call dump stream
        dump(*out);
        if (compression)
            ((ogzstream*)out)->close();
        else
            ((ofstream*)out)->close();
        delete out;
//        cout << "Checkpoint dumped" << endl;
        if (fileExists(filename)) {
            if (std::remove(filename.c_str()) != 0)
                outError("Cannot remove file ", filename);
        }
        if (std::rename(filename_tmp.c_str(), filename.c_str()) != 0)
            outError("Cannot rename file ", filename_tmp);
    } catch (ios::failure &) {
        outError(ERR_WRITE_OUTPUT, filename.c_str());
    }
}

bool Checkpoint::hasKey(string key) {
	return (find(key) != end());
}

bool Checkpoint::hasKeyPrefix(string key_prefix) {
    string prefix = key_prefix;
    if (!struct_name.empty())
        prefix = struct_name + key_prefix;
	auto i = lower_bound(prefix);
    if (i != end()) {
        if (i->first.compare(0, prefix.size(), prefix) == 0)
            return true;
    }
    return false;
}

int Checkpoint::eraseKeyPrefix(string key_prefix) {
    int count = 0;
    iterator first_it = lower_bound(key_prefix);
    iterator i;
	for (i = first_it; i != end(); i++) {
        if (i->first.compare(0, key_prefix.size(), key_prefix) == 0)
            count++;
        else
            break;

    }
    if (count)
        erase(first_it, i);
    return count;
}

int Checkpoint::keepKeyPrefix(string key_prefix) {
    map<string,string> newckp;
    int count = 0;
    erase(begin(), lower_bound(key_prefix));
    
    for (iterator i = begin(); i != end(); i++) {
        if (i->first.compare(0, key_prefix.size(), key_prefix) == 0)
            count++;
        else {
            erase(i, end());
            break;
        }
        
    }
    return count;
}

/*-------------------------------------------------------------
 * series of get function to get value of a key
 *-------------------------------------------------------------*/

bool Checkpoint::getBool(string key, bool &ret) {
    string value;
    if (!get(key, value)) return false;
	if (value == "true") 
        ret = true;
    else if (value == "false") 
        ret = false;
    else
        outError("Invalid boolean value " + value + " for key " + key);
    return true;
}

bool Checkpoint::getBool(string key) {
    bool ret;
    if (!getBool(key, ret))
        return false;
    return ret;
}

/*-------------------------------------------------------------
 * series of put function to put pair of (key,value)
 *-------------------------------------------------------------*/

void Checkpoint::putBool(string key, bool value) {
    if (value)
        put(key, "true");
    else
        put(key, "false");
}


/*-------------------------------------------------------------
 * nested structures
 *-------------------------------------------------------------*/
void Checkpoint::startStruct(string name) {
    struct_name = struct_name + name + CKP_SEP;
}

/**
    end the current struct
*/
void Checkpoint::endStruct() {
    size_t pos = struct_name.find_last_of(CKP_SEP, struct_name.length()-2);
    if (pos == string::npos)
        struct_name = "";
    else
        struct_name.erase(pos+1);
}

void Checkpoint::startList(int nelem) {
    list_element.push_back(-1);
    if (nelem > 0)
        list_element_precision.push_back((int)ceil(log10(nelem)));
    else
        list_element_precision.push_back(0);
}

void Checkpoint::setListElement(int id) {
    list_element.back() = id;
    stringstream ss;
    ss << setw(list_element_precision.back()) << setfill('0') << list_element.back();
    struct_name += ss.str() + CKP_SEP;
}

void Checkpoint::addListElement() {
    list_element.back()++;
    if (list_element.back() > 0) {
        size_t pos = struct_name.find_last_of(CKP_SEP, struct_name.length()-2);
        ASSERT(pos != string::npos);
        struct_name.erase(pos+1);
    }
    stringstream ss;
    ss << setw(list_element_precision.back()) << setfill('0') << list_element.back();
//    ss << list_element.back();
    struct_name += ss.str() + CKP_SEP;
}

void Checkpoint::endList() {
    ASSERT(!list_element.empty());

    if (list_element.back() >= 0) {
        size_t pos = struct_name.find_last_of(CKP_SEP, struct_name.length()-2);
        ASSERT(pos != string::npos);
        struct_name.erase(pos+1);
    }

    list_element.pop_back();
    list_element_precision.pop_back();

}

/*
void Checkpoint::getSubCheckpoint(Checkpoint *target, string partial_key) {
    for (iterator it = begin(); it != end(); it++) {
        if (it->first.find(partial_key) != string::npos)
            (*target)[it->first] = it->second;
    }
}
*/

/*-------------------------------------------------------------
 * CheckpointFactory
 *-------------------------------------------------------------*/

CheckpointFactory::CheckpointFactory() {
    checkpoint = NULL;
}

void CheckpointFactory::setCheckpoint(Checkpoint *checkpoint) {
    this->checkpoint = checkpoint;
}

Checkpoint *CheckpointFactory::getCheckpoint() {
    return checkpoint;
}

void CheckpointFactory::startCheckpoint() {
    checkpoint->startStruct("CheckpointFactory");
}

void CheckpointFactory::saveCheckpoint() {
    // do nothing
}

void CheckpointFactory::restoreCheckpoint() {
    // do nothing
}

void CheckpointFactory::endCheckpoint() {
    checkpoint->endStruct();
}

