#include "simulation.h"

using namespace std;

/** Random Number Generator **/
boost::mt19937 random_engine;

Simulation::Simulation(Net &net, Trial &trial) : net(net), trial(trial) {
    quiet = false;
}

bool Simulation::modify(std::string ID, Range const val) {

    // Split up the ID into its three parts.
    string type_ID;
    string item_ID;
    string param_ID;
    size_t pos;
    pos = ID.find('.');
    type_ID = ID.substr(0, pos);
    string sID = ID.substr(pos + 1);
    pos = sID.find('.');
    item_ID = sID.substr(0, pos);
    param_ID = sID.substr(pos + 1);

    if (type_ID == "population") {
        if (this->net.populations.find(item_ID) != this->net.populations.end()) {
            if (this->net.populations[item_ID].params.vals.find(param_ID) != this->net.populations[item_ID].params.vals.end()) {
                this->net.populations[item_ID].params.vals[param_ID] = val;
                return true;
            }
        }
    }
    if (type_ID == "connection") {
        string from, to;
        pos = item_ID.find(':');
        from = item_ID.substr(0, pos);
        to = item_ID.substr(pos + 1);
        if (this->net.connections.find(to) != this->net.connections.end()) {
            if (this->net.connections[to].find(from) != this->net.connections[to].end()) {
                if (param_ID == "weight") {
                    this->net.connections[to][from].weight = val;
                    return true;
                }
                if (param_ID == "delay") {
                    this->net.connections[to][from].delay = val;
                    return true;
                }
                if (param_ID == "density") {
                    this->net.connections[to][from].density = val;
                    return true;
                }
            }
        }
    }
    cerr << "[X] " << ID << " failed to modify the network." << endl;
    return false;
}

void Simulation::runSimulation(Results::Result *r, double T, double dt, double delay, bool voltage, bool quiet) {

    // Run all the results given to us in the vector
    // Each thread runs multiple results to cut down on thread management overhead
    double input;
    double new_input;
    double tau;
    unsigned int steps = (unsigned int) (T / dt);

    map<string, Population::ConstrainedPopulation>::const_iterator cpIter;
    list<Neuron*>::const_iterator nIter;
    map<string, Net::Connection<double> >::const_iterator fromIter;


	// Jitter the neurons
	for (cpIter = r->cNetwork.populations.begin(); cpIter != r->cNetwork.populations.end(); ++cpIter) {
		for (nIter = cpIter->second.neurons.begin(); nIter != cpIter->second.neurons.end(); ++nIter) { // Loop over neurons
			(*nIter)->jitter();
		}
	}

    for (unsigned int ts = 0; ts < steps; ++ts) { // Loop over time steps
        for (cpIter = r->cNetwork.populations.begin(); cpIter != r->cNetwork.populations.end(); ++cpIter) {

            input = 0.0;
            // Find spikes into our population				
            for (fromIter = r->cNetwork.connections[cpIter->second.ID].begin();
                    fromIter != r->cNetwork.connections[cpIter->second.ID].end();
                    ++fromIter) {
                new_input = 0;
                if (fromIter->second.weight > 0) tau = 0.7;
                else tau = 1.1;
                new_input += r->cNetwork.alpha(ts*dt, r->cNetwork.populations[fromIter->first].neurons, tau, fromIter->second.delay, delay, dt) * fromIter->second.weight;
                input += new_input / (double) (r->cNetwork.populations[fromIter->first].neurons.size());
            }
            // Add our input signal in
            if (cpIter->second.accept_input != 0) {
                input += r->cTrial.values[ts] * cpIter->second.accept_input;
            }
            if (cpIter->second.spontaneous) {
                input = 1; // Default input of 1
            }

            for (nIter = cpIter->second.neurons.begin(); nIter != cpIter->second.neurons.end(); ++nIter) { // Loop over neurons
                // Update our neuron
                (*nIter)->update(input, ts, dt);
            }
        }
    }

    // Delete the voltage if we don't want to save it.
    if (!voltage) {
        for (cpIter = r->cNetwork.populations.begin(); cpIter != r->cNetwork.populations.end(); ++cpIter) {
            for (nIter = cpIter->second.neurons.begin(); nIter != cpIter->second.neurons.end(); ++nIter) { // Loop over neurons
                (*nIter)->voltage.clear();
            }
        }
    }
}

bool Simulation::simulationProgress(boost::threadpool::pool &tp, int total, boost::posix_time::ptime start) {

    boost::posix_time::ptime now(boost::posix_time::microsec_clock::local_time());
    int pending = tp.pending();
    int active = tp.active();

    if (tp.pending() == 0) {
        cout << endl;
        return false;
    }

    double percent_done = (double) (total - pending) / (double) total;
    boost::posix_time::time_duration dur = now - start;
    double time_left = (double) dur.total_microseconds() / percent_done;
    boost::posix_time::time_duration left = boost::posix_time::microseconds(time_left) - dur;

    cout << "\r[" << (int) (percent_done * 100) << "%] ";
    cout << "[" << pending << "/" << total << "] ";
    cout << "[" << (active - 1) << " active] ";
    cout << left << " remaining." << flush;

    return true;
}

