#include "brick_composite.h"

PIVector<BrickBase * > BrickComposite::baseBricks;


BrickComposite::BrickComposite(): BrickCompositeBase(0, 0, 1) {
	type = "Composite";
	setName(type);
	paramNames[0] = "Scheme file";
	note_ = "\"Scheme\" is path to *.mbs file, described this brick";
	parameters[0].setValue("");
	parameters[0].setType(BrickBase::File);
	setComposite(true);
	mode_ = Asynchronous;
	bi = 0; bo = 0; bp = 0; inf = 0;
}


void BrickComposite::clear() {
	for (uint i = 0; i < bricks.size(); ++i)
		delete bricks[i];
	bricks.clear();
}


void BrickComposite::proceedConnections(bool compositeToo) {
	if (compositeToo) {
		if (bi != 0) {
			for (int i = 0; i < inputsCount(); ++i)
				bi->setOutputValue(i, inputs[i]);
			bi->proceedConnections();
		}
		if (bp != 0) {
			for (int i = 1; i < parametersCount(); ++i)
				bp->setOutputValue(i - 1, parameters[i].toFloat());
			bp->proceedConnections();
		}
		switch (mode_) {
			case BrickBase::Synchronous:
				for (uint i = 0; i < bricks.size(); ++i)
					bricks[i]->proceedConnections();
				break;
			case BrickBase::Asynchronous: default:
				for (uint i = 0; i < tree.size(); ++i) {
					tl = &tree[i];
					for (uint j = 0; j < tl->size(); ++j)
						(*tl)[j]->proceedConnections();
				}
				break;
		}
		if (bo != 0)
			for (int i = 0; i < outputsCount(); ++i)
				outputs[i] = bo->input(i);
	}
	BrickBase::proceedConnections(compositeToo);
}


void BrickComposite::parameterChanged(int index) {
	fillBaseBricks();
	if (index != 0) return;
	PIConfig sr(parameters[0].toString());
	PIString prefix, cname;
	BrickBase * b;
	int pcnt;
	clear();
	setMode((Mode)(int)sr.getValue("Mode", 1));
	PIStringList names = sr.getValue("Bricks", PIStringList());
	for (uint i = 0; i < names.size(); ++i) {
		prefix = names[i] + " ";
		cname = sr.getValue(prefix + "codeName", "");
		b = findBaseBrick(cname)->copy();
		BrickComposite::loadBrick(sr, prefix, b);
		bricks.push_back(b);
	}
	uint cnt = sr.getValue("Connections count", 0);
	//cout << endl;
	for (uint i = 0; i < cnt; ++i) {
		prefix = "Connection " + PIString::fromNumber(i) + " ";
		BrickBase::connect(findBrick(sr.getValue(prefix + "from Name", "")),
						   sr.getValue(prefix + "from Port", 0),
						   findBrick(sr.getValue(prefix + "to Name", "")),
						   sr.getValue(prefix + "to Port", 0));
	}

	bi = 0;
	for (uint i = 0; i < bricks.size(); ++i)
		if (bricks[i]->codeName() == "BrickCompositeInput")
			{bi = (BrickCompositeInput * )bricks[i]; break;}
	if (bi != 0) {
		setInputsCount(bi->outputsCount());
		for (int i = 0; i < inputsCount(); ++i)
			inNames[i] = bi->parameter(i).toString();
	} else setInputsCount(0);

	bo = 0;
	for (uint i = 0; i < bricks.size(); ++i)
		if (bricks[i]->codeName() == "BrickCompositeOutput")
		{bo = (BrickCompositeOutput * )bricks[i]; break;}
	if (bo != 0) {
		setOutputsCount(bo->inputsCount());
		for (int i = 0; i < outputsCount(); ++i)
			outNames[i] = bo->parameter(i).toString();
	} else setOutputsCount(0);

	bp = 0;
	for (uint i = 0; i < bricks.size(); ++i)
		if (bricks[i]->codeName() == "BrickCompositeParameters")
		{bp = (BrickCompositeParameters * )bricks[i]; break;}
	if (bp != 0) {
		pcnt = parametersCount();
		setParametersCount(bp->parametersCount() + 1);
		for (int i = 1; i < parametersCount(); ++i) {
			cname = bp->parameter(i - 1).toString();
			int j = cname.toLowerCase().find(':');
			if (j < 0) paramNames[i] = cname;
			else {
				paramNames[i] = cname.left(j).trim();
				if (i >= pcnt) parameters[i].setValue(cname.cutLeft(j + 1).toDouble());
			}
		}
	} else setParametersCount(1);

	inf = 0;
	for (uint i = 0; i < bricks.size(); ++i)
		if (bricks[i]->codeName() == "BrickCompositeInformation")
			{inf = (BrickCompositeInformation * )bricks[i]; break;}
	if (inf != 0) {
		type = inf->parameter(0).toString();
		note_ = inf->parameter(1).toString();
	} else {
		type = "Composite";
		note_ = "\"Scheme\" is path to *.mbs file, described this brick";
	}

	buildBrickTree();
	//cout << name_ << " load " << bricks.size() << " bricks" << endl;
}


