#include "Stdafx.h"
#include "Feuille.h"

using namespace System::IO;
using namespace System::Xml;

namespace CLITableur
{
	Cellule^ Feuille::Cells(int col,int row)
	{
		return cells[col, row]; 
	}

	Feuille::Feuille()
	{
		Init();
	}

	String^ Feuille::evalue(Cellule^ cell, int col, int row)
	{
		ev->setText(cell->Formula->Substring(1));		
		String^ ret = ev->parse();

		if (ev->HasErrors)
		{
			StringBuilder^ name = gcnew StringBuilder();
			name->Append((wchar_t)('A' + col));
			name->Append((int)(row + 1));
			errors->Append(name->ToString() + ": " + ev->getErrors());
		}
		return ret;
	}

	void Feuille::ev_OnGetCellValue(String^ name, double & value)
	{
		int col = 0, row = 0;
		ev->CellNameToColRow(name, col, row);
		if (col >= cols || row >= rows)
		{
			ev->addError("Cellule hors limites " + name);
			value = 0;
		}
		else
			value = cells[col, row]->NumberValue;
	}

	void Feuille::recalc()
	{
		ev = gcnew Evaluator();
		errors = gcnew StringBuilder();

		ev->OnGetCellValue += gcnew Evaluator::del_get_cell_value(this,&Feuille::ev_OnGetCellValue);

		// création du plan
		Graphe^ graphe = gcnew Graphe();

		for (int i = 0; i < cols; i++)
			for (int j = 0; j < rows; j++)
			{
				if (cells[i, j]->Value->StartsWith("="))
				{
					cells[i, j]->Formula = cells[i, j]->Value;
					graphe->AjouterSommet(
						Evaluator::ColRowToName(i, j),
						Evaluator::GetNames(cells[i, j]->Formula));
				}
				else
				{
					if (cells[i, j]->Formula->StartsWith("="))
					{
						graphe->AjouterSommet(
							Evaluator::ColRowToName(i, j),
							Evaluator::GetNames(cells[i, j]->Formula));
					}
				}
			}

			array<String^>^ plan = graphe->TriTopologique();
			errors->Append(graphe->getErrors());
			System::Collections::Generic::Stack<String^>^ pile = 
				gcnew System::Collections::Generic::Stack<String^>();

			// exécution du plan
			for (int p = 0; p < plan->Length; p++)
			{
				int col = 0, row = 0;
				ev->CellNameToColRow(plan[p], col, row);

				if (cells[col, row]->Formula->StartsWith("="))
				{
					if (pile->Contains(plan[p]->ToUpper()))
					{
						//errors.Append("Passe " + plan[p] + "<br>");
					}
					else
					{
						pile->Push(plan[p]->ToUpper());
						//errors.Append("Evalue " + plan[p] + "<br>");
						cells[col, row]->Value = evalue(cells[col, row], col, row );

						// déclenche un événement pour actualiser l'affichage sur la grille
						OnCellUpdated(col, row, cells[col, row]->Value);
					}
				}
			}
	}  

	/// <summary>
	/// Identifie les références des cellules contenues dans une plage
	///	</summary>
	array<Cellule^>^ Feuille::GetRange(String^ range)
	{
		array<String^>^ names = range->Split(gcnew array<wchar_t> { ':' });
		System::Collections::Generic::List<Cellule^>^ res = 
			gcnew System::Collections::Generic::List<Cellule^>();

		Evaluator^ ev = gcnew Evaluator();
		int col1 = 0, col2 = 0;
		int row1 = 0, row2 = 0;

		ev->CellNameToColRow(names[0], col1, row1);
		ev->CellNameToColRow(names[1], col2, row2);

		for (int i = col1; i <= col2; i++)
			for (int j = row1; j <= row2; j++)
			{
				res->Add(cells[i, j]);
			}

			return res->ToArray();
	}

	/// <summary>
	/// Enregistre le document vers le fichier filename
	///	</summary>
	void Feuille::SaveToFilename()
	{
		XmlDocument^ doc = gcnew XmlDocument();
		XmlNode^ root = doc->CreateElement("document");
		doc->AppendChild(root);
		for(int col=0; col<cols; col++)
			for (int row = 0; row < rows; row++)
			{
				if (cells[col, row]->Value != "" || cells[col, row]->Formula != "")
				{
					XmlNode^ n = cells[col, row]->ToXml(doc,col,row);
					root->AppendChild(n);
				}
			}

			StringBuilder^ sb = gcnew StringBuilder();
			XmlTextWriter^ writer = gcnew XmlTextWriter(gcnew StringWriter(sb));
			writer->Formatting = Formatting::Indented;

			doc->WriteTo(writer);
			writer->Flush();

			FileStream^ fs = gcnew FileStream(filename,FileMode::Create,FileAccess::Write);
			StreamWriter^ sw = gcnew StreamWriter(fs);
			sw->Write(sb->ToString());
			sw->Flush();
			fs->Close();	

			isModified = false;
	}

	void Feuille::Save()
	{
		SaveToFilename();
	}

