/*
 * Copyright (C) 2008-2012 Delft University of Technology - All rights reserved.
 *
 * Author: Reijer Idema
 */

/*
 * MatPower case file interface class source.
 */
#include <iostream>					// USES cout
#include <sstream>					// USES stringstream
#include <cmath>					// USES M_PI
#include "../PowerSystem.h"			// USES PowerSystem
#include "MatPowerInterface.h"		// MatPower case file interface class declarations


POWERSYSTEM_NAMESPACE_BEGIN
using namespace std;


/////////////////////////////// PUBLIC ///////////////////////////////////////


//============================= LIFECYCLE ====================================

MatPowerInterface::MatPowerInterface(void) : mFilename("") { }

//============================= OPERATORS ====================================

//============================= OPERATIONS ===================================

//============================= INQUIRY    ===================================

//============================= ACCESS     ===================================

bool MatPowerInterface::Open(const string& rSourceName)
{
	Close();
	mFileStream.open(rSourceName.c_str(), ifstream::in);
	bool success = mFileStream.is_open();
	if (success) mFilename = rSourceName;
	return success;
} // CaseFile::Open


void MatPowerInterface::Close(void)
{
	mFilename = "";
	if (mFileStream.is_open()) mFileStream.close();
} // CaseFile::Close


bool MatPowerInterface::Read(PowerSystem& rPowerSystem)
{
	// Check if we have an open file to read from
	if (!mFileStream.is_open()) return HandleError(msFileNotOpenError);

	// Initialize variables
	char c;
	int lineNumber = 0, itemCount = 0;
	string lineData, blockName;

	// Read power system data from file stream
	while (mFileStream.good())
	{
		// Read the next line from the file stream
		++lineNumber;
		if (getline(mFileStream, lineData).fail()) return HandleError(msGetLineError);
		// Remove comments, trim line data, and check for empty line
		RemoveComment(lineData, "%");
		Trim(lineData);
		if (lineData.empty()) continue;

		// Get a stringstream to the line data and read the first element,
		// which is supposed to be a block name...
		istringstream iss(lineData, istringstream::in);
		iss >> blockName;

		// Process block
		if (blockName == "function")
		{
			// Process function block line of the form "function [...] = name"
			cout << "Processing function block (line " << lineNumber << ")...";
			// Read up to the mandatory '=' character
			char c = 0;
			while (iss.good() && c!='=') iss.get(c);
			if (c!='=') return HandleError(msFunctionBlockError);
			// First ... after the '=' character is the function/case name
			string caseName;
			iss >> caseName;
			cout << " name = " << caseName << "." << endl;
		}
		else if (blockName == "mpc.version")
		{
			// Process version block (data not currently used)
			cout << "Skipping version block (line " << lineNumber << ")...";
			cout << " OK." << endl;
		}
		else if (blockName == "baseMVA" || blockName == "mpc.baseMVA")
		{
			// Process baseMVA block
			cout << "Processing baseMVA block (line " << lineNumber << ")...";
			// Read next character, which should be '='
			iss >> c; if (c!='=') return HandleError(msBaseMVABlockError);
			// Read baseMVA value
			double baseMVA;
			iss >> baseMVA;
			cout << " base = " << baseMVA << " MVA." << endl;
			rPowerSystem.SetBasePower(baseMVA);
		}
		else if (blockName == "bus" || blockName == "mpc.bus")
		{
			// Process bus block
			cout << "Processing bus block (line " << lineNumber << ")...";
			// Read next two characters, which should be '=' and '['
			iss >> c; if (c!='=') return HandleError(msBusBlockError);
			iss >> c; if (c!='[') return HandleError(msBusBlockError);
			// Read bus block data into the power system
			itemCount = ReadBusBlock(mFileStream, lineNumber, rPowerSystem);
			cout << " " << itemCount << " busses." << endl;
			if (itemCount < 0) return HandleError(msBusBlockError);
		}
		else if (blockName == "gen" || blockName == "mpc.gen")
		{
			// Process generator block
			cout << "Processing generator block (line " << lineNumber << ")...";
			// Read next two characters, which should be '=' and '['
			iss >> c; if (c!='=') return HandleError(msGenBlockError);
			iss >> c; if (c!='[') return HandleError(msGenBlockError);
			// Read generator block data into the power system
			itemCount = ReadGenBlock(mFileStream, lineNumber, rPowerSystem);
			cout << " " << itemCount << " generators." << endl;
			if (itemCount < 0) return HandleError(msGenBlockError);
		}
		else if (blockName == "branch" || blockName == "mpc.branch")
		{
			// Process branch block
			cout << "Processing branch block (line " << lineNumber << ")...";
			// Read next two characters, which should be '=' and '['
			iss >> c; if (c!='=') return HandleError(msBranchBlockError);
			iss >> c; if (c!='[') return HandleError(msBranchBlockError);
			// Read branch block data into the power system
			itemCount = ReadBranchBlock(mFileStream, lineNumber, rPowerSystem);
			cout << " " << itemCount << " branches." << endl;
			if (itemCount < 0) return HandleError(msBranchBlockError);
		}
		else if (blockName == "areas" || blockName == "mpc.areas")
		{
			// Process areas block (data not currently used)
			cout << "Skipping areas block (line " << lineNumber << ")...";
			// Read next two characters, which should be '=' and '['
			iss >> c; if (c!='=') return HandleError(msAreasBlockError);
			iss >> c; if (c!='[') return HandleError(msAreasBlockError);
			// Skip areas block
			if (ReadUnknownBlock(mFileStream, lineNumber) < 0) return HandleError(msAreasBlockError);
			cout << " OK." << endl;
		}
		else if (blockName == "gencost" || blockName == "mpc.gencost")
		{
			// Process generator cost block (data not currently used)
			cout << "Skipping generator cost block (line " << lineNumber << ")...";
			// Read next two characters, which should be '=' and '['
			iss >> c; if (c!='=') return HandleError(msGenCostBlockError);
			iss >> c; if (c!='[') return HandleError(msGenCostBlockError);
			// Skip generator cost block
			if (ReadUnknownBlock(mFileStream, lineNumber) < 0) return HandleError(msGenCostBlockError);
			cout << " OK." << endl;
		}
		else if (blockName.compare(0, 6, "return") == 0)
		{
			// Process return statement
			cout << "Return statement read (line " << lineNumber << ")." << endl;
			return HandleError(msNoError);
		}
		else {
			// Process unknown block of the form
			// (A) "blockname ..." (single line) or
			// (B) "blockname = [ ... possibly multiple lines ... ]"
			cout << "Skipping unknown block '" << blockName << "' (line " << lineNumber << ")...";
			// Check if block is of form (A) or (B)
			iss >> c;
			if (c=='=') // No '=' assumes form (A), while '=' assumes form (B)
			{
				// Process block of form (B)
				iss >> c;
				if (c=='[' && ReadUnknownBlock(mFileStream, lineNumber) < 0) return HandleError(msUnknownBlockError);
			}
			cout << " OK." << endl;
		}
	} // while (mFileStream.good())

	cout << "Number of lines read: " << lineNumber << endl;
	return HandleError(msNoError);
} // CaseFile::Read

