
#include "generator.hpp"
#include "../util/math.hpp"
#include "../util/streams.hpp"

#include <cmath>
#include <iostream>

using namespace Sim::Electric;

Generator::Generator(Turbine* turbine, Grid* grid, double length, double diameter, double mass) :
		length(length), diameter(diameter), mass(mass), turbine(turbine), grid(grid)
{
	
}

Generator::Generator(const Json::Value& node, Turbine* turbine, Grid* grid) :
		length(node["length"].asDouble()),
		diameter(node["diameter"].asDouble()),
		friction(node["friction"].asDouble()),
		mass(node["mass"].asDouble()),
		turbine(turbine),
		grid(grid)
{
	velocity = node["velocity"].asDouble();
	phase = node["phase"].asDouble();
	breaker_closed = node["breaker_closed"].asBool();
	energy_generated = node["energy_generated"].asDouble();
}

void Generator::update(double dt)
{
	double energy_input = turbine->extract_energy();
	double energy_friction = get_rpm() / 60 * dt * friction;
	double work = Util::Math::j_to_ms2(energy_input - energy_friction, mass);
	phase = std::fmod(phase + Util::Math::map( get_rpm(), 0, 3600, 0, 120 * M_PI ) * dt, 2 * M_PI);

	// do energy transfer stuff here
	if(breaker_closed)
	{
		double a = get_phase_diff();
		double dist_extra = 0.1;
	
		if(is_stable || (a < 1e-5 && std::abs(get_rpm() - 60 * grid->frequency) < 1e-3))
		{
			is_stable = true;
			energy_generated = (energy_input - energy_friction) / dt;
			grid->pull_energy(energy_friction - energy_input);
			phase -= a;
			set_rpm(grid->frequency * 60);
			return;
		}
	
		glm::vec<2, double> point(std::cos(a), std::sin(a));
		glm::vec<2, double> diff1 = point - glm::vec<2, double>(dist_extra + 1, 0);
		glm::vec<2, double> diff2 = point - glm::vec<2, double>(-dist_extra - 1, 0);

		double strength = 1e10;
		double dist1 = glm::length(diff1);
		double dist2 = glm::length(diff2);
		double force1_len = -strength / (4 * M_PI * dist1 * dist1);
		double force2_len = strength / (4 * M_PI * dist2 * dist2);
		glm::vec<2, double> force1 = diff1 / dist1 * force1_len;
		glm::vec<2, double> force2 = diff2 / dist2 * force2_len;

		// calc the projected force vector

		double t1 = std::tan(a);
		double t2 = std::tan(a + M_PI/2);

		glm::vec<2, double> proj1, proj2;
		proj1.x = ((point.x + force1.x) * t1 - point.x * t2 - force1.y) / (t1 - t2);
		proj2.x = ((point.x + force2.x) * t1 - point.x * t2 - force2.y) / (t1 - t2);
		proj1.y = (proj1.x - point.x) * t2 + point.y;
		proj2.y = (proj2.x - point.x) * t2 + point.y;

		glm::mat<2, 2, double> rot_mat = {
			point.x, -point.y,
			point.y, point.x,
		};

		double eddy = (get_rpm() - 60 * grid->frequency) * dt * 1e5;

		// calc the amount of actual work (in change in m/s) done

		glm::vec<2, double> proj = rot_mat * (proj1 + proj2) * 0.5;
		double work_done = proj.y / (mass * 0.001) * dt - eddy;

		work += work_done;
		double e = Util::Math::ms2_to_j(work_done / dt, mass);
		energy_generated = -e / dt;
		grid->pull_energy(e);

		if(grid->is_above_limit())
		{
			breaker_closed = false;
		}
	}

	else
	{
		energy_generated = 0;
		is_stable = false;
	}

	velocity = std::max(velocity + work, 0.0);
}

double Generator::get_rpm() const
{
	return velocity / (M_PI * mass * 0.001 * diameter * diameter * 0.25);
}

void Generator::set_rpm(double rpm)
{
	velocity = rpm * (M_PI * mass * 0.001 * diameter * diameter * 0.25);
}

double Generator::get_phase_diff() const
{
	return Util::Math::mod(phase - grid->get_phase() + M_PI, 2*M_PI) - M_PI;
}

Generator::operator Json::Value() const
{
	Json::Value node;

	node["length"] = length;
	node["diameter"] = diameter;
	node["velocity"] = velocity;
	node["friction"] = friction;
	node["breaker_closed"] = breaker_closed;
	node["energy_generated"] = energy_generated;
	node["phase"] = phase;
	node["mass"] = mass;
	
	return node;
}

