#include "brick_manager.h"
#include "brick_composite.h"


BrickManager::BrickManager(double frequency, double time_step) {
	timer = new PITimer(run, this, PITimer::ThreadRT);
	setFrequency(frequency, time_step);
	ctime = 0.;
	paused = false;
	wasReset = true;
	mode_ = BrickBase::Asynchronous;
	BrickComposite::fillBaseBricks();
}


void BrickManager::setFrequency(double frequency, double time_step) {
	freq = frequency;
	tstep = (time_step == 0.) ? 1. / frequency : time_step;
	if (!isRunning()) return;
	stop();
	start();
}


void BrickManager::addBrick(BrickBase * brick, bool uniqueName) {
	//lock();
	pause();
	brick->saveInputsToDefault();
	if (uniqueName) brick->setName(getUniqueName(brick->name()));
	brick->setManager(this);
	bricks.push_back(brick);
	if (mode_ == BrickBase::Asynchronous) buildSchemeTree();
	resume();
	//unlock();
}


void BrickManager::removeBrick(BrickBase * brick) {
	for (uint i = 0; i < bricks.size(); ++i) {
		if (bricks[i] == brick) {
			disconnectBrick(brick);
			bricks.remove(i);
			buildSchemeTree();
			return;
		}
	}
}


void BrickManager::replaceBrick(BrickBase * old_b, BrickBase * new_b) {
	PIVector<BrickBase::Connection> conns = old_b->connections;
	PIFlags<BrickBase::IOType> ion = new_b->io_Type;
	BrickBase * cb;
	new_b->setName(getUniqueName(new_b->name()));
	new_b->freqDivider_ = old_b->freqDivider_;
	new_b->manager_ = old_b->manager_;
	if (ion[BrickBase::VariableInputs])
		new_b->setInputsCount(old_b->inputsCount());
	if (ion[BrickBase::VariableOutputs])
		new_b->setOutputsCount(old_b->outputsCount());
	for (uint i = 0; i < bricks.size(); ++i) {
		cb = bricks[i];
		if (cb == old_b) {
			bricks[i] = new_b;
			for (uint j = 0; j < conns.size(); ++j) {
				if (conns[j].out_num > new_b->outputsCount()) continue;
				new_b->addConnection(conns[j]);
			}
			continue;
		}
		for (int j = 0; j < cb->connectionsCount(); ++j) {
			if (cb->connections[j].brick_to != old_b) continue;
			if (cb->connections[j].out_num > new_b->inputsCount())
				cb->removeConnection(j);
			else
				cb->connections[j].brick_to = new_b;
		}
	}
	PIVector<BrickBase * > * tl;
	for (uint i = 0; i < tree.size(); ++i) {
		tl = &tree[i];
		for (uint j = 0; j < tl->size(); ++j) {
			if (tl->at(j) != old_b) continue;
			(*tl)[j] = new_b;
		}
	}
	int pc = piMin<int>(old_b->parametersCount(), new_b->parametersCount()), ic = piMin<int>(old_b->inputsCount(), new_b->inputsCount());
	for (int i = 0; i < pc; ++i)
		new_b->setParameterValueOnly(i, old_b->parameter(i).toString());
	for (int i = 0; i < ic; ++i)
		new_b->setInputValue(i, old_b->input(i));
	//delete old_b;
}


void BrickManager::tick(bool realTime) {
	//cout << "tick\n";
	if (paused) return;
	wasReset = false;
	if (realTime) {
		//lock();
		switch (mode_) {
			case BrickBase::Synchronous:
			for (uint i = 0; i < bricks.size(); ++i)
				bricks[i]->tick(ctime);
			proceedConnections();
			break;
			case BrickBase::Asynchronous:
			PIVector<BrickBase * > * tl;
			for (uint i = 0; i < tree.size(); ++i) {
				tl = &tree[i];
				for (uint j = 0; j < tl->size(); ++j)
					(*tl)[j]->tick(ctime);
				for (uint j = 0; j < tl->size(); ++j)
					(*tl)[j]->proceedConnections();
			}
			break;
		}
		ctime += tstep;
		//unlock();
	} else {
		switch (mode_) {
			case BrickBase::Synchronous:
			for (uint i = 0; i < bricks.size(); ++i)
				if (!bricks[i]->isRealTimeOnly())
					bricks[i]->tick(ctime);
			proceedConnections();
			break;
			case BrickBase::Asynchronous:
			PIVector<BrickBase * > * tl;
			for (uint i = 0; i < tree.size(); ++i) {
				tl = &tree[i];
				for (uint j = 0; j < tl->size(); ++j)
					if (!(*tl)[j]->isRealTimeOnly())
						(*tl)[j]->tick(ctime);
				for (uint j = 0; j < tl->size(); ++j)
					(*tl)[j]->proceedConnections();
			}
			break;
		}
		ctime += tstep;
	}
	//cout << bricks[0]->output() << endl;
}


void BrickManager::proceedConnections(bool compositeToo) {
	for (uint i = 0; i < bricks.size(); ++i)
		bricks[i]->proceedConnections(compositeToo);
}


