
#include <cassert>
#include <string>
#include <cstdlib>
#include <../../libxml++-2.6.1/libxml++/parsers/saxparser.h>
#include "ap_result.h"
#include "parse.h"
#include "sched_config.h"
#include <base64.C>
using namespace std;

extern SCHED_CONFIG config;

const int MIN_PROTOCOL = 3;
const int MAX_PROTOCOL = 3;

class AstropulsePulseOutParser : public xmlpp::SaxParser
{
    // virtual void parse_file(const std::string& filename) throw(exception);
    ResultFile& resultfile;
    Pulse curPulse;
    void newPulse()  { curPulse.clear(); }
    void savePulse() { resultfile.pulses.push_back(curPulse); }
    void saveBestPulse() { resultfile.best_pulses.push_back(curPulse); }
    void getProtocol(int protocol) {
        if (protocol < MIN_PROTOCOL || protocol > MAX_PROTOCOL) {
            throw InvalidPulseOut("invalid protocol");
        }
        resultfile.protocol = protocol;
    }

    static void getTimeSeries(void* destination, string const& base64_encoded_string) throw(InvalidPulseOut)
    {
        try {
            string d = r_base64_decode(base64_encoded_string.c_str(), strlen(base64_encoded_string.c_str()));
            if (d.length() > TIME_SERIES_LENGTH) {
                throw InvalidPulseOut("time series too long");
            }
            memcpy(destination, d.c_str(), TIME_SERIES_LENGTH);
        } catch (InvalidBase64Exception&) {
            throw InvalidPulseOut("invalid base 64");
        }
    }

    enum XML_STATE {
        T,
        T_APO,
        T_APO_T,
        T_APO_T_T,
        T_APO_P,
        T_APO_P_P,
        T_APO_P_P_I,
        T_APO_P_P_PP,
        T_APO_P_P_S,
        T_APO_P_P_D,
        T_APO_P_P_P,
        T_APO_P_P_TS,
        T_APO_BP,
        T_APO_BP_P,
        T_APO_BP_P_I,
        T_APO_BP_P_PP,
        T_APO_BP_P_S,
        T_APO_BP_P_D,
        T_APO_BP_P_P
    } curState;
    int curThresholdNum;

public:
    AstropulsePulseOutParser(ResultFile & resultfile_) :
        resultfile(resultfile_)
    {}

    virtual void on_start_element(const std::string& name, const AttributeMap& attributes)
    {
        // cerr << "## on_start_element " << name << " [state=" << curState << "]\n";
        switch(curState) {
#define SX(N,C,X) do { if (#N == name) { curState = C; X return; } } while(0);
#define S(N,C) do { if (#N == name) { curState = C; return; } } while(0);
#define ATTRINT(A) atoi(attributes.find(#A)->second.c_str())

        case T:
            SX(astropulse_output , T_APO, {getProtocol(ATTRINT(protocol));});
            break;
        case T_APO:
            S(thresholds , T_APO_T);
            S(pulses     , T_APO_P);
            S(best_pulse , T_APO_BP);
            break;
        case T_APO_T:
            SX(threshold , T_APO_T_T, {curThresholdNum = ATTRINT(coadd);});
            break;
        case T_APO_P:
            SX(pulse     , T_APO_P_P, newPulse(););
            break;
        case T_APO_P_P:
            S(index      , T_APO_P_P_I);
            S(peak_power , T_APO_P_P_PP);
            S(scale      , T_APO_P_P_S);
            S(dm         , T_APO_P_P_D);
            S(period     , T_APO_P_P_P);
            S(time_series, T_APO_P_P_TS);
            break;
        case T_APO_BP:
            SX(pulse     , T_APO_BP_P, newPulse(););
            break;
        case T_APO_BP_P:
            S(index      , T_APO_BP_P_I);
            S(peak_power , T_APO_BP_P_PP);
            S(scale      , T_APO_BP_P_S);
            S(dm         , T_APO_BP_P_D);
            S(period     , T_APO_BP_P_P);
            break;
#undef S
#undef SX
        default: assert(0);
        }
        throw InvalidPulseOut("invalid element");
    }

