#include <crtdbg.h>

#include <sstream>
#include <iostream>
#include <fstream>

#include "PropertiesInterface.h"
#include "CodeGenerator.h"

using namespace std;


namespace NDTS
{
	namespace SCO
	{

CodeGenerator::CodeGenerator(GroupProperty *pRoot)
{
	this->pRoot = pRoot;
}

CodeGenerator::~CodeGenerator(void)
{
}


void CodeGenerator::createEnumHeaderFile(const char *path, const char *namespaceName, enum ManagedOrUnmanaged managedOrUnmanaged)
{
  this->managedOrUnmanaged = managedOrUnmanaged;
  filebuf fb;
  fb.open (path, ios::out);
  ostream os(&fb);

  os << "\n";
  os << "  ////////////////////////////////////////////////////////////////////////////////////////\n";
  os << "  // Automatically generated list of enums from EnumProperties that is built with the SDK\n";
  os << "  //\n";
  os << "  // PropertiesInterface.h and the SDK specific include file (e.g. NDTSRS2A.h) must be\n";
  os << "  // included before this file.\n";
  os << "  ////////////////////////////////////////////////////////////////////////////////////////\n";
  os << "\n";
  os << "#pragma once\n";
  os << "\n";
  if (this->managedOrUnmanaged == Managed)
  {
   os << "using namespace NDTS::Managed::SCO;\n";
  }
  else
  {
  os << "using namespace NDTS::SCO;\n";
  }
  os << "\n";
  os << "namespace NDTS\n";
  os << "{\n";
  if (this->managedOrUnmanaged == Managed)
  {
	  os << "namespace Managed\n";
	  os << "{\n";
  }
  os << "  namespace " << namespaceName << "\n";
  os << "  {\n";
  os << "    namespace EnumPropertyNames\n";
  os << "    {\n";

  createEnumsForGroup(os, pRoot);
  
  os << "    }\n";
  os << "  }\n";
  if (this->managedOrUnmanaged == Managed)
	  os << "}\n";
  os << "}\n";
  fb.close();

}


void CodeGenerator::createStringsHeaderFile(const char *path, const char *namespaceName, enum ManagedOrUnmanaged managedOrUnmanaged)
{
  this->managedOrUnmanaged = managedOrUnmanaged;
  filebuf fb;
  fb.open (path, ios::out);
  ostream os(&fb);

  os << "\n";
  os << "  ////////////////////////////////////////////////////////////////////////////////////////\n";
  os << "  // Automatically generated list of strings from properties that is built with the SDK\n";
  os << "  //\n";
  os << "  // PropertiesInterface.h and the SDK specific include file (e.g. NDTSRS2A.h) must be\n";
  os << "  // included before this file.\n";
  os << "  ////////////////////////////////////////////////////////////////////////////////////////\n";
  os << "\n";
  os << "#pragma once\n";
  os << "\n";
  if (this->managedOrUnmanaged == Managed)
  {
   os << "using namespace NDTS::Managed::SCO;\n";
  }
  else
  {
  os << "using namespace NDTS::SCO;\n";
  }
  os << "\n";
  os << "namespace NDTS\n";
  os << "{\n";
  if (this->managedOrUnmanaged == Managed)
  {
	  os << "namespace Managed\n";
	  os << "{\n";
  }
  os << "  namespace " << namespaceName << "\n";
  os << "  {\n";
  os << "    namespace propertyStrings\n";
  os << "    {\n";

  createStringsForGroup(os, pRoot);
  
  os << "    }\n";
  os << "  }\n";
  if (this->managedOrUnmanaged == Managed)
	  os << "}\n";
  os << "}\n";
  fb.close();

}

void CodeGenerator::createEnumsForGroup(ostream &os, GroupProperty *pGroup)
{
	if (pGroup->getIsArray())
	{
		int i=0;
		i++;
	}
	
	int count = pGroup->getIsArray() ? 1 : pGroup->getNumberOfProperties() ;

	for (int i=0; i < count ; i++)
	{
		Property *pCurrentProp = pGroup->getPropertyByNumber(i);
		GroupProperty *pSubGroup = dynamic_cast<GroupProperty*>(pCurrentProp);
		// Recurse if the property is a group
		if (pSubGroup)
			createEnumsForGroup(os, pSubGroup);
		// List the names if the property is an enum
		EnumProperty *pEnumProperty = pGroup->getProperty<EnumProperty>(i) ;
		if (pEnumProperty)
		{
			listEnumNames(os, pEnumProperty);
		}
	}
}

void CodeGenerator::createStringsForGroup(ostream &os, GroupProperty *pGroup)
{
	int count = pGroup->getIsArray() ? 1 : pGroup->getNumberOfProperties() ;

	for (int i=0; i < count ; i++)
	{
		Property *pCurrentProp = pGroup->getPropertyByNumber(i);
		GroupProperty *pSubGroup = dynamic_cast<GroupProperty*>(pCurrentProp);
		// Recurse if the property is a group
		if (pSubGroup)
			createStringsForGroup(os, pSubGroup);
		os << "       static LPCTSTR p" << getNameFromPropName(pCurrentProp) << " = _T(\"" << pCurrentProp->getShortName() << "\");\n";
	}
}
void CodeGenerator::listEnumNames(ostream &os, EnumProperty *pEnumProperty)
{
	if (this->managedOrUnmanaged == Managed)
	{
	os << "      public enum class  " << getNameFromPropName(pEnumProperty) << "\n";
	}
	else
	{
	os << "      struct " << getNameFromPropName(pEnumProperty) <<" \n";
	os << "      {\n";
	os << "        enum enm" << getNameFromPropName(pEnumProperty) << "\n";
	}
	os << "        {\n";
	for (int i=0; i < pEnumProperty->getNumberOfEnumStrings(); i++)
	{
	os << "          " << stripNonAlpha(pEnumProperty->getEnumStringByIndex(i)) << ",\n";
	}
	os << "        };\n";
	if (this->managedOrUnmanaged == Unmanaged)
	{
	os << "      };\n";
	}
}

void CodeGenerator::createCPPHeaderFile(const char *path, const char *namespaceName, const char *rootDeclaration, enum ManagedOrUnmanaged managedOrUnmanaged)
{
  this->managedOrUnmanaged = managedOrUnmanaged;
  filebuf fb;
  fb.open (path, ios::out);
  ostream os(&fb);

  os << "\n";
  os << "  ////////////////////////////////////////////////////////////////////////////////////////\n";
  os << "  // Automatically generated list of properties that is built with the SDK\n";
  os << "  //\n";
  os << "  // PropertiesInterface.h and the SDK specific include file (e.g. NDTSRS2A.h) must be\n";
  os << "  // included before this file.\n";
  os << "  ////////////////////////////////////////////////////////////////////////////////////////\n";
  os << "\n";
  os << "#pragma once\n";
  os << "\n";
  if (this->managedOrUnmanaged == Managed)
  {
  os << "using namespace NDTS::Managed::SCO;\n";
  }
  else
  {
  os << "using namespace NDTS::SCO;\n";
  }
  os << "\n";
  os << "namespace NDTS\n";
  os << "{\n";
  if (this->managedOrUnmanaged == Managed)
  {
  os << "namespace Managed\n";
  os << "{\n";
  }
  os << "namespace " << namespaceName << "\n";
  os << "  {\n";

  if (this->managedOrUnmanaged == Managed)
  {
  os << "      public ref class PropertyVariables\n    {\n";
  }
  else
  {
  os << "    class PropertyVariables\n    {\n";
  }
  os << "    public:\n";
  os << "\n";
  if (this->managedOrUnmanaged == Managed)
  {
  os << "      GroupProperty ^" << getVariableNameFromPropName(pRoot) << ";\n";
  }
  else
  {
  os << "      GroupProperty *" << getVariableNameFromPropName(pRoot) << ";\n";
  }
  vector<int> v;
  createVariableDeclarationsForGroup(os, pRoot, v);

  os << "  ////////////////////////////////////////////////////////////////////////////////////////\n";
  os << "  // Below here is the auto generated implementation that assigns values to properties\n";
  os << "  // This code is subject to frequent change and should not be used in application code\n";
  os << "  ////////////////////////////////////////////////////////////////////////////////////////\n";
  os << "\n";
  os << "    PropertyVariables::PropertyVariables()\n";
  os << "    {\n";

  os << rootDeclaration << "\n";
  if (this->managedOrUnmanaged == Managed)
  {
	  createVariableAllocationsForGroup(os, pRoot, v);
  }
  createAssignmentsForGroup(os, pRoot, 0);

  os << "    }\n\n";
  os << "    PropertyVariables::~PropertyVariables()\n";
  os << "    {\n";
  os << "    }\n";

  os << "  };\n  }\n}\n";
  if (this->managedOrUnmanaged == Managed)
  {
  os << "}\n";
  }
  fb.close();

}

// Strip all non alphabetic characters from a string
string CodeGenerator::stripNonAlpha(const string &name)
{
	string result = "";
	for (unsigned int i=0; i < name.length(); i++)
	{
		if (isalnum(name[i]))
			result += name[i];
	}
	// Prepend an _ if the name begins with a digit to make it a legal C++ identifier
	if (!isalpha(result[0]))
		result = "_" + result;

	return result;
};

// Get a name from a property name.  The name is used directly in the Enum declaration
string CodeGenerator::getNameFromPropName(Property *pProperty)
{
	return stripNonAlpha(pProperty->getShortName());
}

// Get a variable name from a property
// Unmanaged is pPropertyName managed has propertyName
string CodeGenerator::getVariableNameFromPropName(Property *pProperty)
{
	string result =  getNameFromPropName(pProperty);
	if (managedOrUnmanaged == Managed)
	{
		result[0] = _tolower(result[0]);
		return result; 
	}
	else
	{
		return "p" + result; 
	}
}

// Returns true if the property is of a type that we can generate a variable for
// Keep this list and the getVariableTypeString list in sync 
bool CodeGenerator::isKnownType(Property *pProp)
{
	return dynamic_cast<GroupProperty*>(pProp) ||
		dynamic_cast<EventProperty*>(pProp) ||
		dynamic_cast<SIProperty*>(pProp) ||
		dynamic_cast<IntProperty*>(pProp) ||
		dynamic_cast<EnumProperty*>(pProp);
}

// Return the variable type of the property as a string for use in a declaration
string CodeGenerator::getVariableTypeString(Property *pProp)
{
	_ASSERTE(isKnownType(pProp));
	string varType("");
	if (dynamic_cast<GroupProperty*>(pProp))
		varType = "GroupProperty";
	else if (dynamic_cast<EnumProperty*>(pProp))
		varType = "EnumProperty ";
	else if (dynamic_cast<IntProperty*>(pProp))
		varType = "IntProperty  ";
	else if (dynamic_cast<EventProperty*>(pProp))
		varType = "EventProperty";
	else if (dynamic_cast<SIProperty*>(pProp))
		varType = "SIProperty";
	else
		_ASSERTE(0);
	return varType;
}

void CodeGenerator::createVariableDeclarationsForGroup(ostream &os, GroupProperty *pGroup, vector<int> arraySizes)
{
	//  Check for an array
	if (pGroup->getIsArray())
	{
		// When properties are inside an array, they are generated as arrays
		// We add this array to the list of nested array prefixes to apply.
		arraySizes.push_back(pGroup->getNumberOfProperties());
		// An array property contains a single group containing all the properties within 
		// the array.  We recurse through this group using the new array prefix.
		//
		// (pGroup->getIsArray() ? 1 :-
		// Since we are creating the variable declarations, these will be the same for each member
		// of the array so we may as well generate the declaration for the first sub group
	}
	for (int i=0; i < (pGroup->getIsArray() ? 1 : pGroup->getNumberOfProperties()); i++)
	{
		Property *pCurrentProp = pGroup->getPropertyByNumber(i);
		// Only generate declarations for known property types
		const char *p = pCurrentProp->getShortName();
		p;
		if (isKnownType(pCurrentProp))
		{
			GroupProperty *pSubGroup = dynamic_cast<GroupProperty*>(pCurrentProp);
			if (managedOrUnmanaged == Managed)
			{
				os << "      " << getManagedArrayDeclarationsString(arraySizes, getVariableTypeString(pCurrentProp)) << getVariableNameFromPropName(pCurrentProp)   << ";   // " << pCurrentProp->getLongName() << "\n";
			}
			else
			{
				os << "      " << getVariableTypeString(pCurrentProp) << " *" << getVariableNameFromPropName(pCurrentProp)  << getArrayDeclarationsString(arraySizes)  << ";   // " << pCurrentProp->getLongName() << "\n";
			}
			// Recurse if the property is a group
			if (pSubGroup)
				createVariableDeclarationsForGroup(os, pSubGroup, arraySizes);
		}
	}
}


void CodeGenerator::createAssignmentsForGroup(ostream &os, GroupProperty *pGroup, int arrayDepth)
{
	//  Check for an array
	if (pGroup->getIsArray())
	{
		// Generate a for loop using i1, i2, etc. for nested arrays
		arrayDepth++;
		os << "      "  << "for (int i" << arrayDepth << "=0; i" << arrayDepth << "<" << pGroup->getNumberOfProperties() << "; i" << arrayDepth << "++) \n       {\n";
		// Create a special assignment statement for the group within the array group
		GroupProperty *pSubGroup = dynamic_cast<GroupProperty *>(pGroup->getPropertyByNumber(0));
		os << "      "  << getVariableNameFromPropName(pSubGroup) << getArrayIndicesString(arrayDepth) << " = dynamic_cast<" << getVariableTypeString(pSubGroup) << ((this->managedOrUnmanaged == Managed) ? " ^>(" : " *>(") << getVariableNameFromPropName(pGroup) << getArrayIndicesString(arrayDepth-1) << "->getPropertyByNumber(i" << arrayDepth << "));\n";
		createAssignmentsForGroup(os, pSubGroup, arrayDepth);
		// The else below prevents the normal assignment statement from being generated
	}
	else
	{
		for (int i=0; i < pGroup->getNumberOfProperties(); i++)
		{
			Property *pCurrentProp = pGroup->getPropertyByNumber(i);
			// Only generate declarations for known property types
			if (isKnownType(pCurrentProp))
			{
				GroupProperty *pSubGroup = dynamic_cast<GroupProperty*>(pCurrentProp);
				if (managedOrUnmanaged == Managed)
				{
					os << "      "  << getVariableNameFromPropName(pCurrentProp)  << getArrayIndicesString(arrayDepth) << " = " << " dynamic_cast<" << getVariableTypeString(pCurrentProp) <<  " ^>(" << getVariableNameFromPropName(pGroup) << getArrayIndicesString(arrayDepth) << "->getPropertyByName(\"" << pCurrentProp->getShortName() << "\"));\n";
				}
				else
				{
					os << "      "  << getVariableNameFromPropName(pCurrentProp)  << getArrayIndicesString(arrayDepth) << " = (" << getVariableNameFromPropName(pGroup) << getArrayIndicesString(arrayDepth) <<  " == 0) ? 0 : dynamic_cast<" << getVariableTypeString(pCurrentProp) << " *>(" << getVariableNameFromPropName(pGroup) << getArrayIndicesString(arrayDepth) << "->getPropertyByName(\"" << pCurrentProp->getShortName() << "\"));\n";
				}
				// Recurse if the property is a group
				if (pSubGroup)
					createAssignmentsForGroup(os, pSubGroup, arrayDepth);
			}
		}
	}
	if (pGroup->getIsArray())
	{
		// Write the terminating } for the for loop and reduce the array depth
		os << "       }\n";
		_ASSERTE(arrayDepth > 0);
		arrayDepth--;
	}

}

string CodeGenerator::getArrayDeclarationsString(vector<int> arraySizes)
{
	string result = "";
	for (vector<int>::iterator  it = arraySizes.begin(); it != arraySizes.end();  it++)
	{
		int size = *it;
		result += "[";
		std::ostringstream strout;
		strout << size;
		result += strout.str();
		result += "]";
	}
	return result;
}
string CodeGenerator::getArrayIndicesString(int arrayDepth) 
{
	string result = "";
	if (arrayDepth == 0)
		return result;
	if (this->managedOrUnmanaged == Managed)
		result += "[";
	for (int i=0; i<arrayDepth; i++)
	{
	  if (this->managedOrUnmanaged == Managed)
	  {
		result += "i";
		std::ostringstream strout;
		strout << i+1;
		result += strout.str();
		if (i < arrayDepth-1)
			result += ",";
	  }
	  else
	  {
		result += "[i";
		std::ostringstream strout;
		strout << i+1;
		result += strout.str();
		result += "]";
	  }
	}
	if (this->managedOrUnmanaged == Managed)
		result += "]";
	return result;
}

string CodeGenerator::getManagedArrayDeclarationsString(vector<int> arraySizes, string className)
{
	if (arraySizes.size() == 0)
	{
		// MyClass ^
		string result = className + "^ ";
		return result;
	}
	else
	{
		// array< MyClass^, 2 > ^  
		string result = "array<" + className + "^, ";
		std::ostringstream strout;
		strout << (unsigned int)(arraySizes.size());
		result += strout.str();
		result += "> ^";
		return result;
	}
}

// Similar to createVariableDeclarationsForGroup 
// The variable allocations are only needed for managed code
void CodeGenerator::createVariableAllocationsForGroup(ostream &os, GroupProperty *pGroup, vector<int> arraySizes)
{
	//  Check for an array
	if (pGroup->getIsArray())
	{
		// Add this array to the list of nested array prefixes to apply.
		arraySizes.push_back(pGroup->getNumberOfProperties());
	}
	for (int i=0; i < (pGroup->getIsArray() ? 1 : pGroup->getNumberOfProperties()); i++)
	{
		Property *pCurrentProp = pGroup->getPropertyByNumber(i);
		const char *p = pCurrentProp->getShortName();
		p;
		if (isKnownType(pCurrentProp))
		{
			GroupProperty *pSubGroup = dynamic_cast<GroupProperty*>(pCurrentProp);
			createManagedArrayAllocationString(os, arraySizes, getVariableTypeString(pCurrentProp), getVariableNameFromPropName(pCurrentProp));
			// Recurse if the property is a group
			if (pSubGroup)
				createVariableAllocationsForGroup(os, pSubGroup, arraySizes);
		}
	}
}

void CodeGenerator::createManagedArrayAllocationString(ostream &os, vector<int> arraySizes, string className, string variableName)
{
	// Don't need an allocation unless variable is an array
	if (arraySizes.size() == 0)
		return;
	// local = gcnew array< MyClass^, 2 >(ARRAY_SIZE, ARRAY_SIZE);
	os << "     " << variableName << " = gcnew array<" << className << "^, " << (unsigned int)(arraySizes.size()) << ">(";
	for (unsigned int i=0; i<(unsigned int)(arraySizes.size()); i++)
	{
		os << arraySizes[i];
		if (i < arraySizes.size() - 1)
			os << ",";
	}
	os << ");\n";
}
}}


