/*
 *  tarsysbod.h from ObjectProDSP 0.1
 *  Copyright (C) 1994, Mountain Math Software. All rights reserved.
 *  
 *  This file is part of ObjectProDSP, a tool for Digital Signal
 *  Processing design, development and implementation. It is free
 *  software provided you use and distribute it under the terms of
 *  version 2 of the GNU General Public License as published
 *  by the Free Software Foundation. You may NOT distribute it or
 *  works derived from it or code that it generates under ANY
 *  OTHER terms.  In particular NONE of the ObjectProDSP system is
 *  licensed for use under the GNU General Public LIBRARY License.
 *  Mountain Math Software plans to offer a commercial version of
 *  ObjectProDSP for a fee. That version will allow redistribution
 *  of generated code under standard commercial terms.
 *  
 *  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 version 2 of the GNU General
 *  Public License along with this program. See file COPYING. If not
 *  or if you wish information on commercial versions and licensing
 *  write Mountain Math Software, P. O. Box 2124, Saratoga, CA 95070,
 *  USA, or send us e-mail at: support@mtnmath.com.
 *  
 *  You may also obtain the GNU General Public License by writing the
 *  Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
 *  USA.  However if you received a copy of this program without the
 *  file COPYING or without a copyright notice attached to all text
 *  files, libraries and executables please inform Mountain Math Software.
 *  
 *  ObjectProDSP is a trademark of Mountain Math Software.
 */
#include <string.h>
#include "ObjProGen/linlist.h"
#include "ObjProUsr/network.h"
#include "ObjProGui/yacintfc.h"
#include "ObjProGui/travparm.h"
#include "ObjProGui/destrgt.h"
#include "ObjProGui/mktarget.h"
#include "ObjProGen/dosname.h"
#include "ObjProGen/mkstr.h"
#include "ObjProGui/shrbuf.h"
#include "ObjProGui/shrdary.h"


class TargetSystemGroup {
	const char * Name ;
	Network ** TheGroupMembers ;
public:
	TargetSystemGroup(const char * name, Network * FirstEntry = 0);
	const char * GetName() const {return Name;}
	void AddMember(Network&Net);
	void Describe() const ;
	int AlreadyUsed(Network&) const ;
	int MakeTarget(TargetSystem& TheSystem, const char * GroupPrefix) ;
	void OutputIncludes(ofstream& TheSystemFile);
	void OutputTables(ofstream& TheSystemCFile, const char * GroupPrefix);
	int CountNets() const ;
};

TargetSystemGroup::TargetSystemGroup(const char * name, Network * FirstEntry ):
	Name(name),
	TheGroupMembers(0)
{
	if (FirstEntry) AddMember(*FirstEntry);
}

int TargetSystemGroup::CountNets() const 
{
	if (!TheGroupMembers) return 0 ;
	for (int Count = 0 ; TheGroupMembers[Count]; Count++);
	return Count ;
}

int TargetSystemGroup::AlreadyUsed(Network& newnet) const
{
	// LogOut << "TargetSystemGroup::TheGroupMembers\n" ;
	// LogOut << "checing against " << newnet.GetName() << "\n" ;
	if (TheGroupMembers) for (Network ** net = TheGroupMembers; *net; net++) {
		// LogOut << "Checking " << (void *) *net << "\n" ;
		// LogOut << "name is " << (*net)->GetName() << "\n" ;
		if (!strcmp((*net)->GetName(),newnet.GetName())) return 1 ;
	}
	return 0 ;
}

void TargetSystemGroup::Describe() const
{
	if (TheGroupMembers) {
		HelpOut << "    Network group `" << Name <<
			"' has members:\n" ;
		for (Network ** net = TheGroupMembers; *net; net++) HelpOut
			<< "        " << (*net)->GetName() << "\n" ;
	} else HelpOut<<"    Network group `" << Name<<"' has no members.\n" ;
	HelpOut << "\n" ;
}

void TargetSystemGroup::AddMember(Network&TheNet)
{
	TheGroupMembers = (Network **) AddToList(&TheNet,
		(void **) TheGroupMembers);
}

