#include "Examples.h"
#include "Settings.h"
#include "FileTools.h"
#include "NetTools.h"

////////////////////////////////////////////////////////////////////////////////////////////////

// clear info data
void ExampleVersion::ClearInfo(void)
{
	_anyArchitecture	= false;
	_name				= "";
	_version			= "";
	_author				= "";
	_maintainer			= "";
	_sentence			= "";
	_paragraph			= "";
	_website			= "";
	_category			= "";
	_url				= "";
	_archiveFileName	= "";
	_size				=  0;
	_checksum			= "";
	_architectures.Clear();
	_requires.Clear();
}

// read properties file
void ExampleVersion::ReadPropertiesFile(String const &path)
{
	ClearInfo();

	String infoFile = AppendFileName(path, "example.properties");
	if(FileExists(infoFile))
	{
		FileIn f(infoFile);
		while(!f.IsEof())
		{
			String s = TrimBoth(f.GetLine());
			if(s.IsEmpty())
				continue;
			if(s.StartsWith("name="))
				_name = TrimBoth(s.Mid(5));
			else if(s.StartsWith("version="))
				_version = AVersion(TrimBoth(s.Mid(8)));
			else if(s.StartsWith("author="))
				_author = TrimBoth(s.Mid(7));
			else if(s.StartsWith("maintainer="))
				_maintainer = TrimBoth(s.Mid(11));
			else if(s.StartsWith("sentence="))
				_sentence = TrimBoth(s.Mid(9));
			else if(s.StartsWith("website="))
				_website = TrimBoth(s.Mid(8));
			else if(s.StartsWith("paragraph="))
				_paragraph = TrimBoth(s.Mid(10));
			else if(s.StartsWith("category="))
				_category = TrimBoth(s.Mid(9));
			else if(s.StartsWith("url="))
				_url = TrimBoth(s.Mid(4));
			else if(s.StartsWith("archiveFileName="))
				_archiveFileName = TrimBoth(s.Mid(16));
			else if(s.StartsWith("size="))
				_size = ScanInt(TrimBoth(s.Mid(5)));
			else if(s.StartsWith("checksum="))
				_checksum = TrimBoth(s.Mid(9));
			else if(s.StartsWith("architectures="))
			{
				String archs = TrimBoth(s.Mid(14));
				if(archs == "*")
					_anyArchitecture = true;
				else
				{
					_architectures = Split(archs, ',');
					for(int i = 0; i < _architectures.GetCount(); i++)
						_architectures[i] = TrimBoth(_architectures[i]);
				}
			}
			else if(s.StartsWith("requires="))
			{
				String reqs = TrimBoth(s.Mid(9));
				_requires = Split(reqs, ',');
				for(int i = 0; i < _requires.GetCount(); i++)
					_requires[i] = TrimBoth(_requires[i]);
			}
		}
	}
	else
	{
		String p = UnixPath(path);
		if(p.EndsWith("\\"))
			p.Trim(p.GetCount() - 1);
		_name = GetFileTitle(p);
		_anyArchitecture = true;
	}
}

// write properties file
void ExampleVersion::WritePropertiesFile(String const &path) const
{
	String infoFile = AppendFileName(path, "example.properties");
	FileOut f(infoFile);
	f << "name="			<< _name			<< "\n";
	f << "version="			<< _version			<< "\n";
	f << "author="			<< _author			<< "\n";
	f << "maintainer="		<< _maintainer		<< "\n";
	f << "sentence="		<< _sentence		<< "\n";
	f << "paragraph="		<< _paragraph		<< "\n";
	f << "category="		<< _category		<< "\n";
	f << "website="			<< _website			<< "\n";
	f << "url="				<< _url				<< "\n";
	f << "archiveFileName="	<< _archiveFileName << "\n";
	f << "size="			<< _size			<< "\n";
	f << "checksum="		<< _checksum		<< "\n";
	f << "architectures="	<< Join(_architectures, ",")	<< "\n";
	f << "requires="		<< Join(_requires, ",")			<< "\n";
	f.Close();
}