/////////////////////////////// PRIVATE    ///////////////////////////////////

int MatPowerInterface::ReadBusBlock(istream& rFileStream, int& rLineNumber, PowerSystem& rPowerSystem)
{
	int busCount = 0;
	string lineData;
	while (rFileStream.good())
	{
		// Read the next line from the file stream
		++rLineNumber;
		if (getline(rFileStream, lineData).fail()) return -1;

		// Remove comments, trim line data, and check for empty line or end of block
		RemoveComment(lineData, "%");
		Trim(lineData);
		if (lineData.empty()) continue;		// No data to be processed, go to next line
		if (lineData[0]==']') break;		// Block ended, stop reading lines

		// Read data from line using a stringstream
		// Line should be of the form: bus Pd Qd Gs Bs area Vm Va baseKV zone maxVm minVm
		istringstream iss(lineData, istringstream::in);
		int busIndex, type, area, zone;
		double pd, qd, gs, bs, vm, va, baseKV, vmMax, vmMin;
		iss >> busIndex >> type >> pd >> qd >> gs >> bs >> area;
		iss >> vm >> va >> baseKV >> zone >> vmMax >> vmMin;

		// Create bus object
		Bus bus;
		ostringstream oss;
		oss << busIndex;
		bus.SetName(oss.str());
		if (type == 1) bus.SetLoadType();			// load bus
		else if (type == 2) bus.SetGeneratorType();	// generator bus
		else if (type == 3) bus.SetSlackType();		// slack bus
		else continue;								// inactive bus
		bus.SetActivePowerDemand(pd);
		bus.SetReactivePowerDemand(qd);
		bus.SetShuntConductance(gs);
		bus.SetShuntSusceptance(bs);
		bus.SetVoltageMagnitude(vm);
		if (va != 0) va *= M_PI / 180;				// convert angle from degrees to radians
		bus.SetVoltageAngle(va);
		bus.SetBaseKiloVoltage(baseKV);
		// Ignoring: area, zone, vmMax, vmMin

		// Add bus object to the power system
		if (!rPowerSystem.AddBus(bus)) return -1;
		++busCount;
	} // while (rFileStream.good())
	return busCount;
} // MatPowerInterface::ReadBusBlock