void BrickComposite::loadBrick(PIConfig & sr, const PIString & prefix, BrickBase * b, BrickManager * m) {
	b->setName(sr.getValue(prefix + "name", b->name()));
	b->setInitial(sr.getValue(prefix + "initial", false));
	b->setFrequencyDivider(sr.getValue(prefix + "freqDiv", 1));
	b->setOutputsCount(sr.getValue(prefix + "outCount", b->outputsCount()));
	b->setInputsCount(sr.getValue(prefix + "inCount", b->inputsCount()));
	for (int i = 0; i < b->inputsCount(); ++i)
		b->setInputValue(i, sr.getValue(prefix + "in " + PIString::fromNumber(i), b->input(i)));
	b->setParametersCount(sr.getValue(prefix + "paramCount", b->parametersCount()));
	for (int i = 0; i < b->parametersCount(); ++i)
		b->setParameterValue(i, PIVariant::fromValue<PIString>(sr.getValue(prefix + "param " + PIString::fromNumber(i), b->parameter(i).toString())), false);
	b->setManager(m);
}


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


BrickBase * BrickComposite::findBaseBrick(const PIString & code_name) {
	for (uint i = 0; i < baseBricks.size(); ++i)
		if (baseBricks[i]->codeName() == code_name)
			return baseBricks[i];
	return 0;
}


bool BrickComposite::tick_body(double time) {
	if (bi != 0) {
		for (int i = 0; i < inputsCount(); ++i)
			bi->setOutputValue(i, inputs[i]);
		bi->proceedConnections();
	}
	if (bp != 0) {
		for (int i = 1; i < parametersCount(); ++i)
			bp->setOutputValue(i - 1, parameters[i].toFloat());
		bp->proceedConnections();
	}
	switch (mode_) {
		case BrickBase::Synchronous:
			for (uint i = 0; i < bricks.size(); ++i)
				bricks[i]->tick(time);
			for (uint i = 0; i < bricks.size(); ++i)
				bricks[i]->proceedConnections();
			break;
		case BrickBase::Asynchronous: default:
			for (uint i = 0; i < tree.size(); ++i) {
				tl = &tree[i];
				for (uint j = 0; j < tl->size(); ++j)
					(*tl)[j]->tick(time);
				for (uint j = 0; j < tl->size(); ++j)
					(*tl)[j]->proceedConnections();
			}
			break;
	}
	if (bo != 0)
		for (int i = 0; i < outputsCount(); ++i)
			outputs[i] = bo->input(i);
	return true;
}