// constructor
ExampleVersion::ExampleVersion()
{
	ClearInfo();
}

// destructor
ExampleVersion::~ExampleVersion()
{
}

// get the staging archive
String ExampleVersion::GetStagingArchivePath(void) const
{
	String s = AppendFileName(Settings.GetIdeRoot(), "staging");
	s = AppendFileName(s, "examples");
	s = AppendFileName(s, _archiveFileName);
	return s;
}

// get example local folder
String ExampleVersion::GetLocalFolder(void) const
{
	String s = AppendFileName(Settings.GetIdeRoot(), "examples");
	s = AppendFileName(s, _category);
	s = AppendFileName(s, _name);
	return s;
}

///////////////////////////////////////////////////////////////////////////////////////////////	

// constructor
Example::Example()
{
	_installed = false;
	_installedVersion.Clear();
}

// destructor
Example::~Example()
{
}

// install or update an example
bool Example::Install(MaintainerVersion const &ver)
{
	// look for requested version
	int idx = _versions.Find(ver);
	if(idx < 0)
		return false;
	
	ExampleVersion &version = _versions[idx];

	// remove a previously installed version
	if(_installed)
		Remove();
	
	// download packed archive if not there
	String staging = version.GetStagingArchivePath();
	if(!FileExists(staging))
	{
		// staging not there, we shall download from peer
		// we use a timeout of 1 second for each 100 KBytes data
		// plus 1 second fixed time
		uint32_t timeout = version.GetSize() / 100 + 20000;
		
		String prompt = Format(t_("Downloading example '%s'"), _name);
		String packed = NetTools::Download(prompt, version.GetUrl(), timeout);
		if(packed.IsEmpty())
		{
			Exclamation(Format(t_("Error downloading example '%s'"), GetFileName(staging)));
			return false;
		}
		RealizeDirectory(GetFileFolder(staging));
		SaveFile(staging, packed);
	}
	
	// install the archive
	if(!FileTools::UnpackArduinoArchive(Format(t_("Installing example '%s'"), GetFileName(staging)), staging, version.GetLocalFolder()))
		return false;
	
	version.WritePropertiesFile(version.GetLocalFolder());
	
	_installed = true;
	_installedVersion = ver;
	
	return true;
}

// uninstall the example
bool Example::Remove(void)
{
	if(!_installed)
		return true;
	int idx = _versions.Find(_installedVersion);
	if(idx < 0)
		return false;
	ExampleVersion &version = _versions[idx];
	DeleteFolderDeep(version.GetLocalFolder());
	
	_installed = false;
	return true;
}

// get latest version available
MaintainerVersion Example::GetLatestVersion(void) const
{
	MaintainerVersion last;
	for(int i = 0; i < _versions.GetCount(); i++)
	{
		MaintainerVersion curr = _versions.GetKey(i);
		if(curr.version > last.version)
			last = curr;
	}
	return last;
}


///////////////////////////////////////////////////////////////////////////////////////////////	

// constructor
ExamplesClass::ExamplesClass()
{
}

// destructor
ExamplesClass::~ExamplesClass()
{
}

