/*  Multimodal Image Segmentation Tool (MIST)  */
/*  Eelke Visser  */

/*  Copyright (c) 2016 University of Oxford  */

/*  CCOPYRIGHT  */

#include <cstdio>
#include <vector>
#include <string>
#include <iostream>
#include <iostream>
#include <fstream>
#include <sstream>

#include <boost/log/trivial.hpp>

extern "C"
{
    #include "gd.h"
    #include "libgdc/gdc.h"
    #include "libgdc/gdchart.h"
}


#include "armawrap/newmat.h"

#include "plotting.h"

using namespace std;
using namespace NEWMAT;

namespace Plotting
{
    static char ylabelfmt[] = "%f";

    void plotpng(const std::vector<ColumnVector> &data, const::vector<std::size_t> &deltas, int steps,
                 int vertex, const std::string &plotname, const std::string &basename)
    {
        BOOST_LOG_TRIVIAL(debug) << "Creating plot with " << data.size() << " lines and " << data[0].Nrows() << " points ...";

        std::vector<float> floats;
        std::size_t points = data[0].Nrows();

        for (std::size_t j = 0; j < data.size(); j++)
        {
            if (data[j].Nrows() != points)
                throw PlottingException("All lines must have the same length");

            std::size_t delta = 0;
            if (steps)
                delta = deltas[j];

            for (std::size_t i = 0; i < delta; i++)
                floats.push_back(data[j](1));

            for (std::size_t i = 0; i < points; i++)
                floats.push_back(data[j](i + 1));

            for (std::size_t i = 0; i < steps - delta; i++)
                floats.push_back(data[j](points));
        }

        std::string basefilename = basename + "_" + std::to_string(vertex) + "_" + plotname;

        GDC_image_type = GDC_PNG;
        GDC_ylabel_fmt = ylabelfmt;
        FILE *outfile = std::fopen((basefilename + ".png").c_str(), "wb");
        GDC_out_graph(data[0].Nrows() * 15, 400, outfile, GDC_LINE, points + steps, NULL, data.size(), floats.data(), NULL);
        std::fclose(outfile);

        /*
        {
            std::ofstream os(basefilename + ".txt");

            for (std::size_t i = 1; i <= data[0].Nrows(); i++)
                for (std::size_t j = 0; j < data.size(); j++)
                    os << data[j](i) << (j == data.size() - 1 ? "\n" : "\t");
        }
        */

        BOOST_LOG_TRIVIAL(debug) << "... done";
    }

    void plotsqlite(const std::vector<ColumnVector> &data, const::vector<std::size_t> &deltas, int steps,
                    int vertex, const std::string &plotname, const std::string &dbfile, int jobid)
    {
        std::ostringstream yvalstr;

        std::size_t vecs = data.size();
        for (std::size_t j = 0; j < vecs; j++)
        {
            yvalstr << "(";

            std::size_t rows = data[j].Nrows();
            for (std::size_t i = 1; i <= rows; i++)
            {
                yvalstr << data[j](i);

                if (i != rows)
                    yvalstr << ", ";
            }

            yvalstr << ")";

            if (j != vecs - 1)
                yvalstr << ", ";
        }

        std::ostringstream deltastr;
        deltastr << "(";

        for (std::size_t i = 0; i < deltas.size(); i++)
        {
            deltastr << deltas[i];

            if (i != deltas.size() - 1)
                deltastr << ", ";
        }

        deltastr << ")";

        BOOST_LOG_TRIVIAL(debug) << "Opening sqlite database";

        sqlite3 *db;
        int sq3result = sqlite3_open_v2(dbfile.c_str(), &db,
                                                SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);

        if (sq3result != SQLITE_OK)
            throw PlottingException("Cannot open sqlite database. Error code: " + std::to_string(sq3result));

        // Wait for up to one hour for the database to be unlocked
        sqlite3_busy_timeout(db, 3600000);

        std::string createtext = std::string("CREATE TABLE IF NOT EXISTS plots "
                               "(jobid INT, vertex INT, name TEXT, yvals TEXT, deltas TEXT, steps INT, "
                               "CONSTRAINT pk PRIMARY KEY (jobid, vertex, name));");

        BOOST_LOG_TRIVIAL(debug) << "Creating table (if non-existent)";

        sqlite3_stmt *createstmt;
        int result = sqlite3_prepare_v2(db, createtext.c_str(), -1, &createstmt, NULL);
        if (result != SQLITE_OK)
            throw PlottingException("sqlite3_prepare_v2() failed for CREATE statement with error code " + std::to_string(result));

        result = sqlite3_step(createstmt);
        if (result != SQLITE_DONE)
            throw PlottingException("sqlite3_step() returned error code " + std::to_string(result)
                                   + " for CREATE statement (SQLITE_DONE was expected)");

        sqlite3_finalize(createstmt);

        BOOST_LOG_TRIVIAL(debug) << "Inserting plot data";

        std::string inserttext = "INSERT INTO plots VALUES (?, ?, ?, ?, ?, ?)";

        sqlite3_stmt *insertstmt;
        result = sqlite3_prepare_v2(db, inserttext.c_str(), -1, &insertstmt, NULL);
        if (result != SQLITE_OK)
            throw PlottingException("sqlite3_prepare_v2() failed for INSERT statement with error code " + std::to_string(result));

        result = sqlite3_bind_int(insertstmt, 1, jobid);
        if (result != SQLITE_OK)
            throw PlottingException("sqlite3_bind_int() failed to bind jobid with error code " + std::to_string(result));

        result = sqlite3_bind_int(insertstmt, 2, vertex);
        if (result != SQLITE_OK)
            throw PlottingException("sqlite3_bind_int() failed to bind vertex with error code " + std::to_string(result));

        result = sqlite3_bind_text(insertstmt, 3, plotname.c_str(), -1, SQLITE_TRANSIENT);
        if (result != SQLITE_OK)
            throw PlottingException("sqlite3_bind_text() failed to bind name with error code " + std::to_string(result));

        result = sqlite3_bind_text(insertstmt, 4, yvalstr.str().c_str(), -1, SQLITE_TRANSIENT);
        if (result != SQLITE_OK)
            throw PlottingException("sqlite3_bind_text() failed to bind yvals with error code " + std::to_string(result));

        result = sqlite3_bind_text(insertstmt, 5, deltastr.str().c_str(), -1, SQLITE_TRANSIENT);
        if (result != SQLITE_OK)
            throw PlottingException("sqlite3_bind_text() failed to bind deltas with error code " + std::to_string(result));

        result = sqlite3_bind_int(insertstmt, 6, steps);
        if (result != SQLITE_OK)
            throw PlottingException("sqlite3_bind_int() failed to bind steps with error code " + std::to_string(result));

        result = sqlite3_step(insertstmt);
        if (result != SQLITE_DONE)
            throw PlottingException("sqlite3_step() returned error code " + std::to_string(result)
                                   + " for INSERT statement (SQLITE_DONE was expected)");

        sqlite3_finalize(insertstmt);

        BOOST_LOG_TRIVIAL(debug) << "Closing database";

        sq3result = sqlite3_close_v2(db);
        if (sq3result != SQLITE_OK)
            throw PlottingException("Cannot close sqlite database. Error code: " + std::to_string(sq3result));
    }
}