int TargetSystemGroup::MakeTarget(TargetSystem& TheSystem,
	const char * GroupPrefix)
{
	if (TheGroupMembers) for (Network ** net=TheGroupMembers;*net;net++) {
		if (!TheSystem.ChangeNetwork(**net,GroupPrefix)) return 0 ;
	}
	return 1 ;
}

void TargetSystemGroup::OutputIncludes(ofstream& TheSystemFile)
{
	if (TheGroupMembers) for (Network ** net=TheGroupMembers;*net;net++)
		TheSystemFile << "#include \"" <<
		ToDosName((*net)->GetName()) << ".h\"\n" ;
}

void TargetSystemGroup::OutputTables(ofstream& TheSystemCFile,
	const char * GroupPrefix)
{
	TheSystemCFile << "static ExecuteState * " << GroupPrefix <<
		ToCppName(GetName()) << "StateArray["
		<< ListLength((void **) TheGroupMembers) +1 << "] = {\n" ;
	if (TheGroupMembers) for (Network ** net=TheGroupMembers;*net;net++)
		TheSystemCFile << "\t&The" << (*net)->GetName()
		<< "State,\n" ;
	TheSystemCFile << "\t0\n};\n\n" ;
}

void TargetSystem::Ctor()
{
	// LogOut << "TargetSystem::Ctor() enter\n" ;
	TheGroups = 0 ;
	TheParallelNets = 0 ;
	EnumCount = 0 ;
	// LogOut << "TargetSystem::Ctor() exit\n" ;
}

void TargetSystem::InitArithType(ArithType::ArithTypes )
{
}

int TargetSystem::GroupAlreadyUsed(const char * GroupName) const
{
	// LogOut << "TargetSystem::GroupAlreadyUsed(" << GroupName << ")\n" ;
	if (TheGroups) 
		for (TargetSystemGroup ** Grp = TheGroups ; *Grp;Grp++) {
			// LogOut << "checking " << (void *) *Grp << "\n" ;
			// LogOut << "name is `" << (*Grp)->GetName() << "'\n" ;
			if (!strcmp((*Grp)->GetName(),GroupName)) {
				State.Error("Group name `", GroupName, "' is already used in `",
					GetName(), "'");
				return 1 ;
			}
		}
	return 0 ;
}

int TargetSystem::AlreadyUsed(Network&Check) const
{
	// LogOut << "TargetSystem::AlreadyUsed\n" ;
	int Used = 0 ;
	if (TheParallelNets)
		for (Network ** Nets = TheParallelNets; *Nets; Nets++)
			if (!strcmp((*Nets)->GetName(),Check.GetName())) {
				Used = 1 ;
				break ;
	}
	if (!Used && TheGroups) 
		for (TargetSystemGroup ** Grp = TheGroups ; *Grp;Grp++)
			if ((*Grp)->AlreadyUsed(Check)) {
				Used = 2 ;
				break ;
	}
	if (!Used) return 0 ;
	State.Error("Network `", Check.GetName(),
		"' is already used in `", GetName(), "'");
	return 1 ;
}

void TargetSystem::AddParallelNet (Network&TheNet)
{
	if (AlreadyUsed(TheNet)) return ;
	TheParallelNets = (Network **) AddToList(&TheNet,
		(void **) TheParallelNets);
}

void TargetSystem::MakeOneNetGroup (Network&TheNet)
{
	if (GroupAlreadyUsed(TheNet.GetName())) return ;
	if (AlreadyUsed(TheNet)) return ;
	TheGroups = (TargetSystemGroup **) AddToList( new
		TargetSystemGroup(TheNet.GetName(), &TheNet),
		(void **) TheGroups) ;
}

void TargetSystem::MakeGroup (const char * GroupName)
{
	// LogOut << "TargetSystem::MakeGroup(" << GroupName << ")\n" ;
	if (GroupAlreadyUsed(GroupName)) return ;
	TheGroups = (TargetSystemGroup **) AddToList (
		new TargetSystemGroup(GroupName), (void **) TheGroups);
}