void BrickManager::start() {
	paused = false;
	startBricks();
	timer->stop();
#ifdef WINDOWS
	timer->waitForFinish();
#endif
	timer->start(1000. / freq);
}


void BrickManager::stop() {
	paused = false;
	timer->stop();
	stopBricks();
}


void BrickManager::reset() {
	paused = false;
	wasReset = true;
	if (isRunning()) lock();
	for (int i = 0; i < 3; ++i) {
		resetOutputs();
		proceedConnections(true);
	}
	saveInputsToDefault();
	for (uint i = 0; i < bricks.size(); ++i)
		bricks[i]->reset();
	ctime = 0.;
	if (isRunning()) unlock();
}


void BrickManager::resetOutputs() {
	for (uint i = 0; i < bricks.size(); ++i)
		bricks[i]->resetOutputs();
}


void BrickManager:: resetScheme() {
	stop();
	paused = false;
	tree.clear();
	for (uint i = 0; i < bricks.size(); ++i)
		delete bricks[i];
	bricks.clear();
}


bool BrickManager::loadScheme(const PIString & file) {
	PIConfig sr(file);
	if (!sr.isOpened()) return false;
	resetScheme();

	setFrequency(sr.getValue("Real-time Frequency", 100), sr.getValue("Real-time Step", 0.));
	setMode((BrickBase::Mode)(int)sr.getValue("Mode", 1));
	PIMathSolver::method_global = static_cast<PIMathSolver::Method>((int)sr.getValue("PIMathSolver", 0));

	PIStringList names = sr.getValue("Bricks", PIStringList());
	PIString prefix, cname;
	BrickBase * b, * bf, * bt;
	if (BrickComposite::baseBricks.size() == 0)
		BrickComposite::fillBaseBricks();
	for (int i = 0; i < names.size_s(); ++i) {
		prefix = names[i] + " ";
		cname = sr.getValue(prefix + "codeName", "");
		b = BrickComposite::findBaseBrick(cname);
		if (b == 0) {
			piCout << "Error while loading scheme: can`t find brick \"" << cname << "\"";
			continue;
		} else
			b = b->copy();
		loadBrick(sr, prefix, b);
		bricks.push_back(b);
	}
	int cnt = sr.getValue("Connections count", 0);
	for (int i = 0; i < cnt; ++i) {
		prefix = "Connection " + PIString::fromNumber(i) + " ";
		bf = findBrick(sr.getValue(prefix + "from Name", "").value().stdString());
		bt = findBrick(sr.getValue(prefix + "to Name", "").value().stdString());
		if (bf == 0 || bt == 0) {
			piCout << "Error while loading scheme: can`t create connection " << i;
			continue;
		}
		BrickBase::connect(bf, sr.getValue(prefix + "from Port", 0), bt, sr.getValue(prefix + "to Port", 0));
	}
	buildSchemeTree();
	startBricks();
	return true;
}


void BrickManager::disconnectBrick(BrickBase * brick) {
	for (uint i = 0; i < bricks.size(); ++i) {
		for (int j = 0; j < bricks[i]->connectionsCount(); ++j) {
			if (bricks[i]->connection(j).brick_to == brick)
				bricks[i]->removeConnection(j);
		}
	}
}


void BrickManager::loadBrick(PIConfig & sr, const PIString & prefix, BrickBase * b) {
	BrickComposite::loadBrick(sr, prefix, b, this);
}


void BrickManager::saveInputsToDefault() {
	for (uint i = 0; i < bricks.size(); ++i)
		bricks[i]->saveInputsToDefault();
}


bool BrickManager::checkUniqueName(const PIString & name, BrickBase * brick) {
	for (uint i = 0; i < bricks.size(); ++i)
		if ((bricks[i]->name() == name) && (bricks[i] != brick))
			return false;
	return true;
}


PIString BrickManager::getUniqueName(const PIString & name) {
	int cind = 0, di = 0;
	if (name.size() > 0) {
		di = name.size_s() - 1;
		while (name[di].isDigit()) --di;
		di = name.size_s() - di - 1;
	}
	PIString cname, bname;
	if (di > 0) bname = PIString(name).cutRight(di);
	else bname = name + "_";
	cname = bname + PIString::fromNumber(cind);
	bool ok = false;
	while (!ok) {
		ok = true;
		for (uint i = 0; i < bricks.size(); ++i) {
			if (bricks[i]->name() == cname) {
				cind++;
				ok = false;
				break;
			}
		}
		cname = bname + PIString::fromNumber(cind);
	}
	return cname;
}


BrickBase* BrickManager::findBrick(const PIString & name) {
	for (uint i = 0; i < bricks.size(); ++i)
		if (bricks[i]->name() == name)
			return bricks[i];
	return 0;
}


void BrickManager::setMode(BrickBase::Mode m) {
	mode_ = m;
	if (mode_ == BrickBase::Asynchronous) buildSchemeTree();
	else
		for (uint i = 0; i < bricks.size(); ++i)
			bricks[i]->level_ = -666;
}


void BrickManager::buildSchemeTree() {
	buildTree(bricks, tree);
}