    virtual void  on_characters (const std::string &characters)
    {
        switch (curState) {
#define INT() atol(characters.c_str())
#define FLOAT() atof(characters.c_str())
        case T_APO_T_T:    resultfile.thresholds[curThresholdNum] = INT(); return;
        case T_APO_P_P_I:  curPulse.index = INT()        ; return;
        case T_APO_P_P_PP: curPulse.peak_power = FLOAT() ; return;
        case T_APO_P_P_S:  curPulse.scale = INT()        ; return;
        case T_APO_P_P_D:  curPulse.dm = INT()           ; return;
        case T_APO_P_P_P:  curPulse.period = FLOAT()     ; return;
        case T_APO_P_P_TS: getTimeSeries(curPulse.time_series, characters); return;
        case T_APO_BP_P_I:   curPulse.index = INT()        ; return;
        case T_APO_BP_P_PP:  curPulse.peak_power = FLOAT() ; return;
        case T_APO_BP_P_S:   curPulse.scale = INT()        ; return;
        case T_APO_BP_P_D:   curPulse.dm = INT()           ; return;
        case T_APO_BP_P_P:   curPulse.period = FLOAT()     ; return;
#undef INT
#undef FLOAT
        default: break;
        }
    }

    virtual void on_end_element(const std::string& name)
    {
        switch(curState) {
#define S(C) curState = C; return
#define SX(C,X) curState = C; X return
        case T_APO:         S(T);
        case T_APO_T:       S(T_APO);
        case T_APO_T_T:     S(T_APO_T);
        case T_APO_P:       S(T_APO);
        case T_APO_P_P:     SX(T_APO_P, savePulse(););
        case T_APO_P_P_I:
        case T_APO_P_P_PP:
        case T_APO_P_P_S:
        case T_APO_P_P_D:
        case T_APO_P_P_P:
        case T_APO_P_P_TS:  S(T_APO_P_P);
        case T_APO_BP:      S(T_APO);
        case T_APO_BP_P:    SX(T_APO_BP, saveBestPulse(););
        case T_APO_BP_P_I:
        case T_APO_BP_P_PP:
        case T_APO_BP_P_S:
        case T_APO_BP_P_D:
        case T_APO_BP_P_P:  S(T_APO_BP_P);
#undef S
#undef SX
        default: assert(0);
        }
    }

    virtual void on_end_document()
    {
        if (curState != 0) {
            throw InvalidPulseOut("end of document inside body");
        }
    }
};

void ResultFile::parse_pulse_out(const char* filename) throw(InvalidPulseOut)
{
    AstropulsePulseOutParser p(*this);
    p.parse_file(filename);
}

// Sort pulses lexicographically using:
//       index, peak_power, dm, scale, period
inline bool operator<(Pulse const& p1, Pulse const& p2)
{
    if (p1.index < p2.index) return true;
    if (p1.index > p2.index) return false;

    if (p1.peak_power < p2.peak_power) return true;
    if (p1.peak_power > p2.peak_power) return false;

    if (p1.dm < p2.dm) return true;
    if (p1.dm > p2.dm) return false;

    if (p1.scale < p2.scale) return true;
    if (p1.scale > p2.scale) return false;

    if (p1.period < p2.period) return true;
    if (p1.period > p2.period) return false;

    return false;
}

void ResultFile::sort()
{
    std::sort(pulses.begin(), pulses.end());
}

// get the name of a result's (first) output file
string get_output_file_path(RESULT const& result) throw(ResultError)
{
    string buf;
    if (!parse_str(result.xml_doc_in, "<name>", buf)) {
        throw ResultError("get_output_file_path", "couldn't get output filename");
    }

    return string(config.upload_dir) + '/' + buf;
}

ResultFile::ResultFile(RESULT const& result) throw(ResultError) {
    protocol = 0;
    memset(thresholds, 0, sizeof(thresholds));

    string filename = get_output_file_path(result);
    parse_pulse_out(filename.c_str());
    // filter_by_server_threshold();

    // I don't think we should sort! -- quarl 2003-11-29

    // sort();
}