void TargetSystem::AddToGroup (const char * GroupName, Network&TheNet)
{
	// LogOut << "TargetSystem::AddToGroup(" << GroupName << ")\n" ;
	if (AlreadyUsed(TheNet)) return ;
	// LogOut << "checking for\n" ;
	if (TheGroups) for (TargetSystemGroup ** Grp = TheGroups ; *Grp;Grp++)
		if(!strcmp((*Grp)->GetName(),GroupName)) {
			// LogOut << "AddMemeber\n" ;
			(*Grp)->AddMember(TheNet);
			// LogOut << "return\n" ;
			return ;
		}
	State.Error("there is no group `", GroupName, "' in target system `",
		GetName(), "'");
}

void TargetSystem::Describe()
{
	if (TheParallelNets) {
		HelpOut << "Parallel networks in `" << GetName() << "':\n" ;
		for (Network ** Nets = TheParallelNets; *Nets; Nets++)
			HelpOut << "    " << (*Nets)->GetName() << "\n" ;
		HelpOut << "\n" ;
	} else HelpOut << "There are no parallel networks in `" <<
		GetName() << "'.\n" ;
	if (TheGroups) {
		HelpOut << "Network groups in `" << GetName() << "':\n" ;
		for (TargetSystemGroup ** Grp = TheGroups ; *Grp;Grp++)
		(*Grp)->Describe();
	} else HelpOut << "There are no network groups in `" <<
		GetName() << "'.\n" ;
}

void TargetSystem::TargetAbort()
{
	State.Error("MakeTarget for system aborted");
	delete TheSystemFile ;
	TheSystemFile = 0;
}

int TargetSystem::ChangeNetwork(ProcessNet& TheNet, const char * EnumPrefix)
{
	// LogOut << "TargetSystem::ChangeNetwork\n" ;
	*TheSystemFile << "\t" << EnumPrefix << TheNet.GetName()
		<< " = " << EnumCount++ << ",\n" ;
	// LogOut << "ToMake = 0x" << (void *) &ToMake << "\n" ;
	// LogOut << "Doing ToMake.ChangeNetwork\n" ;
	if (ToMake.ChangeNetwork(TheNet)) if (ToMake.Emit()) {
		// LogOut << "back form ToMake.ChangeNetwork\n" ;
		return 1;
	}
	TargetAbort() ;
	return  0 ;
}

void TargetSystem::StartEnum(const char *EnumName)
{
	*TheSystemFile << "enum " << EnumName << "{\n" ;
	EnumCount = 0 ;
}

void TargetSystem::EndEnum(const char* EnumPrefix)
{
	*TheSystemFile << "\t" << EnumPrefix << "Size\n};\n" ;
}

static const char * ParallelPrefix = "TheParallel" ;
static const char * AllGroups = "GroupNetIndicies" ;

void TargetSystem::OutputGroupTables()
{
	*TheSystemFile << "extern class ExecuteState * " << ParallelPrefix
		<< "StateArray[] ;\n" ;
	*TheSystemFile << "extern class ExecuteState ** " << AllGroups
		<< "StateArray[] ;\n" ;
	
	char * SystemCFileName = Concatenate(ToMake.GetDirectory(),"/",
		"sysheads.C");
	// LogOut << "SystemCFileName = `" << SystemCFileName << "'\n" ;

	ofstream * TheSystemCFile = new ofstream(SystemCFileName) ;

	if (!TheSystemCFile) {
		State.Error("Cannot create system interface file `",
			SystemCFileName, "'") ;
		TargetAbort();
		delete SystemCFileName ;
		SystemCFileName = 0 ;
		return ;
	}
	delete SystemCFileName ;
	SystemCFileName = 0 ;
	
	*TheSystemCFile << "#include \"sysheads.h\"\n\n" ;
	*TheSystemCFile << "#include \"buffer.h\"\n\n" ;
	OutputIncludes(*TheSystemCFile);
	TheSharedBuffer.declare_space(*TheSystemCFile);
/*
 *	*TheSystemCFile << "\nMachWordType " << TheSharedBuffer.Name << "[" <<
 *		TheSharedBuffer.GetMaxSize() << "];\n\n" ;
 */
	*TheSystemCFile << "ExecuteState * " << ParallelPrefix <<
		"StateArray[" << ListLength((void **) TheParallelNets) +1 
		<< "] = {\n" ;
	if (TheParallelNets) {
		for (Network ** Nets = TheParallelNets; *Nets; Nets++)
			*TheSystemCFile << "\tThe" << (*Nets)->GetName() <<
			"State,\n" ;
	}
	*TheSystemCFile << "\t0\n};\n\n" ;

	if (TheGroups) for (TargetSystemGroup ** Groups = TheGroups; *Groups ;
		Groups++) (*Groups)->OutputTables(*TheSystemCFile, AllGroups);

	*TheSystemCFile << "ExecuteState ** " << AllGroups <<
		"StateArray[" << ListLength((void **) TheGroups) +1 <<
		"] = {\n" ;
	if (TheGroups) {
	 	for (TargetSystemGroup ** Groups = TheGroups; *Groups ;
			Groups++) *TheSystemCFile <<
			"\t(ExecuteState **) &" << AllGroups <<
			ToCppName((*Groups)->GetName()) << "StateArray,\n" ;
	}
	*TheSystemCFile << "\t0\n};\n\n" ;

	delete TheSystemCFile ;
}

