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

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

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

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

	String infoFile = AppendFileName(path, "library.properties");
	if(FileExists(infoFile))
	{
		_oldStyle = !DirectoryExists(AppendFileName(path, "src"));
		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
	{
		String p = UnixPath(path);
		if(p.EndsWith("\\"))
			p.Trim(p.GetCount() - 1);
		_name = GetFileTitle(p);
		_anyArchitecture = true;
	}
}

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

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

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

// get library local folder
String LibraryVersion::GetLocalFolder(void) const
{
	String s = AppendFileName(Settings.GetIdeRoot(), "libraries");
	s = AppendFileName(s, _name);
	return s;
}

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

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

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

// install or update a library
bool Library::Install(MaintainerVersion const &ver)
{
	// look for requested version
	int idx = _versions.Find(ver);
	if(idx < 0)
		return false;
	
	LibraryVersion &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 library '%s'"), _name);
		String packed = NetTools::Download(prompt, version.GetUrl(), timeout);
		if(packed.IsEmpty())
		{
			Exclamation(Format(t_("Error downloading library '%s'"), GetFileName(staging)));
			return false;
		}
		RealizeDirectory(GetFileFolder(staging));
		SaveFile(staging, packed);
	}
	
	// install the archive
	if(!FileTools::UnpackArduinoArchive(Format(t_("Installing library '%s'"), GetFileName(staging)), staging, version.GetLocalFolder()))
		return false;
	
	_installed = true;
	_installedVersion = ver;
	
	return true;
}

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

// get latest version available
MaintainerVersion Library::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
LibrariesClass::LibrariesClass()
{
}

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

// scan from installed on disk
bool LibrariesClass::ScanLocal(void)
{
	// get all folders in system library folder
	String const &path = Settings.GetSystemLibsRoot();
	Vector<String> libs = FileTools::ScanFolder(path, "*", true);
	for(int i = 0; i < libs.GetCount(); i++)
	{
		String libPath = AppendFileName(path, libs[i]);
		LibraryVersion version;
		version.ReadPropertiesFile(libPath);
		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 = _libraries.Find(name);
		if(idx >= 0)
		{
			// found, check the version
			Library &lib = _libraries[idx];
			VectorMap<MaintainerVersion, LibraryVersion> const &versions = lib.GetVersions();
			if(versions.Find(mv) >= 0)
			{
				lib._installedVersion = mv;
				lib._installed = true;
				continue;
			}
		}
		else
		{
			// should not happen... something wrong here
			RLOG("LIBS OUT OF SYNC");
			RLOG("FOUND STRAY LIBRARY '" << libPath << "'");
/*
			// 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 LibrariesClass::Clear(void)
{
	_libraries.Clear();
}


// read from json file
bool LibrariesClass::ParseJSON(String const &s)
{
	Value v = ::ParseJSON(s);
	
	Value librariesData = v["libraries"];
	if(IsValueArray(librariesData))
	{
		for(int iLibrary = 0; iLibrary < librariesData.GetCount(); iLibrary++)
		{
			Value libraryData = librariesData[iLibrary];
			
			LibraryVersion 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 types					= libraryData["types"];
			if(IsValueArray(types))
				for(int i = 0; i < types.GetCount(); i++)
					version._types.Add(types[i]);
				
			// add this library to known ones
			int idx = _libraries.Find(version._name);
			if(idx < 0)
			{
				_libraries.Add(version._name);
				idx = _libraries.GetCount() - 1;
				_libraries[idx]._name = version._name;
			}
			VectorMap<MaintainerVersion, LibraryVersion> &versions = _libraries[idx]._versions;
			versions.Add(MaintainerVersion(ExtractMaintainer(version._maintainer, version._author), version._version), pick(version));
		}
	}
	
/*
	@@@@ WE DO THIS AFTER LOADING ALL JSON DATA (MANY URLS...)
	// once parsed the json file, we shall gather all the installed libraries
	// present inside folder
	ScanLocal();
*/
	
	// sort the libraries by name and its versions by highest number first
	for(int i = 0; i < _libraries.GetCount(); i++)
		SortByKey(_libraries[i]._versions, [](MaintainerVersion const &a, MaintainerVersion const &b){return a > b; });
	SortByKey(_libraries);

	return true;
}

// find a library by name
Library const *LibrariesClass::Find(String const &name) const
{
	int i = _libraries.Find(name);
	if(i < 0)
		return NULL;
	return &_libraries[i];
}
	
Library *LibrariesClass::Find(String const &name)
{
	int i = _libraries.Find(name);
	if(i < 0)
		return NULL;
	return &_libraries[i];
}

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

LibrariesClass &__GetLibraries(void)
{
	static LibrariesClass lib;
	return lib;
}