bool Simulation::run(Results &results, string filename, double T, double dt, double delay, int number_of_trials, bool voltage, boost::threadpool::pool &tp) {

    vector<Trial::ConstrainedTrial>* inputs = this->trial.inputFactory(T, dt, delay);
    vector<Net::ConstrainedNetwork>* networks = this->net.networkFactory();
    vector<double> timesteps = this->genTimeSeries(T, dt, delay);

    int total = number_of_trials * inputs->size() * networks->size();
    int steps = (int) (T / dt);

    results = Results(T, dt, delay);
    results.timeseries = timesteps;

    /**************************
     * INITIALIZE SIMULATIONS *
     **************************/
    if (filename != "") {
        ofstream fstr;
        fstr.open(filename.c_str(), fstream::out);
        if (fstr.fail()) {
            cout << "[X] Unable to open " << filename << " for writing." << endl;
            return false;
        }
        fstr.close();
    }
    if (!quiet) 
    {
        cout << "Initializing " << total << " Simulations ";
        if (voltage) cout << "with voltage traces." << endl;
        if (!voltage) cout << "without voltage traces." << endl;
    }
    const int progress_width = 50;
    int progress = 0;
    string progress_done;
    string progress_left;
    results.init(total);
    // Steal the unconstrained IDs from the trial and network.
    for (map<string, Range>::iterator iter = this->net.unconstrained.begin(); iter != this->net.unconstrained.end(); ++iter) {
        results.unconstrained[iter->first] = iter->second;
    }
    for (map<string, Range>::iterator iter = this->trial.unconstrained.begin(); iter != this->trial.unconstrained.end(); ++iter) {
        results.unconstrained[iter->first] = iter->second;
    }

    for (vector<Trial::ConstrainedTrial>::iterator t = inputs->begin(); t != inputs->end(); ++t) {
        for (vector<Net::ConstrainedNetwork>::iterator n = networks->begin(); n != networks->end(); ++n) {
            for (int i = 0; i < number_of_trials; ++i) {
                // Initialize the neurons
                map<string, Population::ConstrainedPopulation>::iterator pops;
                list<Neuron*>::iterator neurons;
                for (pops = n->populations.begin(); pops != n->populations.end(); ++pops) {
                    for (neurons = pops->second.neurons.begin(); neurons != pops->second.neurons.end(); ++neurons) {
                        (*neurons)->init(steps, delay);
                    }
                }

                // Create the Result container
                Results::Result r;

                // Make copies of the neurons
                for (pops = n->populations.begin(); pops != n->populations.end(); ++pops) {
                    list<Neuron*> copiedNeurons;
                    for (neurons = pops->second.neurons.begin(); neurons != pops->second.neurons.end(); ++neurons) {
                        copiedNeurons.push_back((*neurons)->clone());
                    }
                    pops->second.neurons.assign(copiedNeurons.begin(), copiedNeurons.end());
                }

                r.cNetwork = *n;
                r.cTrial = *t;
                r.trial_num = i;
                r.result_set = 0;
                results.add(r);
            }

            if (!quiet)
            {
            progress += number_of_trials;
            progress_done = string((int) (progress_width * ((float) progress / (float) total)), '*');
            progress_left = string(progress_width * (1 - ((float) progress / (float) total)), ' ');
            cout << "\r[" << progress_done << progress_left << "]" << flush;
            }
        }
    }
    if (!quiet)
    {
    progress_done = string(progress_width, '*');
    cout << "\r[" << progress_done << progress_left << "]" << endl;
    }


    /*******************
     * RUN SIMULATIONS *
     *******************/
    boost::posix_time::ptime start(boost::posix_time::microsec_clock::local_time());
    if (!quiet) 
    {
        cout << "Running " << networks->size() << " networks against " << inputs->size() << " inputs over " << number_of_trials << " trials." << endl;
        tp.schedule(boost::threadpool::looped_task_func(boost::bind(&Simulation::simulationProgress, tp, total, start), 1000));
        cout << "Running " << results.results.size() << " Simulations ..." << endl;
    }
    for (int r_index = 0; r_index < total; ++r_index) {
        tp.schedule(boost::bind(&runSimulation, &results.results[r_index], T, dt, delay, voltage, quiet));
    }

    tp.wait();
    if (!quiet)
    {
        boost::posix_time::ptime end(boost::posix_time::microsec_clock::local_time());
        boost::posix_time::time_duration dur = end - start;
        cout << "Completed in " << dur << endl;
    }

    if (filename != "") {
        if (!quiet) cout << "Saving simulation..." << endl;
        results.save(filename);
    } else {
        if (!quiet) cout << "[WARNING] Simulation WAS NOT SAVED!" << endl;
    }
    return true;
}

string Simulation::toString() {
    string r;
    r = "A Simulation\n";
    return r;
}

vector<double> Simulation::genTimeSeries(double T, double dt, double delay) {
    vector<double> timesteps;
    unsigned int steps = (int) (T / dt);
    timesteps.resize(steps);
    for (unsigned int i = 0; i < steps; ++i) {
        timesteps[i] = i * dt - delay;
    }
    return timesteps;
}