/** Read CIM files
 *
 * @author Georg Reinke <georg.reinke@rwth-aachen.de>
 * @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
 *
 * DPsim
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *********************************************************************************/

#include <CIMModel.hpp>
#include <IEC61970.hpp>

#include "Reader.h"

using namespace DPsim;
using namespace DPsim::CIM;
using namespace IEC61970::Base::Core;
using namespace IEC61970::Base::Domain;
using namespace IEC61970::Base::Equivalents;
using namespace IEC61970::Base::Topology;
using namespace IEC61970::Base::Wires;

Reader::Reader(Real systemFrequency, Logger::Level logLevel, Logger::Level componentLogLevel)
	: mLog("Logs/CIMpp.log", logLevel) {
	mModel.setDependencyCheckOff();
	mFrequency = systemFrequency;
	mOmega = 2 * PI*mFrequency;
	mComponentLogLevel = componentLogLevel;
}

Reader::~Reader() { }

Real Reader::unitValue(Real value, UnitMultiplier mult) {
	switch (mult) {
	case UnitMultiplier::p:
		value *= 1e-12;
		break;
	case UnitMultiplier::n:
		value *= 1e-9;
		break;
	case UnitMultiplier::micro:
		value *= 1e-6;
		break;
	case UnitMultiplier::m:
		value *= 1e-3;
		break;
	case UnitMultiplier::c:
		value *= 1e-2;
		break;
	case UnitMultiplier::d:
		value *= 1e-1;
		break;
	case UnitMultiplier::k:
		value *= 1e3;
		break;
	case UnitMultiplier::M:
		value *= 1e6;
		break;
	case UnitMultiplier::G:
		value *= 1e9;
		break;
	case UnitMultiplier::T:
		value *= 1e12;
		break;
	default:
		break;
	}
	return value;
}

Component::Ptr Reader::mapComponent(BaseClass* obj) {
	if (ACLineSegment *line = dynamic_cast<ACLineSegment*>(obj))
		return mapACLineSegment(line);
	if (EnergyConsumer *consumer = dynamic_cast<EnergyConsumer*>(obj))
		return mapEnergyConsumer(consumer);
	if (PowerTransformer *trans = dynamic_cast<PowerTransformer*>(obj))
		return mapPowerTransformer(trans);
	if (SynchronousMachine *syncMachine = dynamic_cast<SynchronousMachine*>(obj))
		return mapSynchronousMachine(syncMachine);
	return nullptr;
}

void Reader::addFiles(String filename) {
	if (!mModel.addCIMFile(filename))
		std::cout << "Failed to read file " << filename << std::endl;
}

void Reader::addFiles(std::list<String> filenames) {
	for (String filename : filenames)
		addFiles(filename);
}

void Reader::parseFiles() {	
	try {
		mModel.parseFiles();
	}
	catch (...) {
		mLog.Log(Logger::Level::ERROR) << "Failed to parse CIM files" << std::endl;
		return;
	}
		
	mLog.Log(Logger::Level::INFO) << "#### List of TopologicalNodes, associated Terminals and Equipment" << std::endl;

	for (auto obj : mModel.Objects) {
		if (TopologicalNode* topNode = dynamic_cast<TopologicalNode*>(obj)) {
			processTopologicalNode(topNode);
		}
	}
	// Collect voltage state variables associated to nodes that are used
	// for various components.
	mLog.Log(Logger::Level::INFO) << "#### List of Node voltages and Terminal power flow data" << std::endl;

	for (auto obj : mModel.Objects) {
		// Check if object is of class SvVoltage
		if (SvVoltage* volt = dynamic_cast<SvVoltage*>(obj)) {
			processSvVoltage(volt);
		}
		// Check if object is of class SvPowerFlow
		else if (SvPowerFlow* flow = dynamic_cast<SvPowerFlow*>(obj)) {
			processSvPowerFlow(flow);
		}
	}

	mLog.Log(Logger::Level::INFO) << "#### Create other components" << std::endl;
	for (auto obj : mModel.Objects) {
		// Check if object is not TopologicalNode, SvVoltage or SvPowerFlow
		if (!dynamic_cast<TopologicalNode*>(obj)
			&& !dynamic_cast<SvVoltage*>(obj)
			&& !dynamic_cast<SvPowerFlow*>(obj)) {
			if (IdentifiedObject* idObj = dynamic_cast<IdentifiedObject*>(obj)) {
			// Check if object is already in equipment list
				if (mPowerflowEquipment.find(idObj->mRID) == mPowerflowEquipment.end()) {
					Component::Ptr comp = mapComponent(obj);
					if (comp) mPowerflowEquipment.insert(std::make_pair(comp->mUID, comp));
				}
			}
		}
	}
}

