#include <fstream>
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>

#include <productiondatabaseclient.h>
#include <boost/program_options.hpp>

using namespace std;
using namespace ProductionDatabase;
namespace po = boost::program_options;

struct unitInfo {
    std::string redSerial = "";
    std::string blueSerial = "";
    std::string crystalSerial = "";
    uint barCode = 0;
};

static string fileName = "serials.dat";
static int boxNo = 0;
static bool debug = false;
static string username = "";
static string password = "";

std::string moduleSN;
static bool makeCapsules = true;
static bool makeUnits = true;
static bool assignToModule = false;

static vector<unitInfo> newUnits;

void processArgumentsAndQueryMissing(int m_argc, char* m_argv[]);
void loadSerialsFromFileName(string m_fileName, bool m_debug = false);

int main(int argc, char* argv[]) {
    processArgumentsAndQueryMissing(argc, argv);
    ProductionDatabaseClient *proddb = new ProductionDatabaseClient();

    if (debug) cout << "Intitialized apdBoxSetter with version " << proddb->getVersion() << " of the database access libraries." << endl << endl
                    << "Now trying to set Batch number " << boxNo << " for ADPs from serial file " << fileName << endl;

    loadSerialsFromFileName(fileName);

    if (username == "" || password == "") proddb->queryCredentials();
    else proddb->setCredentials(username, password);
    DatabaseClientResponse response = proddb->checkConnectivityAndCredentials();
    if (response != Successful ) {
        cerr << "Connection to database failed because of error: " << response << endl;
        return response;
    }
    if (debug) cout << "Connection successful!" << endl;

    int nFailed = 0;

    std::vector<uint> barcodes;
    if (assignToModule && newUnits.size() == 8)
        for (size_t i = 0; i < 8; i++)
            barcodes.push_back(0);

    for (auto newUnit = newUnits.begin(); newUnit < newUnits.end(); newUnit++) {
        try {
            if (makeCapsules) {
                proddb->createApdCapsule(newUnit->redSerial, newUnit->blueSerial);
            }
            if (makeUnits) {
                std::string capsuleSerial = newUnit->blueSerial + "/" + newUnit->redSerial;
                proddb->createApdUnit(capsuleSerial, newUnit->crystalSerial, std::to_string(newUnit->barCode));
            }
            if (assignToModule) {
                barcodes.push_back(newUnit->barCode);
            }
        }
        catch (std::exception &e) {
            nFailed++;
            std::cerr << "An error occurred on APD pair with red APD " << newUnit->redSerial << " and blue APD " << newUnit->blueSerial << "!" << std::endl;
            std::cerr << e.what() << std::endl << std::endl;
            continue;
        }
    }

    if (assignToModule) {
        if (barcodes.size() != 16)
            cerr << "Assignments are only possible if there are 8 or 16 Units in file!" << std::endl;
        else {
            try {
                proddb->assignUnitToModule(moduleSN, barcodes);
            }
            catch (std::exception &e) {
                std::cerr << "An error occurred while assigning APDs to Module " << moduleSN << std::endl;
                std::cerr << e.what() << std::endl << std::endl;
            }
        }
    }

    cout << "Created " << newUnits.size() << " APD Capsules and assigned them to Units." << std::endl;
    if (nFailed > 0)
        cerr << "Data entry failed for " << nFailed << " entries!" << std::endl;

    return (-newUnits.empty());
}

void processArgumentsAndQueryMissing(int m_argc, char* m_argv[]) {
    po::options_description desc("Available options");
    desc.add_options()
            ("help", "produce help message")
            ("fileName", po::value<string>(), "file name or serial numbers [serials.dat]")
            ("noMakeCapsules", "Skip the step of creating capsules from APDs")
            ("noMakeUnits", "Skip the step of creating units from capsules")
            ("assignToModule", po::value<string>(), "Skip the step of assigning units to submodules")
            ("debug", "emit additional messages for debugging purposes")
            ("user", po::value<string>(), "User name to connect to DB. Only used when combined with pass!")
            ("pass", po::value<string>(), "Password to connect to DB. Only used when combined with user!")
            ;
    po::variables_map vm;
    po::store(po::parse_command_line(m_argc, m_argv, desc), vm);
    po::notify(vm);

    if (vm.count("help")) {
        cout << desc << "\n";
        exit(0);
    }
    if (vm.count("fileName")) {
        fileName = vm["fileName"].as<string>();
        cout << "Reading serials from " << fileName << endl;
    }
    else {
        throw "Please enter a file name!";
    }
    if (vm.count("noMakeCapsules")) {
        cout << "Skipping capsule creation. Only works if capsules already exist!" << endl;
        makeCapsules = false;
    }
    if (vm.count("noMakeUnits")) {
        cout << "Skipping unit creation. Module assignment only works if units already exist!" << endl;
        makeUnits= false;
    }
    if (vm.count("assignToModule")) {
        moduleSN = vm["assignToModule"].as<string>();
        cout << "Will assign units to submodule " << moduleSN << std::endl;
        assignToModule = true;
    }
    if (vm.count("user")) {
        username = vm["user"].as<string>();
    }
    if (vm.count("pass")) {
        password = vm["pass"].as<string>();
    }
    if (vm.count("debug")) {
        debug = true;
    }
}

void loadSerialsFromFileName(string m_fileName, bool m_debug) {
    ifstream in(m_fileName.c_str());
    if (!in.good()) {
        cerr << "Could not load serials. Does the input file exist?" << endl;
        exit(0);
    }

    newUnits.clear();

    int pos = 1;

    string line = "";
    while (in.good()) {
        getline(in,line);
        if (line.length() == 0 || !isdigit(line[0])) {
          pos++;
          continue;
        }
        unitInfo newUnit;

        uint barcode;

        std::stringstream linestream(line);
        linestream >> newUnit.redSerial >> newUnit.blueSerial >> newUnit.crystalSerial >> barcode;

        if (barcode == 0) continue;
        if (barcode <1000000 )
        barcode += 1309000000;
        newUnit.barCode = barcode;

        std::cout << "Found new unit with Red = " << newUnit.redSerial << ", Blue = " << newUnit.blueSerial << ", xtal = " << newUnit.crystalSerial << ", Barcode = " << newUnit.barCode << std::endl;
        
        newUnits.push_back(newUnit);
    }

    cerr << "Found " << newUnits.size() << " new Units to be entered." << endl;
    return;
}