/*
 *  qlo10k1 - GUI frontend for ld10k1
 *
 *  Copyright (c) 2004 by Peter Zubaj
 *
 *   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 2 of the License, or
 *   (at your option) 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, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
 
#include <stdlib.h>
#include "strglobal.h"
#include "strparam.h"
#include "structure.h"
#include "structure_io.h"
#include "structure_link.h"
#include "structure_patch.h"

#include "ld10k1file.h"

StrGlobal::StrGlobal(CardParam *card)
{
	Card = card;
	Card->Structure = this;
		
	// registers
	Inputs.setAutoDelete(TRUE); // the list owns the objects
	Outputs.setAutoDelete(TRUE);
	FXs.setAutoDelete(TRUE);
	Links.setAutoDelete(TRUE);

	// patches
	Patches.setAutoDelete(TRUE); // the list owns the objects
}

StrGlobal::~StrGlobal(void)
{
	Card = NULL;
}

void StrGlobal::clear(void)
{
	Links.clear();
	Inputs.clear();
	Outputs.clear();
	FXs.clear();
	Patches.clear();
	UsedItems.clear();
}

void StrGlobal::clearFlags(void)
{
	StrFX *fx = NULL;
	for(fx = FXs.first(); fx; fx = FXs.next())
        {
		fx->setFlagUsed(false);
		fx->setFlagNew(false);
		fx->setFlagChanged(false);
	}
	
	StrInput *in = NULL;
	for(in = Inputs.first(); in; in = Inputs.next())
        {
		in->setFlagUsed(false);
		in->setFlagNew(false);
		in->setFlagChanged(false);
	}
	
	StrOutput *out = NULL;
	for(out = Outputs.first(); out; out = Outputs.next())
        {
		out->setFlagUsed(false);
		out->setFlagNew(false);
		out->setFlagChanged(false);
	}
	
	StrPatch *p = NULL;
	for(p = Patches.first(); p; p = Patches.next())
        {
		p->setFlagUsed(false);
		p->setFlagNew(false);
		p->setFlagChanged(false);
	}
	
	StrLink *l = NULL;
	for(l = Links.first(); l; l = Links.next())
        {
		l->setFlagUsed(false);
		l->setFlagNew(false);
		l->setFlagChanged(false);
	}
}

int StrGlobal::loadFromLD(void)
{
	clearFlags();

	// load registers
	int cnt;
	int err = 0;
	int i;
	
	QString ioname;
	
	// FXs
	if ((err = Card->getFXCount(&cnt)) < 0)
		return err;
	
	for (i = 0; i < cnt; i++)
	{
		if ((err = Card->getFX(i, ioname)) < 0)
			return err;
		
		StrFX *fx = findFXByNum(i);
		if (fx)
		{
			if (fx->name() != ioname)
			{
				fx->setName(ioname);
				fx->setFlagChanged(true);
			}
			// Don't make unconnected IO/FX disappear.
			fx->setFlagUsed(true);
		}
		else
		{
			fx = new StrFX(i, ioname);
			fx->setFlagNew(true);
			FXs.append(fx);
		}
	}

	if ((err = Card->getInputCount(&cnt)) < 0)
		return err;
	
	for (i = 0; i < cnt; i++)
	{
		if ((err = Card->getInput(i, ioname)) < 0)
			return err;
		
		StrInput *in = findInputByNum(i);
		if (in)
		{
			if (in->name() != ioname)
			{
				in->setName(ioname);
				in->setFlagChanged(true);
			}
			in->setFlagUsed(true);
		}
		else
		{
			in = new StrInput(i, ioname);
			in->setFlagNew(true);
			Inputs.append(in);
		}
	}

	if ((err = Card->getOutputCount(&cnt)) < 0)
		return err;
	
	for (i = 0; i < cnt; i++)
	{
		if ((err = Card->getOutput(i, ioname)) < 0)
			return err;
		
		StrOutput *out = findOutputByNum(i);
		if (out)
		{
			if (out->name() != ioname)
			{
				out->setName(ioname);
				out->setFlagChanged(true);
			}
			out->setFlagUsed(true);
		}
		else
		{
			out = new StrOutput(i, ioname);
			out->setFlagNew(true);
			Outputs.append(out);
		}
	}

	// load patches and patch registers
	CardParamPatchInfo **pi;
	
	
	if ((err = Card->getPatchesInfo(&pi, &cnt)) < 0)
		return err;
	// copy registers to internal structure
	for (i = 0; i < cnt; i++)
	{
		StrPatch *patch = NULL;
		
		if ((err = actualizePatch(pi[i]->id, pi[i]->patch_num, pi[i]->patch_name, &patch)) < 0)
		{
			for (i = 0; i < cnt; i++)
				delete pi[i];
			delete pi;
			return err;
		}
		patch->setOrder(i);
	}
	
	for (i = 0; i < cnt; i++)
		delete pi[i];
	delete pi;
	
	int *ld_tmplids = NULL;
	if ((err = Card->getPointsInfo(&ld_tmplids, &cnt)) < 0)
		return err;
	
	for (i = 0; i < cnt; i++)
	{
		StrLink *link = NULL;
		
		if ((err = actualizeLink(ld_tmplids[i], &link)) < 0)
		{
			free(ld_tmplids);
			return err;
		}
	}

	free(ld_tmplids);
	
	// copy used items
	StrPatch *patch;
	
	UsedItems.clear();
		
	for(patch = Patches.first(); patch; patch = Patches.next() )
	{
		if (patch->flagUsed())
			UsedItems.append(patch);
	}
	
	// remove unused
	for(patch = Patches.last(); patch; patch = Patches.prev() )
	{
		if (!patch->flagUsed())
			Patches.remove(patch);
	}
	
	QPtrListIterator <StrInput> itin(Inputs);
	StrInput *in;
	while ((in = itin.current()) != 0 ) {
		++itin;
		if (in->flagUsed())
			UsedItems.append(in);
		else
			Inputs.remove(in);
	}
	
	QPtrListIterator <StrOutput> itout(Outputs);
	StrOutput *out;
	while ((out = itout.current()) != 0 ) {
		++itout;
		if (out->flagUsed())
			UsedItems.append(out);
		else
			Outputs.remove(out);
	}
	
	QPtrListIterator <StrFX> itfx(FXs);
	StrFX *fx;
	while ((fx = itfx.current()) != 0 ) {
		++itfx;
		if (fx->flagUsed())
			UsedItems.append(fx);
		else
			FXs.remove(fx);
	}
	
	QPtrListIterator <StrLink> itlnk(Links);
	StrLink *lnk;
	while ((lnk = itlnk.current()) != 0 ) {
		++itlnk;
		if (lnk->flagUsed())
			UsedItems.append(lnk);
		else
			Links.remove(lnk);
	}
	
	updatePatchesOrder();
	
	AutoArange(&UsedItems);
	
	QPtrListIterator <StrLink> itlnk1(Links);
	while ((lnk = itlnk1.current()) != 0 ) {
		++itlnk1;
		lnk->calcSize();
	}
	return 0;
}

int StrGlobal::getPio(int pnum, bool out, int idx, QString &name)
{
	int err;
	
	if (out)
		err = Card->getPOutput(pnum, idx, name);
	else
		err = Card->getPInput(pnum, idx, name);
	return err;
}

int StrGlobal::actualizePatch(int pid, int pnum, QString pname, StrPatch **out)
{
	QString ioname;
	int z, j;
	int err;
		
	int cnt1[2];
	
	if ((err = Card->getPInputCount(pnum, &(cnt1[0]))) < 0)
		return err;
	if ((err = Card->getPOutputCount(pnum, &(cnt1[1]))) < 0)
		return err;
		
	StrPatch *patch = findPatchById(pid);
		
	if (patch)
	{
		// check changes
		if (patch->name() != pname)
		{
			patch->setName(pname);
			patch->setFlagChanged(true);
		}
		
		for (z = 0; z < 2; z++)
		{
			for (j = 0; j < cnt1[z]; j++)
			{
				if ((err = getPio(pnum, z, j, ioname)) < 0)
					return err;
				
				RSItemIO *io = patch->getIO(z, j);
				if (io->getDesc() != ioname)
				{
					io->setDesc(ioname);
					patch->setFlagChanged(true);
				}
			}
		}
	}
	else
	{
		patch = new StrPatch(pnum, pid, pname);
		Patches.append(patch);

		for (z = 0; z < 2; z++)
		{
			for (j = 0; j < cnt1[z]; j++)
			{
				if ((err = getPio(pnum, z, j, ioname)) < 0)
					return err;
					
				RSItemIO *io = new RSItemIO(patch, z, j, ioname);
				patch->setIO(z, j, io);
			}
		}
		
		patch->setFlagNew(true);
	}
	
	patch->setFlagUsed(true);
	
	*out = patch;
	return 0;
}

StrPatch *StrGlobal::findPatchByNum(int num)
{
	StrPatch *patch;
		
	for(patch = Patches.first(); patch; patch = Patches.next() )
	{
		if (patch->num() == num)
			return patch;
	}
	
	return NULL;
}

StrPatch *StrGlobal::findPatchById(int id)
{
	StrPatch *patch;
		
	for(patch = Patches.first(); patch; patch = Patches.next() )
	{
		if (patch->id() == id)
			return patch;
	}
	
	return NULL;
}

StrFX *StrGlobal::findFXByNum(int num)
{
	StrFX *fx;
		
	for(fx = FXs.first(); fx; fx = FXs.next() )
	{
		if (fx->num() == num)
			return fx;
	}
	
	return NULL;
}

StrInput *StrGlobal::findInputByNum(int num)
{
	StrInput *in;
		
	for(in = Inputs.first(); in; in = Inputs.next() )
	{
		if (in->num() == num)
			return in;
	}
	
	return NULL;
}

StrOutput *StrGlobal::findOutputByNum(int num)
{
	StrOutput *out;
		
	for(out = Outputs.first(); out; out = Outputs.next() )
	{
		if (out->num() == num)
			return out;
	}
	
	return NULL;
}

StrLink *StrGlobal::findLinkById(int id)
{
	StrLink *l;
		
	for(l = Links.first(); l; l = Links.next() )
	{
		if (l->id() == id)
			return l;
	}
	
	return NULL;
}

int StrGlobal::conAdd(bool multi, bool simple, RSItemIO *from_io, RSItemIO *to_io, int *id)
{
	int ftype = 0;
	int ttype = 0;
	int type = 0;
	int fp = 0;
	int tp = 0;
	int p = 0;
	int fio = 0;
	int tio = 0;
	int io = 0;
	
	RSItemIO *tmp;
	
	RSItemBaseWithType *owner;

	for (int i = 0; i < 2; i++)
	{
		if (!i)
			tmp = from_io;
		else
			tmp = to_io;
			
		owner = (RSItemBaseWithType *)tmp->getOwner();
		
		p = -1;
		io = -1;
				
		switch (owner->type())
		{
			case RSItemBaseWithType::In:
				type = CON_IO_IN;
				io = ((StrInput *)owner)->num();
				break;
			case RSItemBaseWithType::Out:
				type = CON_IO_OUT;
				io = ((StrOutput *)owner)->num();
				break;
			case RSItemBaseWithType::FX:
				type = CON_IO_FX;
				io = ((StrFX *)owner)->num();
				break;
			case RSItemBaseWithType::Patch:
				if (!tmp->isOutput())
					type = CON_IO_PIN;
				else
					type = CON_IO_POUT;
				io = tmp->getIdx();
					
				p = ((StrPatch *)owner)->num();
				break;
			default:
				break;
		}
		
		if (!i)
		{
			ftype = type;
			fio = io;
			fp = p;
		}
		else
		{
			ttype = type;
			tio = io;
			tp = p;
		}
	}

	return Card->conAdd(multi, simple, ftype, fp, fio, ttype, tp, tio, id);
	// FIXME - uprava strglobal - urobene inde
}

int StrGlobal::conDel(RSItemIO *from_io, int *id)
{
	int io, p;
	int type = 0;
	
	RSItemBaseWithType *owner;

	owner = (RSItemBaseWithType *)from_io->getOwner();
		
	p = -1;
	io = -1;
				
	switch (owner->type())
	{
		// only patch valid
		case RSItemBaseWithType::Patch:
			if (!from_io->isOutput())
				type = CON_IO_PIN;
			else
				type = CON_IO_POUT;
			io = from_io->getIdx();
				
			p = ((StrPatch *)owner)->num();
			break;
		default:
			break;
	}
		
	return Card->conDel(type, p, io, id);
}

int StrGlobal::load(LD10k1File *ld10k1file, StrPatch *before, StrPatch **loaded)
{
	int bef = -1;
	int loade, loade_id;
	int retval;
	
	if (before)
		bef = before->num();
	if ((retval = Card->load(ld10k1file, bef, &loade, &loade_id)) < 0)
		return retval;
		
	StrPatch *p = NULL;
	
	if ((retval = actualizePatch(loade_id, loade, ld10k1file->getPatchName(), &p)) < 0)
		return retval;
		
	UsedItems.append(p);
	updatePatchesOrder();
	AutoArange(&UsedItems);
	*loaded = p;
	return retval;
}

int StrGlobal::unload(StrPatch *p)
{
	int retval;
	if ((retval = Card->unload(p->num())) < 0)
		return retval;
		
	// FIXME - odmaz aj prepojenia
	UsedItems.remove(p);
	Patches.remove(p);
	updatePatchesOrder();
	return retval;
}

int StrGlobal::clearDSP(void)
{
	int retval;
		
	if ((retval = Card->clearDSP()) < 0)
		return retval;
		
	clear();
	return 0;
}

void StrGlobal::destroyLink(StrLink *l)
{
	UsedItems.remove(l);
	l->disconnectAll();
	
	// delete not needed - autodelete
	Links.remove(l);
}

int StrGlobal::disconnectFromLink(RSItemIO *io)
{
	StrLink *link = io->getConnectedTo();
	if (link)
	{
		int idx = link->findRoute(io);
		int conn_id;
		int err;
		if((err = conDel(io, &conn_id)) < 0)
			return err;
		link->setRoutePoint(idx, NULL);
		if (conn_id < 0 || !link->isValid())
		{
			destroyLink(link);
			return 0;
		}
		link->calcSize();
	}
	return 0;
}

int StrGlobal::actualizeLink(int id, StrLink **out)
{
	int err;
	CardParamPointInfo point;
	
	err = Card->getPointInfo(id, &point);	
	if (err == LD10K1_ERR_UNKNOWN_POINT)
	{
		// FIXME - probably not exists - remove
		StrLink *delLink = findLinkById(id);
		if (delLink)
			destroyLink(delLink);
		
		*out = NULL;
		return 0;
	}
	if (err < 0)
		return err;
	
	RSItemIO *firstItem = NULL;
	StrLink::LinkType newType = StrLink::LinkFX;
	switch(point.type)
	{
		case CON_IO_FX:
			newType = StrLink::LinkFX;
			{
				StrFX *ft = findFXByNum(point.io_idx);
				if (ft)
					firstItem = ft->getIO(true, 0);
			}
			break;
		case CON_IO_IN:
			newType = StrLink::LinkIn;
			{
				StrInput *in = findInputByNum(point.io_idx);
				if (in)
					firstItem = in->getIO(true, 0);
			}
			break;
		case CON_IO_OUT:
			newType = StrLink::LinkOut;
			{
				StrOutput *out = findOutputByNum(point.io_idx);
				if (out)
					firstItem = out->getIO(false, 0);
			}
			break;
		case CON_IO_NORMAL:
			newType = StrLink::LinkNormal;
			break;
	}
	
	StrLink *link = findLinkById(id);
	if (link)
	{
		bool actualized[POINTINFO_MAX_CONN_PER_POINT];
		RSItemIO *newIOs[POINTINFO_MAX_CONN_PER_POINT];
		unsigned int j;
		unsigned int niosc = 0;
		
		for (j = 0; j < POINTINFO_MAX_CONN_PER_POINT; j++)
			actualized[j] = false;
		
		int r = 0;
		if (firstItem)
		{
			actualized[r] = true;
			((RSItemBaseWithType *)firstItem->getOwner())->setFlagUsed(true);
			r++;
		}
		
		// actualize current
		for (j = 0; j < point.conn_count; j++)
		{
			RSItemIO *io = findPatchIO(point.io_type[j], point.patch[j], point.io[j]);
			((RSItemBaseWithType *)io->getOwner())->setFlagUsed(true);
			
			// find io
			int rf = link->findRoute(io);
			if (rf >= 0)
			{
				actualized[rf] = true;
				link->setRoutePoint(rf, io);	
			}
			else
			{
				newIOs[niosc++] = io;
			}		
		}
		
		// delete old
		for (j = 0; j < POINTINFO_MAX_CONN_PER_POINT; j++)
		{
			if (!actualized[j])
			{
				link->setRoutePoint(j, NULL);
				link->clearRoutesPoints(j);
			}
		}
		
		// add new
		int l = 0;
		for (j = 0; j < niosc; j++)
		{
			for (; l < POINTINFO_MAX_CONN_PER_POINT; l++)
			{
				if (!link->getRoutePoint(l))
				{
					link->setRoutePoint(l, newIOs[j]);
					break;
				}
			}
		}
		
		link->setFlagChanged(true);
	}
	else
	{
		link = new StrLink(id, newType);
		int r = 0;
		if (firstItem)
		{
			link->setRoutePoint(r++, firstItem);
			((RSItemBaseWithType *)firstItem->getOwner())->setFlagUsed(true);
		}
			
		unsigned int j = 0;
		for (j = 0; j < point.conn_count; j++)
		{
			RSItemIO *io = findPatchIO(point.io_type[j], point.patch[j], point.io[j]);
			((RSItemBaseWithType *)io->getOwner())->setFlagUsed(true);
			link->setRoutePoint(r++, io);
		}
		link->setFlagNew(true);
		Links.append(link);
		UsedItems.append(link);
	}
	link->setFlagUsed(true);
	
	*out = link;
	return 0;
}

void StrGlobal::updatePatchesOrder(void)
{
	CardParamPatchInfo **pi;
	int i, cnt;
	int err;
	
	if ((err = Card->getPatchesInfo(&pi, &cnt)) < 0)
		return;
		
	for (i = 0; i < cnt; i++)
	{
		StrPatch *p = findPatchById(pi[i]->id);
		if (p)
			p->setOrder(i);
	}
	
	for (i = 0; i < cnt; i++)
		delete pi[i];
	delete pi;
}

RSItemIO *StrGlobal::findPatchIO(bool out, int pid, int ionum)
{
	if (pid < 0 || ionum < 0)
		return NULL;
	StrPatch *p = findPatchById(pid);
	if (!p)
		return NULL;
	
	return p->getIO(out, ionum);
}

int StrGlobal::deleteOneLink(StrLink *l)
{
	int err;
	
	for (int i = 0; i < l->getMaxRoute(); i++)
	{
		RSItemIO *io = l->getRoutePoint(i);
		if (io)
		{
			RSItemBaseWithType *owner = (RSItemBaseWithType *)io->getOwner();
			if (owner->type() == RSItemBaseWithType::Patch)
			{
				// only patch can be disconnected
				int conn_id;
				if((err = conDel(io, &conn_id)) < 0)
					return err;
				if (conn_id < 0)
				{
					destroyLink(l);
					return 0;
				}
					
			}
		}
	}
	return 0;
}

int StrGlobal::deleteOneFX(StrFX *fx)
{
	int err;
	
	RSItemIO *io = fx->getIO(true, 0);
	if (io)
	{
		StrLink *l = io->getConnectedTo();
		if (l)
		{
			if ((err = deleteOneLink(l)) < 0)
				return err;
		}
	}
	
	UsedItems.remove(fx);
	
	// delete not needed - autodelete
	FXs.remove(fx);
	
	return 0;
}

int StrGlobal::deleteOneIn(StrInput *in)
{
	int err;
	
	RSItemIO *io = in->getIO(true, 0);
	if (io)
	{
		StrLink *l = io->getConnectedTo();
		if (l)
		{
			if ((err = deleteOneLink(l)) < 0)
				return err;
		}
	}
	
	UsedItems.remove(in);
	
	// delete not needed - autodelete
	Inputs.remove(in);
	
	return 0;
}

int StrGlobal::deleteOneOut(StrOutput *out)
{
	int err;
	
	RSItemIO *io = out->getIO(false, 0);
	if (io)
	{
		StrLink *l = io->getConnectedTo();
		if (l)
		{
			if ((err = deleteOneLink(l)) < 0)
				return err;
		}
	}
	
	UsedItems.remove(out);
	
	// delete not needed - autodelete
	Outputs.remove(out);
	
	return 0;
}

int StrGlobal::deleteOnePatch(StrPatch *p)
{
	int err;
	int z;
	int j;
	
	// store all conn ids
	
	QValueList <int> actIds;
					
	actIds.clear();
	
	for (z = 0; z < 2; z++)
	{
		for (j = 0; j < p->getMaxIOIdx(z); j++)
		{
			RSItemIO *io = p->getIO(z, j);
			if (io)
			{
				StrLink *l = io->getConnectedTo();
				if (l && actIds.findIndex(l->id()) < 0)
					actIds.append(l->id());
			}
		}
	}
	
	if ((err = unload(p)) < 0)
		return err;
		
	// actualize links
	for (unsigned int i = 0; i < actIds.count(); i++)
	{
		StrLink *link = NULL;
		int aid = actIds[i];
		if ((err = actualizeLink(aid, &link)) < 0)
			return err;
		
		if (link)
		{
			if (!link->isValid())
				destroyLink(link);
			else
				//wasn't error
				link->calcSize();
		}
	}
	
	return 0;
}

int StrGlobal::deleteOneItem(RSItemBaseWithType *item)
{
	switch (item->type())
	{
		case RSItemBaseWithType::In:
			return deleteOneIn((StrInput *)item);
		case RSItemBaseWithType::Out:
			return deleteOneOut((StrOutput *)item);
		case RSItemBaseWithType::FX:
			return deleteOneFX((StrFX *)item);
		case RSItemBaseWithType::Link:
			return deleteOneLink((StrLink *)item);
		case RSItemBaseWithType::Patch:
			return deleteOnePatch((StrPatch *)item);
		default:
			return 0;
	}
}

int StrGlobal::deleteAllSelected()
{
	int err;
	// through all selected
	RSItemBaseWithType *item;
	
	// first connections
	QPtrListIterator <RSItemBaseWithType> it1(UsedItems);
	while ((item = it1.current()) != 0 ) 
	{
		++it1;
		if (item->flagSelected() && item->type() == RSItemBaseWithType::Link)
		{
			if ((err = deleteOneLink((StrLink *)item)) < 0)
				return err;
		}
	}
	
	// everything others
	QPtrListIterator <RSItemBaseWithType> it2(UsedItems);
	while ((item = it2.current()) != 0 ) 
	{
		++it2;
		if (item->flagSelected())
		{
			if ((err = deleteOneItem(item)) < 0)
				return err;
		}
	}
	
	return 0;
}

QString StrGlobal::errorStr(int err)
{
	return Card->errorStr(err);
}

int StrGlobal::get(int patch_num, LD10k1File **dc)
{
	return Card->get(patch_num, dc);
}


int StrGlobal::getDspConfig(LD10k1DspFile **dc)
{
	return Card->getDspConfig(dc);
}

int StrGlobal::putDspConfig(LD10k1DspFile *dc)
{
	return Card->putDspConfig(dc);
}

StrCardGlobal::StrCardGlobal()
{
	// cards
	Cards.setAutoDelete( TRUE ); // the list owns the objects
}