int MatPowerInterface::ReadGenBlock(istream& rFileStream, int& rLineNumber, PowerSystem& rPowerSystem)
{
	int genCount = 0;
	string lineData;
	while (rFileStream.good())
	{
		// Read the next line from the file stream
		++rLineNumber;
		if (getline(rFileStream, lineData).fail()) return -1;

		// Remove comments, trim line data, and check for empty line or end of block
		RemoveComment(lineData, "%");
		Trim(lineData);
		if (lineData.empty()) continue;		// No data to be processed, go to next line
		if (lineData[0]==']') break;		// Block ended, stop reading lines

		// Read data from line using a stringstream
		// Line should be of the form: bus Pg Qg Qmax Qmin Vg mBase status Pmax Pmin
		istringstream iss(lineData, istringstream::in);
		int busIndex, status;
		double pg, qg, qMax, qMin, vg, mvaBase, pMax, pMin;
		iss >> busIndex >> pg >> qg >> qMax >> qMin >> vg >> mvaBase >> status >> pMax >> pMin;
		// :NOTE: Skipping version 2 fields 11-21.

		// Get corresponding bus object from power system
		ostringstream oss;
		oss << busIndex;
		PowerSystem::BusIndexType idx = rPowerSystem.GetBusIndex(oss.str());
		const Bus* pBus = rPowerSystem.GetBus(idx);
		if (pBus == NULL) return -1;
		Bus bus = *pBus;	// Get a copy we can manipulate

		// Check whether it is indeed a generator (or slack) bus
		if (!bus.IsGeneratorType() && !bus.IsSlackType()) return -1;

		// Check if the generator is in service
		if (status <= 0)
		{
			cout << endl << "ERROR: Cannot handle generators that are not in service (status <= 0)." << endl;
			return -1;	// :TODO: Handle machines out of service properly
		}
		bus.SetActivePowerGeneration(pg);
		bus.SetReactivePowerGeneration(qg);
		bus.SetVoltageMagnitude(vg);
//		bus.SetVoltageAngle(0);	// Set 0 or use data from bus block for generators. And how about the slack?
		// Ignoring: qMax, qMin, mvaBase, pMax, pMin

		// Set bus object into the power system
		if (!rPowerSystem.SetBus(idx, bus)) return -1;
		++genCount;
	} // while (rFileStream.good())
	return genCount;
} // MatPowerInterface::ReadGenBlock