void Reader::processTopologicalNode(TopologicalNode* topNode) {
	// Add this node to global node list and assign simulation node incrementally.
	mPowerflowNodes[topNode->mRID] = std::make_shared<Node>(topNode->mRID, Int(mPowerflowNodes.size()));

	mLog.Log(Logger::Level::INFO) << "TopologicalNode " << topNode->mRID
		<< " as simulation node " << mPowerflowNodes[topNode->mRID]->mSimNode << std::endl;

	for (auto term : topNode->Terminal) {
		// Insert Terminal if it does not exist in the map and add reference to node.
		// This could be optimized because the Terminal is searched twice.
		mPowerflowTerminals.insert(std::make_pair(term->mRID, std::make_shared<Terminal>(term->mRID)));
		mPowerflowTerminals[term->mRID]->mNode = mPowerflowNodes[topNode->mRID];
		// Add reference to Terminal to this node.
		mPowerflowNodes[topNode->mRID]->mTerminals.push_back(mPowerflowTerminals[term->mRID]);
		mLog.Log(Logger::Level::INFO) << "    " << "Terminal " << term->mRID
			<< ", sequenceNumber " << term->sequenceNumber << std::endl;

		// Try to process Equipment connected to Terminal.
		ConductingEquipment *equipment = term->ConductingEquipment;
		if (!equipment) {
			mLog.Log(Logger::Level::WARN) << "Terminal " << term->mRID
				<< " has no Equipment, ignoring!" << std::endl;
		}
		else {
			// Insert Equipment if it does not exist in the map and add reference to Terminal.
			// This could be optimized because the Equipment is searched twice.
			if (mPowerflowEquipment.find(equipment->mRID) == mPowerflowEquipment.end()) {
				Component::Ptr comp = mapComponent(equipment);
				if (comp) mPowerflowEquipment.insert(std::make_pair(equipment->mRID, comp));
			}				
			auto pfEquipment = mPowerflowEquipment[equipment->mRID];			
			pfEquipment->setTerminalAt(mPowerflowTerminals[term->mRID], term->sequenceNumber-1);

			mLog.Log(Logger::Level::INFO) << "        Added Terminal "
				<< term->mRID << " to Equipment " << equipment->mRID << std::endl;
		}
	}
}

void Reader::processSvVoltage(SvVoltage* volt) {
	TopologicalNode* node = volt->TopologicalNode;
	if (!node) {
		mLog.Log(Logger::Level::WARN) << "SvVoltage references missing Topological Node, ignoring" << std::endl;
		return;
	}
	auto search = mPowerflowNodes.find(node->mRID);
	if (search == mPowerflowNodes.end()) {
		mLog.Log(Logger::Level::WARN) << "SvVoltage references Topological Node " << node->mRID
			<< " missing from mTopNodes, ignoring" << std::endl;
		return;
	}
	Real voltageAbs = Reader::unitValue(volt->v.value, UnitMultiplier::k);
	Real voltagePhase = volt->angle.value * PI / 180;
	mPowerflowNodes[node->mRID]->mInitialVoltage = std::polar<Real>(voltageAbs, voltagePhase);
	mLog.Log(Logger::Level::INFO) << "Node " << mPowerflowNodes[node->mRID]->mUID
		<< " SimNode " << mPowerflowNodes[node->mRID]->mSimNode << ": "
		<< std::abs(mPowerflowNodes[node->mRID]->mInitialVoltage) << " V, "
		<< std::arg(mPowerflowNodes[node->mRID]->mInitialVoltage)*180/PI << " deg" << std::endl;
}

void Reader::processSvPowerFlow(SvPowerFlow* flow) {
	IEC61970::Base::Core::Terminal* term = flow->Terminal;
	ConductingEquipment* eq = term->ConductingEquipment;

	mPowerflowTerminals[term->mRID]->mPower = { Reader::unitValue(flow->p.value, UnitMultiplier::M), Reader::unitValue(flow->q.value, UnitMultiplier::M) };

	mLog.Log(Logger::Level::INFO) << "Terminal " << term->mRID << ": "
		<< mPowerflowTerminals[term->mRID]->mPower.real() << " W + j"
		<< mPowerflowTerminals[term->mRID]->mPower.imag() << " Var" << std::endl;
}