void TargetSystem::OutputIncludes(ofstream& TheSystemFile)
{
	if (TheParallelNets)  for (Network ** Nets = TheParallelNets; *Nets;
		Nets++) TheSystemFile << "#include \"" <<
		ToDosName((*Nets)->GetName()) << ".h\"\n" ;
	if (TheGroups) for (TargetSystemGroup ** Groups = TheGroups; *Groups ;
	    	Groups++) (*Groups)->OutputIncludes(TheSystemFile);
}


int TargetSystem::OutputDriver()
{
	// LogOut << "TargeSystem::OutputDriver()\n" ;
	const char * FileName = "netdrv.C" ;
	char * FullFileName =
		Concatenate(ToMake.GetDirectory(),"/",FileName);
	ofstream FileOut(FullFileName);
	if (!CheckFile(&FileOut,FullFileName)) return 0 ;
	FileOut << "#include \"exec.h\"\n" ;
	FileOut << "#include \"sysheads.h\"\n" ;
	// if (!DoNotEmitMain) {
		FileOut << "extern void cppmain(int argc, char ** argv);\n\n" ;
		FileOut << "main(int argc, char ** argv)\n" ;
		FileOut << "{\n" ;
		FileOut << "\tcppmain(argc,argv);\n" ;
		FileOut << "}\n\n" ;
	// }
	FileOut <<
		"/* main driver for the stand alone DSP network */\n";
	FileOut << "void cppmain(int argc, char ** argv)\n" ;
	FileOut << "{\n" ;
	FileOut << "\tfor (;;) {\n" ;

	FileOut << "\t\t/* Parallel Networks */\n" ;
	FileOut << "\t\tfor (ExecuteState ** exec = " << ParallelPrefix <<
			"StateArray;\n" ;
	FileOut << "\t\t\t*exec; exec++) (*exec)->ExecuteNetwork();\n\n" ;
	
	FileOut << "\t\t/* NetworkGroups */\n" ;
	FileOut << "\t\tfor (ExecuteState *** grp = " << AllGroups <<
		"StateArray;\n" ;
	FileOut << "\t\t\t*grp; grp++) for (exec = *grp; *exec; exec++)\n";
	FileOut << "\t\t\t\t(*exec)->ExecuteNetwork();\n\n" ;
	
	FileOut << "\t\t/* Reset everything */\n" ;
	FileOut << "\t\tfor (exec = " << ParallelPrefix <<
		"StateArray; *exec; exec++)\n";
	FileOut << "\t\t\t(*exec)->Reset();\n" ;

	FileOut << "\t\t/* NetworkGroups */\n" ;
	FileOut << "\t\tfor (grp = " << AllGroups <<
		"StateArray; *grp; grp++)\n" ;
	FileOut << "\t\t\tfor (exec = *grp; *exec; exec++) (*exec)->Reset();\n" ;
	FileOut << "\t}\n}\n\n" ;
	delete FullFileName ;
	return 1 ;
}

int TargetSystem::CountNets() const
{
	int Count = 0 ;
	if (TheParallelNets) for ( ;TheParallelNets[Count];Count++);
	if (TheGroups) for (TargetSystemGroup ** Grp = TheGroups;
		*Grp; Grp++) Count+=(*Grp)->CountNets();
	return Count ;
}