void BrickComposite::fillBaseBricks() {
	if (!baseBricks.isEmpty()) return;
	PIStringList libs = PICollection::groups();
	piForeachC (PIString & l, libs) {
		PIVector<const PIObject * > group = PICollection::groupElements(l);
		piForeachC (PIObject * o, group)
			baseBricks << (BrickBase * )o;
	}
	/*baseBricks.push_back(new BrickEmitConst());
	baseBricks.push_back(new BrickEmitLinear());
	baseBricks.push_back(new BrickEmitSin());
	baseBricks.push_back(new BrickEmitDelta());
	baseBricks.push_back(new BrickEmitStep());
	baseBricks.push_back(new BrickEmitPulse());
	baseBricks.push_back(new BrickEmitStrobe());
	baseBricks.push_back(new BrickEmitTime());
	baseBricks.push_back(new BrickEmitFrequency());
	baseBricks.push_back(new BrickEmitNoiseRandom());
	baseBricks.push_back(new BrickEmitNoiseNormal());
	baseBricks.push_back(new BrickMathAbsolute());
	baseBricks.push_back(new BrickMathSign());
	baseBricks.push_back(new BrickMathAmplifier());
	baseBricks.push_back(new BrickMathSum());
	baseBricks.push_back(new BrickMathMultiply());
	baseBricks.push_back(new BrickMathDivide());
	baseBricks.push_back(new BrickMathPower());
	baseBricks.push_back(new BrickMathDeadZone());
	baseBricks.push_back(new BrickMathSaturation());
	baseBricks.push_back(new BrickMathDelayTicks());
	baseBricks.push_back(new BrickMathDelaySeconds());
	baseBricks.push_back(new BrickMathRelay());
	baseBricks.push_back(new BrickMathDerivate());
	baseBricks.push_back(new BrickMathIntegral());
	baseBricks.push_back(new BrickMathIntegralSaturated());
	baseBricks.push_back(new BrickMathQuantize());
	baseBricks.push_back(new BrickMathThreshold());
	baseBricks.push_back(new BrickMathFunction());
	baseBricks.push_back(new BrickMathFFT());
	baseBricks.push_back(new BrickMathCopy());
	baseBricks.push_back(new BrickMathBessel());
	baseBricks.push_back(new BrickLogicNot());
	baseBricks.push_back(new BrickLogicAnd());
	baseBricks.push_back(new BrickLogicOr());
	baseBricks.push_back(new BrickLogicXor());
	baseBricks.push_back(new BrickLogicNAnd());
	baseBricks.push_back(new BrickLogicNOr());
	baseBricks.push_back(new BrickLogicNXor());
	baseBricks.push_back(new BrickLogicMemory());
	baseBricks.push_back(new BrickLogicCompare());
	baseBricks.push_back(new BrickDigitalTriggerRS());
	baseBricks.push_back(new BrickDigitalTriggerD());
	baseBricks.push_back(new BrickDigitalTriggerJK());
	baseBricks.push_back(new BrickDigitalCounter2());
	baseBricks.push_back(new BrickDigitalCounter4());
	baseBricks.push_back(new BrickDigitalCounter8());
	baseBricks.push_back(new BrickDigitalCoder1());
	baseBricks.push_back(new BrickDigitalCoder2());
	baseBricks.push_back(new BrickDigitalCoder3());
	baseBricks.push_back(new BrickDigitalCoder4());
	baseBricks.push_back(new BrickDigitalDecoder1());
	baseBricks.push_back(new BrickDigitalDecoder2());
	baseBricks.push_back(new BrickDigitalDecoder3());
	baseBricks.push_back(new BrickDigitalDecoder4());
	baseBricks.push_back(new BrickDigitalMux1());
	baseBricks.push_back(new BrickDigitalMux2());
	baseBricks.push_back(new BrickDigitalMux3());
	baseBricks.push_back(new BrickDigitalMux4());
	baseBricks.push_back(new BrickDigitalDemux1());
	baseBricks.push_back(new BrickDigitalDemux2());
	baseBricks.push_back(new BrickDigitalDemux3());
	baseBricks.push_back(new BrickDigitalDemux4());
	baseBricks.push_back(new BrickDigitalDigitalToAnalog2());
	baseBricks.push_back(new BrickDigitalDigitalToAnalog4());
	baseBricks.push_back(new BrickDigitalDigitalToAnalog8());
	baseBricks.push_back(new BrickDigitalAnalogToDigital2());
	baseBricks.push_back(new BrickDigitalAnalogToDigital4());
	baseBricks.push_back(new BrickDigitalAnalogToDigital8());
	baseBricks.push_back(new BrickLinkTransferFunction());
	baseBricks.push_back(new BrickLinkFilter1Degree());
	baseBricks.push_back(new BrickLinkFilterBandpass());
	baseBricks.push_back(new BrickInterfaceSerialIn());
	baseBricks.push_back(new BrickInterfaceSerialOut());
	baseBricks.push_back(new BrickInterfaceUDPIn());
	baseBricks.push_back(new BrickInterfaceUDPOut());
	baseBricks.push_back(new BrickInterfaceBinFileOut());
	baseBricks.push_back(new BrickStatisticMinMaxI());
	baseBricks.push_back(new BrickStatisticMinMaxF());
	baseBricks.push_back(new BrickStatisticExpectation());
	baseBricks.push_back(new BrickStatisticVariance());
	baseBricks.push_back(new BrickComposite());
	baseBricks.push_back(new BrickCompositeInput());
	baseBricks.push_back(new BrickCompositeOutput());
	baseBricks.push_back(new BrickCompositeParameters());
	baseBricks.push_back(new BrickCompositeInformation());*/
}