// scan from installed on disk
bool ExamplesClass::ScanLocal(void)
{
	// get all folders in system examples folder
	String const &rootPath = Settings.GetExamplesRoot();
	Vector<String> examplesCats = FileTools::ScanFolder(rootPath, "*", true);
	for(int iCat = 0; iCat < examplesCats.GetCount(); iCat++)
	{
		String path = AppendFileName(rootPath, examplesCats[iCat]);
		Vector<String> examples = FileTools::ScanFolder(path, "*", true);
		for(int i = 0; i < examples.GetCount(); i++)
		{
			String examplePath = AppendFileName(path, examples[i]);
			ExampleVersion version;
			version.ReadPropertiesFile(examplePath);
			String name = version._name;
			String maintainer = ExtractMaintainer(version.GetMaintainer(), version.GetAuthor());
			AVersion ver = version._version;
			MaintainerVersion mv(maintainer, ver);
			
			// look for library name into libraries
			int idx = _examples.Find(name);
			if(idx >= 0)
			{
				// found, check the version
				Example &example = _examples[idx];
				VectorMap<MaintainerVersion, ExampleVersion> const &versions = example.GetVersions();
				if(versions.Find(mv) >= 0)
				{
					example._installedVersion = mv;
					example._installed = true;
					continue;
				}
			}
			else
			{
				// should not happen... something wrong here
				RLOG("EXAMPLES OUT OF SYNC");
				RLOG("FOUND STRAY EXAMPLE '" << examplePath << "'");
	/*
				// not found, add it
				Library &newLib = _libraries.Add(name);
				newLib._name = name;
				newLib._installed = true;
				newLib._installedVersion = ver;
				newLib._versions.Add(ver, pick(version));
	*/
			}
		}
	}
	return true;
}

// clear data
void ExamplesClass::Clear(void)
{
	_examples.Clear();
}


// read from json file
bool ExamplesClass::ParseJSON(String const &s)
{
	Value v = ::ParseJSON(s);
	
	Value examplesData = v["examples"];
	if(IsValueArray(examplesData))
	{
		for(int iExample = 0; iExample < examplesData.GetCount(); iExample++)
		{
			Value libraryData = examplesData[iExample];
			
			ExampleVersion version;
			version._name				= libraryData["name"];
			version._version			= libraryData["version"];
			version._author				= libraryData["author"];
			version._maintainer			= libraryData["maintainer"];
			version._sentence			= libraryData["sentence"];
			version._paragraph			= libraryData["paragraph"];
			version._website			= libraryData["website"];
			version._category			= libraryData["category"];
			version._url				= libraryData["url"];
			version._archiveFileName	= libraryData["archiveFileName"];
			version._size				= libraryData["size"];
			version._checksum			= libraryData["checksum"];

			Value archs					= libraryData["architectures"];
			if(IsValueArray(archs))
				for(int i = 0; i < archs.GetCount(); i++)
					version._architectures.Add(archs[i]);
			Value reqs					= libraryData["requires"];
			if(IsValueArray(reqs))
				for(int i = 0; i < reqs.GetCount(); i++)
					version._requires.Add(reqs[i]);
				
			// add this example to known ones
			int idx = _examples.Find(version._name);
			if(idx < 0)
			{
				_examples.Add(version._name);
				idx = _examples.GetCount() - 1;
				_examples[idx]._name = version._name;
			}
			VectorMap<MaintainerVersion, ExampleVersion> &versions = _examples[idx]._versions;
			versions.Add(MaintainerVersion(ExtractMaintainer(version._maintainer, version._author), version._version), pick(version));
		}
	}
	
	// once parsed the json file, we shall gather all the installed examples
	// present inside folder
	ScanLocal();
	
	// sort the examples by name and its versions by highest number first
	for(int i = 0; i < _examples.GetCount(); i++)
		SortByKey(_examples[i]._versions, [](MaintainerVersion const &a, MaintainerVersion const &b){return a > b; });
	SortByKey(_examples);

	return true;
}

// find an example by name
Example const *ExamplesClass::Find(String const &name) const
{
	int i = _examples.Find(name);
	if(i < 0)
		return NULL;
	return &_examples[i];
}
	
Example *ExamplesClass::Find(String const &name)
{
	int i = _examples.Find(name);
	if(i < 0)
		return NULL;
	return &_examples[i];
}

///////////////////////////////////////////////////////////////////////////////////////////////	

ExamplesClass &__GetExamples(void)
{
	static ExamplesClass examples;
	return examples;
}