	void Feuille::Save(String^ filename)
	{
		this->filename = filename;
		SaveToFilename();
	}

	void Feuille::Load(String^ filename)
	{
		Init();

		this->filename = filename;

		XmlDocument^ doc = gcnew XmlDocument();
		doc->Load(filename);

		XmlNode^ n = doc->DocumentElement->FirstChild;
		while (n != nullptr)
		{
			if (n->NodeType == XmlNodeType::Element &&
				n->Name == "cell")
			{
				int col, row;
				col = System::Int32::Parse(n->Attributes["col"]->Value);
				row = System::Int32::Parse(n->Attributes["row"]->Value);

				cells[col, row]->FromXml(n);

				OnCellUpdated(col, row, cells[col, row]->Value);
				OnCellStyleUpdated(col,row);
			}
			n = n->NextSibling;
		}
		recalc();
	}

	void Feuille::Init()
	{
		isModified = false;
		filename = "";
		fileshortname = "";

		rows = ROWS;
		cols = COLS;
		cells = gcnew array<Cellule^,2>(cols, rows);

		for (int i = 0; i < cols; i++)
		{			
			for (int j = 0; j < rows; j++)
				cells[i, j] = gcnew Cellule();
		}
	}

	void Feuille::SaveCopyExcel(String^ filename)
	{
		String^ excelStream = GetExcelStream();

		FileStream^ fs = gcnew FileStream(filename,FileMode::Create,FileAccess::Write);
		StreamWriter^ sw = gcnew StreamWriter(fs);
		sw->Write(excelStream);
		sw->Flush();
		fs->Close();	
	}

	String^ Feuille::GetExcelStream()
	{
		XmlDocument^ doc = gcnew XmlDocument();
		XmlNamespaceManager^ mgr = gcnew XmlNamespaceManager(doc->NameTable);
		mgr->AddNamespace("", "urn:schemas-microsoft-com:office:spreadsheet");
		mgr->AddNamespace("ss", "urn:schemas-microsoft-com:office:spreadsheet");

		XmlProcessingInstruction^ pi = doc->CreateProcessingInstruction("mso-application", "progid='Excel.Sheet'");
		doc->AppendChild(pi);

		XmlElement^ workbook = doc->CreateElement("Workbook", "urn:schemas-microsoft-com:office:spreadsheet");
		doc->AppendChild(workbook);

		XmlElement^ worksheet = doc->CreateElement("Worksheet", "urn:schemas-microsoft-com:office:spreadsheet");

		XmlAttribute^ name = doc->CreateAttribute("ss", "Name", "urn:schemas-microsoft-com:office:spreadsheet");
		name->Value = "Tableau 1";
		workbook->AppendChild(worksheet);
		worksheet->Attributes->Append(name);

		XmlElement^ table = doc->CreateElement("Table", "urn:schemas-microsoft-com:office:spreadsheet");
		worksheet->AppendChild(table);

		XmlAttribute^ arow = doc->CreateAttribute("ss:ExpandedRowCount", "urn:schemas-microsoft-com:office:spreadsheet");
		arow->Value = "" + rows;

		XmlAttribute^ acol = doc->CreateAttribute("ss:ExpandedColumnCount", "urn:schemas-microsoft-com:office:spreadsheet");
		acol->Value = "" + cols;

		table->Attributes->Append(arow);
		table->Attributes->Append(acol);

		for (int i = 0; i < rows; i++)
		{
			XmlElement^ row = doc->CreateElement("Row", "urn:schemas-microsoft-com:office:spreadsheet");
			table->AppendChild(row);

			XmlAttribute^ ati = doc->CreateAttribute("ss:Index", "urn:schemas-microsoft-com:office:spreadsheet");
			
			for (int j = 0; j < cols; j++)
			{
				XmlElement^ cl = doc->CreateElement("Cell", "urn:schemas-microsoft-com:office:spreadsheet");
				row->AppendChild(cl);

				XmlAttribute^ atc = doc->CreateAttribute("ss:Index", "urn:schemas-microsoft-com:office:spreadsheet");
				atc->Value = "" + (j + 1);
				
				XmlElement^ data = doc->CreateElement("Data", "urn:schemas-microsoft-com:office:spreadsheet");
				cl->AppendChild(data);

				XmlAttribute^ typ = doc->CreateAttribute("ss:Type", "urn:schemas-microsoft-com:office:spreadsheet");
				if (cells[j, i]->IsNumber())
					typ->Value = "Number";
				else
					typ->Value = "String";

				data->Attributes->Append(typ);
				XmlText^ value = doc->CreateTextNode(cells[j, i]->Value);
				data->AppendChild(value);
			}
		}

		StringBuilder^ sb = gcnew StringBuilder();
		XmlTextWriter^ writer = gcnew XmlTextWriter(gcnew StringWriter(sb));
		//writer.Formatting = Formatting.Indented;
		doc->WriteTo(writer);
		writer->Flush();
		return "<?xml version=\"1.0\"?>" + sb->ToString();
	} 	
}