//
// Created by jholder on 22.10.21.
//

//#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE

#include "Compute.h"

#include <iostream>
#include <utility>

#include <spdlog/spdlog.h>
#include "Data/Rod2d.hpp"
#include "Data/Simulation.h"

void Compute::evalMSD(const Rod2d &rod2D) {
    SPDLOG_TRACE("Evaluating MSD");
    const auto &new_pos = rod2D.getPos();
    auto old_pos = start_rod.getPos();
    auto diff = new_pos - old_pos;
    auto msd = diff.dot(diff);
    SPDLOG_TRACE("Feeding {}", msd);
    agg.feed(msd);
}

void Compute::evalX(const Rod2d &rod2D) {
    SPDLOG_TRACE("Evaluating X");
    const auto &new_pos = rod2D.getPos();
    auto msd = (new_pos[0]);
    agg.feed(msd);
    SPDLOG_TRACE("Feeding {}", msd);
}

void Compute::evalX_squared(const Rod2d &rod2D) {
    const auto &new_pos = rod2D.getPos();
    auto msd = pow(new_pos[0], 2.0);
    agg.feed(msd);
}

void Compute::evalOAF(const Rod2d &rod2D) {
    const auto &new_e = rod2D.getE();
    auto old_e = start_rod.getE();
    auto oaf = old_e.dot(new_e);
    agg.feed(oaf);
}

void Compute::eval_empXX(const Rod2d &rod2D) {
    const auto &new_pos = rod2D.getPos();
    auto old_pos = start_rod.getPos();
    auto old_e = start_rod.getE();
    double empxx = pow((new_pos - old_pos).dot(old_e), 2) / 2 / time;
    agg.feed(empxx);
}

void Compute::eval_empYY(const Rod2d &rod2D) {
    const auto &new_pos = rod2D.getPos();
    auto old_pos = start_rod.getPos();
    auto old_e = start_rod.getE();
    Eigen::Vector2d old_e_ortho = {-old_e[1], old_e[0]};
    double empxx = pow((new_pos - old_pos).dot(old_e_ortho), 2) / 2 / time;
    agg.feed(empxx);
}

void Compute::eval(const Rod2d &rod2D) {
    time_step++;
    SPDLOG_TRACE("resetting: {} time_step {}", resetting, time_step);
    if (time_step % every == 0){
        if (resetting or time_step == every) {
            switch (type) {
                case Type::msd:
                    evalMSD(rod2D);
                    break;
                case Type::oaf:
                    evalOAF(rod2D);
                    break;
                case Type::empxx:
                    eval_empXX(rod2D);
                    break;
                case Type::empyy:
                    eval_empYY(rod2D);
                    break;
                case Type::x:
                    evalX(rod2D);
                    break;
                case Type::x_squared:
                    evalX_squared(rod2D);
                    break;
            }
            start_rod = rod2D;
        }
    }
}
Compute::Compute(Rod2d rod, Type t_type, Force force, size_t t_every,
                 Simulation &sim)
    : start_rod(std::move(rod)),
      every(t_every),
      time_step(0),
      type(t_type),
      time(sim.getMDeltaT() * static_cast<double>(every)) {
    switch (type) {
        case Type::msd: {
            resetting = true;
            type_str = "msd";
            switch (force) {
                case Force::zero_F:
                    target = 2 * (rod.getDiff().trace()) * time;
                    break;
                case Force::const_F:
                    target = 0.0;
                    break;   //TODO missing analytic
                case Force::harmonic_F:
                    target =
                        2 * (1 - 2 * std::exp(-time) + std::exp(-2 * time)) +
                        2 * (1 - std::exp(-2 * time));
                    break;
            }
            break;
        }

        case Type::oaf: {
            resetting = true;
            type_str = "oaf";
            target = std::exp(-rod.getDRot() * time);
            break;
        }
        case Type::empxx: {
            resetting = true;
            type_str = "empxx";
            const double Dmean = 0.5 * (rod.getDiff().trace());
            const double u = 4.0;
            const double deltaD = rod.getDiff()(1, 1) - rod.getDiff()(0, 0);
            switch (force) {
                case Force::zero_F:
                    target = Dmean - deltaD / 2 *
                                         (1 - exp(-u * rod.getDRot() * time)) /
                                         u / rod.getDRot() / time;
                    break;
                case Force::const_F:
                case Force::harmonic_F:
                    target = -100;  //TODO missing analytic
                    break;
            }
        } break;
        case Type::empyy: {
            resetting = true;
            type_str = "empyy";
            const double Dmean = 0.5 * (rod.getDiff().trace());
            const double u = 4.0;
            const double deltaD = rod.getDiff()(1, 1) - rod.getDiff()(0, 0);
            switch (force) {
                case Force::zero_F:
                    target = Dmean + deltaD / 2 *
                                         (1 - exp(-u * rod.getDRot() * time)) /
                                         u / rod.getDRot() / time;
                    break;
                case Force::const_F:
                case Force::harmonic_F:
                    target = -100; //TODO missing analytic
                    break;
            }
        } break;
        case Type::x: {
            resetting = false;
            type_str = "<x>";
            switch (force) {
                case Force::zero_F:
                    target = rod.getPos().sum();
                    break;
                case Force::const_F:
                    target = -100; //TODO missing analytic
                    break;
                case Force::harmonic_F:
                    target = rod.getPos()[0] * exp(-1 * time);
                    break;
            }

        } break;
        case Type::x_squared: {
            resetting = false;
            type_str = "<x_squared>";

            switch (force) {
                case Force::zero_F:
                case Force::const_F:
                    target = -100; //TODO missing analytic
                    break;
                case Force::harmonic_F:
                    target = pow(rod.getPos()[0], 2) * exp(-2 * time) +
                             1 * (1 - exp(-2 * time));
                    break;
            }
        } break;
    }
}

const LiveAgg &Compute::getAgg() const { return agg; }

Compute::Type Compute::getType() const { return type; }

double Compute::getTime() const { return time; }

double Compute::getTarget() const { return target; }

double Compute::getDifference() const { return abs(agg.getMean() - target); }

std::ostream &operator<<(std::ostream &os, const Compute &compute) {
    os << "agg: " << compute.agg << " type: " << compute.type_str;
    return os;
}

void Compute::reset(const Rod2d &rod) {
    SPDLOG_TRACE("Resettign Compute for new run");

    time_step = 0;
    start_rod = rod;
}
std::string Compute::getType_Str() const {
    switch (type) {
        case Type::msd:
            return "msd";
        case Type::oaf:
            return "oaf";
        case Type::empxx:
            return "empxx";
        case Type::empyy:
            return "empyy";
        case Type::x:
            return "x";
        case Type::x_squared:
            return "x_squared";
    }
    return "unknown";
}