void sumTrees(PIVector<PIVector<BrickBase * > > & ft, PIVector<PIVector<BrickBase * > > & st) {
	uint mi = piMin<uint>(ft.size(), st.size());
	for (uint i = 0; i < mi; ++i)
		for (uint j = 0; j < st[i].size(); ++j)
			ft[i].push_back(st[i][j]);
		if (st.size() > ft.size())
			for (uint i = mi; i < st.size(); ++i)
				ft.push_back(st[i]);
}


void buildTree(PIVector<BrickBase * > & bricks, PIVector<PIVector<BrickBase * > > & tree) {
	PIVector<PIVector<BrickBase * > > ct;
	BrickBase * b;
	tree.clear();
	if (bricks.size() == 0) return;
	for (uint i = 0; i < bricks.size(); ++i) {
		b = bricks[i];
		b->ticked = false;
		b->level_ = -666;
		b->tmp_ = 0;
	}
	ct = buildSubtree(bricks, tree);
	//cout << "--------" << endl;
	while (ct.size() > 0) {
		//cout << "subtree " << ct.size() << endl;
		sumTrees(tree, ct);
		ct = buildSubtree(bricks, tree);
	}
	//cout << "subtrees built" << endl;
	uint tcnt = 0;
	for (uint i = 0; i < tree.size(); ++i)
		tcnt += tree[i].size();
	//cout << "built for " << tcnt << " from " << bricks.size() << endl;
	if (tcnt < bricks.size()) {
		for (uint i = 0; i < bricks.size(); ++i) {
			b = bricks[i];
			for (uint j = 0; j < tree.size(); ++j)
				for (uint k = 0; k < tree[j].size(); ++k)
					if (tree[j][k] == b) goto next;
			//cout << "add " << b->name().stdString() << " in level (" << b->level_ << ") ... " << flush;
			if (b->level_ < 0) b->level_ = 0;
			if ((uint)b->level_ < tree.size() && b->level_ >= 0) tree[b->level_].push_back(b);
			else {
				tree.resize(b->level_ + 1);
				tree[b->level_].push_back(b);
			}
				//cout << "ok" << endl;
			next: ;
		}
	}
	int mlev = bricks[0]->level_;
	for (uint i = 1; i < bricks.size(); ++i)
		if (mlev > bricks[i]->level_)
			mlev = bricks[i]->level_;
		if (mlev >= 0) return;
		piCout << "minimum level =" << mlev << ", so rebuild tree manually";
	tree.clear();
	int clev = 0;
	uint cnt = 0;
	PIVector<BrickBase * > level;
	for (uint i = 0; i < bricks.size(); ++i)
		bricks[i]->level_ -= mlev;
	mlev = 0;
	while (bricks.size() > cnt) {
		level.clear();
		for (uint i = 0; i < bricks.size(); ++i) {
			b = bricks[i];
			if (b->level_ != clev) continue;
			level.push_back(b);
			++cnt;
		}
		if (level.size() > 0) tree.push_back(level);
		++clev;
	}
}