int MatPowerInterface::ReadBranchBlock(istream& rFileStream, int& rLineNumber, PowerSystem& rPowerSystem)
{
	int branchCount = 0;
	string lineData;
	while (rFileStream.good())
	{
		// Read the next line from the file stream
		++rLineNumber;
		if (getline(rFileStream, lineData).fail()) return -1;

		// Remove comments, trim line data, and check for empty line or end of block
		RemoveComment(lineData, "%");
		Trim(lineData);
		if (lineData.empty()) continue;		// No data to be processed, go to next line
		if (lineData[0]==']') break;		// Block ended, stop reading lines

		// Read data from line using a stringstream
		// Line should be of the form: fbus tbus r x b rateA rateB rateC ratio angle status
		istringstream iss(lineData, istringstream::in);
		int fromBus, toBus, status;
		double r, x, b, rateA, rateB, rateC, ratio, angle;
		iss >> fromBus >> toBus >> r >> x >> b >> rateA >> rateB >> rateC >> ratio >> angle >> status;
		// :NOTE: Skipping version 2 fields 12-13.

		// Check if the branch is between two distinct existing busses
		if (fromBus == toBus) return -1;
		ostringstream oss;
		oss << fromBus;
		PowerSystem::BusIndexType idx1 = rPowerSystem.GetBusIndex(oss.str());
		oss.str("");	// Reset stringstream
		oss << toBus;
		PowerSystem::BusIndexType idx2 = rPowerSystem.GetBusIndex(oss.str());

		// Create branch object
		Branch branch;
		status > 0 ? branch.Activate() : branch.Deactivate();
		branch.SetFromBus(idx1);
		branch.SetToBus(idx2);
		branch.SetResistance(r);
		branch.SetReactance(x);
		branch.SetSusceptance(b);
		branch.SetRatio(ratio);
		if (angle != 0) angle *= M_PI / 180;	// convert angle from degrees to radians
		branch.SetAngle(angle);
		// Ignoring: rateA, rateB, rateC

		// Add branch object to the power system
		if (!rPowerSystem.AddBranch(branch)) return -1;
		++branchCount;
	} // while (rFileStream.good())
	return branchCount;
} // MatPowerInterface::ReadBranchBlock


int MatPowerInterface::ReadUnknownBlock(istream& rFileStream, int& rLineNumber)
{
	string lineData;
	while (rFileStream.good()) {
		// Read the next line from the file stream
		++rLineNumber;
		if (getline(rFileStream, lineData).fail()) return -1;

		// Remove comments, trim line data, and check for empty line or end of block
		RemoveComment(lineData, "%");
		Trim(lineData);
		if (lineData.empty()) continue;		// No data to be processed, go to next line
		if (lineData[0]==']') break;		// Block ended, stop reading lines
	} // while (rFileStream.good())
	return 0;
} // MatPowerInterface::ReadUnknownBlock


void MatPowerInterface::RemoveComment(string& str, const string& com) const
{
	// Find the comment identifier in the string, and erase everything after it
	string::size_type pos = str.find(com);
	if (pos != string::npos) str.erase(pos);
} // MatPowerInterface::RemoveComment


void MatPowerInterface::Trim(string& str) const
{
	// Remove trailing and leading spaces and tabs
	string::size_type pos = str.find_last_not_of(" \t");
	if (pos == string::npos)
	{	// Only spaces and tabs in the string
		str.clear();
	}
	else
	{	// Not just spaces and tabs in the string
		str.erase(pos + 1);
		str.erase(0, str.find_first_not_of(" \t"));
	}
} // MatPowerInterface::Trim


bool MatPowerInterface::HandleError(MatPowerInterface::ErrorType err) const
// returns true if err is no error, false if it is an error
{
	if (err == msNoError) return true;
	cout << "ERROR." << endl;
	return false;
} // MatPowerInterface::HandleError


POWERSYSTEM_NAMESPACE_END