SystemTopology Reader::getSystemTopology() {
	SystemTopology system(mFrequency);
	for (auto comp : mPowerflowEquipment) {
		system.mComponents.push_back(comp.second);
	}
	system.mNodes.resize(mPowerflowNodes.size());
	for (auto node : mPowerflowNodes) {
		system.mNodes[node.second->mSimNode] = node.second;
	}
	return system;
}

Matrix::Index Reader::mapTopologicalNode(String mrid) {
	auto search = mPowerflowNodes.find(mrid);
	if (search == mPowerflowNodes.end()) {
		return -1;
	}
	return search->second->mSimNode;
}

Component::Ptr Reader::mapEnergyConsumer(EnergyConsumer* consumer) {
	mLog.Log(Logger::Level::INFO) << "    Found EnergyConsumer " << consumer->name << std::endl;	
	return std::make_shared<Components::DP::PQLoad>(consumer->mRID, consumer->name, mComponentLogLevel);
}

Component::Ptr Reader::mapACLineSegment(ACLineSegment* line) {	
	mLog.Log(Logger::Level::INFO) << "    Found ACLineSegment " << line->name
		<< " r=" << line->r.value << " x=" << line->x.value
		<< " length=" << line->length.value << std::endl;

	Real resistance = line->r.value;
	Real inductance = line->x.value / mOmega;
	if (line->length.value > 0) {
		resistance = line->r.value * line->length.value;
		inductance = line->x.value / mOmega * line->length.value;
	}

	return std::make_shared<Components::DP::RxLine>(line->mRID, line->name, resistance, inductance, mComponentLogLevel);
}

Component::Ptr Reader::mapPowerTransformer(PowerTransformer* trans) {
	if (trans->PowerTransformerEnd.size() != 2) {
		mLog.Log(Logger::Level::WARN) << "    PowerTransformer " << trans->name
			<< " does not have exactly two windings, ignoring" << std::endl;
		return nullptr;
	}	
	mLog.Log(Logger::Level::INFO) << "Found PowerTransformer " << trans->name << std::endl;

	PowerTransformerEnd* end1;
	PowerTransformerEnd* end2;
	for (auto end : trans->PowerTransformerEnd) {
		if (end->Terminal->sequenceNumber == 1) end1 = end;
		else if (end->Terminal->sequenceNumber == 2) end2 = end;
		else return nullptr;
	}

	mLog.Log(Logger::Level::INFO) << "    PowerTransformerEnd_1 " << end1->name
		<< " Vrated=" << end1->ratedU.value << " R=" << end1->r.value << " X=" << end1->x.value << std::endl;	
	mLog.Log(Logger::Level::INFO) << "    PowerTransformerEnd_2 " << end2->name
		<< " Vrated=" << end2->ratedU.value << " R=" << end2->r.value << " X=" << end2->x.value << std::endl;

	Real voltageNode1 = unitValue(end1->ratedU.value, UnitMultiplier::k);
	Real voltageNode2 = unitValue(end2->ratedU.value, UnitMultiplier::k);
	Real ratioAbs = voltageNode1 / voltageNode2;
	Real ratioPhase = 0;

	Real inductance = 0;
	Real resistance = 0;
	if (voltageNode1 > voltageNode2 && end1->x.value > 0.001) {
		inductance = end1->x.value / mOmega;
		resistance = end1->r.value;
	} else if (voltageNode1 > voltageNode2 && end2->x.value > 0.001) {
		inductance = end2->x.value / mOmega / std::pow(ratioAbs, 2);
		resistance = end2->r.value / std::pow(ratioAbs, 2);
	}
	else if (voltageNode2 > voltageNode1 && end2->x.value > 0.001) {
		inductance = end2->x.value / mOmega;
		resistance = end2->r.value;
	}
	else if (voltageNode2 > voltageNode1 && end1->x.value > 0.001) {
		inductance = end1->x.value / mOmega / std::pow(ratioAbs, 2);
		resistance = end1->r.value / std::pow(ratioAbs, 2);
	}
		
	return std::make_shared<Components::DP::Transformer>(trans->mRID, trans->name, ratioAbs, ratioPhase, 0, inductance, mComponentLogLevel);
}

Component::Ptr Reader::mapSynchronousMachine(SynchronousMachine* machine) {	
	mLog.Log(Logger::Level::INFO) << "    Found  Synchronous machine " << machine->name << std::endl;
	return std::make_shared<Components::DP::SynchronGeneratorIdeal>(machine->mRID, machine->name, mComponentLogLevel);
}