void TargetSystem::MakeTarget()
{
	// Requirements include:
	//	1. Name space - interface to user control
	//	2. Do not duplicate parameter arrays for multiple
	//		channels
	//	3. Buffer allocations consistent with group definitions
	// The following partial implementation does not do do any of the
	// above storage optimiization and does not support multiple channels.

	int NumberNets = CountNets();
	if (NumberNets > 1) {
		SharedArray * DoShareArrays = new SharedArray(
			ToMake.GetDirectory(), "arrycom.h","arrycom.C");
		ToMake.SetSharedArrays(DoShareArrays);
	}
	// LogOut << "TargetSystem::MakeTarget\n" ;
	char * SystemFileName = Concatenate(ToMake.GetDirectory(),"/",
		"sysheads.h");
	// LogOut << "SystemFileName = `" << SystemFileName << "'\n" ;

	TheSystemFile = new ofstream(SystemFileName) ;

	if (!TheSystemFile) {
		State.Error("Cannot create system interface file `",
			SystemFileName, "'") ;
		TargetAbort();
		delete SystemFileName ;
		SystemFileName = 0 ;
		return ;
	}
	delete SystemFileName ;
	SystemFileName = 0 ;

	
	if (TheParallelNets)  {
		StartEnum("ParallelNetIndicies");
		TheSharedBuffer.Reset();
		for (Network ** Nets = TheParallelNets; *Nets; Nets++) 
			if (!ChangeNetwork(**Nets,ParallelPrefix)) return ;
		EndEnum(ParallelPrefix);
	}
	if (TheGroups) {
		for (TargetSystemGroup ** Groups = TheGroups; *Groups ;
	    	Groups++) {
			// LogOut << "Doing group `" << (*Groups)->GetName() << "'.\n" ;
			const char * GroupPrefix = Concatenate("Group",
				ToCppName((*Groups)->GetName()));
			// LogOut << "GroupPrefix = " << GroupPrefix << "\n" ;
		
			StartEnum(GroupPrefix);
			TheSharedBuffer.Reset();
			if (!(*Groups)->MakeTarget(*this,GroupPrefix)) return ;
			EndEnum(GroupPrefix);
		}
		StartEnum(AllGroups);
		for (Groups = TheGroups; *Groups ; Groups++) *TheSystemFile
			<< "\t" << AllGroups << ToCppName((*Groups)->GetName())
			<< " = " << EnumCount++ << ",\n" ;
		EndEnum(AllGroups);
	}
	OutputGroupTables();
	// LogOut << "Checking for OutputDriver\n" ;
/*
 *	if (GetInterfaceDirectory()) LogOut << "Directory is `" <<
 *		GetInterfaceDirectory() << "'\n" ;
 */
	if (!GetInterfaceDirectory()) if (!OutputDriver()) {
		TargetAbort();
		return ;
	}
	// LogOut << "Back from checking for OutputDriver\n" ;
	int  Opt = TargetDescription::WriteMake ;
	// TargetDescription::TargetOptions Opt = TargetDescription::WriteMake ;
	if (GetFlags()&1) Opt |=  TargetDescription::Create ;
	if (GetFlags()&2) {
		// TheLog << "Set LinkForTiming\n" ;
		Opt |= TargetDescription::LinkForTiming ;
	}
	Opt |= TargetDescription::WriteDriver ;
	if (GetInterfaceDirectory()) Opt |= TargetDescription::OmitCppDriver ;
	// LogOut << "Calling DoWriteDriver(0x" << hex << Opt << dec << ")\n" ;
	ToMake.DoWriteDriver((TargetDescription::TargetOptions) Opt);
	// LogOut << "Calling DoWriteDriver - back \n" ;
	
	ToMake.DoWriteMake((TargetDescription::TargetOptions)Opt,
		GetInterfaceDirectory());
	delete TheSystemFile ;
}

const char * TargetSystem::DoEmitState(OutTokens& ) 
{
	return 0 ;
}

int TargetSystem::MakeSystemInterface()
{
	return 0 ;
}