PIVector<PIVector<BrickBase * > > buildSubtree(PIVector<BrickBase * > & bricks, PIVector<PIVector<BrickBase * > > & tree) {
	PIVector<BrickBase * > bbs, tbs, level;
	PIVector<PIVector<BrickBase * > > ttree;
	BrickBase * b, * sb;
	int minlev, clev;
	if (bricks.size() == 0) return ttree;
	//cout << "initial stage" << endl;
	/// Initial stage: first level - initialize bricks
	for (uint i = 0; i < bricks.size(); ++i) {
		b = bricks[i];
		if (b->level_ >= 0) continue;
		bbs.push_back(b);
		if (!b->isInitial()) {
			tbs.push_back(b);
			continue;
		}
		b->level_ = 0;
		level.push_back(b);
	}
	if (level.size() == 0 && tbs.size() == 0) return ttree;
	if (level.size() == 0) {
		level.push_back(tbs[0]);
		level.back()->level_ = 0;
		tbs.pop_front();
	}
	ttree.clear();
	ttree.push_back(level);
	//cout << "first stage" << endl;
	/// First pass: direct bricks bypass
	while (tbs.size() > 0) {
		level.clear();
		for (uint i = 0; i < ttree.back().size(); ++i) {
			b = ttree.back()[i];
			b->tmp_++;
			for (uint j = 0; j < b->connections.size(); ++j) {
				sb = b->connections[j].brick_to;
				if (sb->ticked) continue;
				level.push_back(sb);
				sb->level_ = ttree.size();
				for (uint k = 0; k < tbs.size(); ++k) {
					if (tbs[k] == sb) {
						tbs.remove(k);
						break;
					}
				}
			}
			if (b->tmp_ >= b->inputs_count) b->ticked = true;
		}
		if (level.size() == 0) break;
		ttree.push_back(level);
	}
	minlev = ttree[0][0]->level_;
	if (tbs.size() == 0) goto third;
	for (uint i = 0; i < ttree.size(); ++i)
		for (uint j = 0; j < ttree[i].size(); ++j)
			ttree[i][j]->ticked = true;
		//cout << "second stage" << endl;
	/// Second pass: ttree complement with invert bypass
			while (tbs.size() > 0) {
				level.clear();
				for (uint i = 0; i < tbs.size(); ++i) {
					b = tbs[i];
					for (uint j = 0; j < b->connections.size(); ++j) {
						sb = b->connections[j].brick_to;
						if (!sb->ticked) continue;
						level.push_back(b);
						b->level_ = sb->level_ - 1;
						if (minlev > b->level_) minlev = b->level_;
						b->ticked = true;
						for (uint k = 0; k < tbs.size(); ++k) {
							if (tbs[k] == b) {
								tbs.remove(k);
								break;
							}
						}
						i = 0;
					}
				}
				if (level.size() == 0) break;
			}
			//cout << "third stage" << endl;
			third:
			/// Third pass: rebuild ttree
			ttree.clear();
			clev = minlev;
			level.push_back(0);
			while (level.size() > 0) {
				level.clear();
				for (uint i = 0; i < bbs.size(); ++i) {
					b = bbs[i];
					if (b->level_ == clev) level.push_back(b);
				}
				if (level.size() > 0) ttree.push_back(level);
				++clev;
			}
			//cout << "final stage" << endl << endl;
			/// Final stage: normalize levels
			for (uint i = 0; i < ttree.size(); ++i)
				for (uint j = 0; j < ttree[i].size(); ++j)
					ttree[i][j]->level_ = i;
				return ttree;
}


BrickCompositeInput::BrickCompositeInput(int inputs_num): BrickCompositeBase(0, inputs_num, inputs_num) {
	type = "Input";
	setName(type);
	io_Type = BrickBase::VariableOutputs;
	outNames[0] = "0";
	note_ += "\"Parameters\" are labels for inputs that will be shown in parent composite brick";
	parameters[0].setValue("");
}


BrickCompositeOutput::BrickCompositeOutput(int outputs_num): BrickCompositeBase(outputs_num, 0, outputs_num) {
	type = "Output";
	setName(type);
	io_Type = BrickBase::VariableInputs;
	inNames[0] = "0";
	note_ += "\"Parameters\" are labels for outputs that will be shown in parent composite brick";
	parameters[0].setValue("");
}


BrickCompositeParameters::BrickCompositeParameters(): BrickCompositeBase(0, 1, 1) {
	type = "Parameters";
	setName(type);
	io_Type = BrickBase::VariableOutputs;
	note_ = "There are parameters you can set from parent brick\nEach parameter is float and should be typed in format: \"name[:default]\"";
	parameters[0].setValue("p");
	outNames[0] = "p";
}


void BrickCompositeParameters::parameterChanged(int index) {
	PIString s = parameters[index].toString();
	int i = s.toLowerCase().find(':');
	if (i < 0) outNames[index] = s;
	else outNames[index] = s.left(i).trim();
}


BrickCompositeInformation::BrickCompositeInformation(): BrickCompositeBase(0, 0, 2) {
	type = "Information";
	setName(type);
	parametersName_ = "Information";
	paramNames[0] = "Type name";
	paramNames[1] = "Promt";
	note_ = "There are information fields about parent composite brick";
	parameters[0].setValue("");
	parameters[1].setValue("");
}
