#include "Platform.h"
#include "Package.h"

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

#include <Archive/Archive.h>

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


// constructor
PlatformVersion::PlatformVersion(Platform *platform)
{
	_platform = platform;
	
}

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

// check if is installed
bool PlatformVersion::CheckInstall(void)
{
	return true;
}

// get local root folder
String PlatformVersion::GetLocalFolder(void)
{
	String s = AppendFileName(Settings.GetIdeRoot(), "packages");
	s = AppendFileName(s, _platform->GetPackage().GetName());
	s = AppendFileName(s, "hardware");
	s = AppendFileName(s, _platform->GetArchitecture());
	s = AppendFileName(s, _version);
	return s;
}

// get local staging archive
String PlatformVersion::GetStagingArchivePath(void)
{
	String s = AppendFileName(Settings.GetIdeRoot(), "staging");
	s = AppendFileName(s, "packages");
	s = AppendFileName(s, _archiveName);
	return s;
}

// parse board file
bool PlatformVersion::ParseBoardsFile(void)
{
	String boardsFile = AppendFileName(GetLocalFolder(), "boards.txt");
	
	// read the file in form of a map of (name, value) pairs
	VectorMap<String, String> map = FileTools::ReadValueFile(boardsFile);
	
	// board name is given in form of NAME.name = DESCRIPTION
	// so we shall locate any key with this format first to get
	// board names and descriptions; then we create the board
	// and let it to parse rest of data
	for(int i = 0; i < map.GetCount(); i++)
	{
		String key = map.GetKey(i);
		int namePos = key.Find(".name");
		if(namePos < 0)
			continue;
		if(key.Find(".") != namePos)
			continue;
		String name = key.Left(namePos);
		if(_boards.Find(name) < 0)
		{
			Board &board = _boards.Add(name, new Board(_platform));
			board._name = name;
			board.ParseBoardsFile(map);
		}
	}
	return true;
}

// parse a map containing platform file in form ok key=value
bool PlatformVersion::ParsePlatformFile()
{
	String platformFile = AppendFileName(GetLocalFolder(), "platform.txt");
	_values = FileTools::ReadValueFile(platformFile);
	
	// append platform runtime values
	_values.Add("runtime.platform.path", GetLocalFolder());
	
	// appena build architecture
	_values.Add("build.arch", ToUpper(_platform->GetArchitecture()));
	return true;
}
	
// get dependent tools values
VectorMap<String, String> PlatformVersion::GetToolsValues(void) const
{
	VectorMap<String, String>res;
	for(int iTool = 0; iTool < _toolsDeps.GetCount(); iTool++)
	{
		ToolDep const &dep = _toolsDeps[iTool];
		
		// first try to locate in current package
		Tool const *tool = _platform->GetPackage().FindTool(dep._name, dep._version);
		
		// if not found, look elsewhere
		if(!tool)
		{
			for(int iPackage = 0; iPackage < Packages.GetCount(); iPackage++)
			{
				Package const &package = Packages[iPackage];
				tool = package.FindTool(dep._name, dep._version);
				if(tool)
					break;
			}
		}

		if(tool)
		{
			VectorMap<String, String> toolVars = tool->GetVariables();
			for(int i = 0; i < toolVars.GetCount(); i++)
				res.Add(toolVars.GetKey(i), toolVars[i]);
		}
	}
	return res;
}
	

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

// constructor
Platform::Platform(Package *package)
{
	_package = package;

	_installed = false;
	_installedVersion = "";
}

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

// parse platform data contained in a Value object
bool Platform::ParseJSON(Value const &json)
{
	// if this version is already there, just do nothing
	String version = json["version"];
	if(_versions.Find(version) >= 0)
		return true;
	
	// add a new version
	PlatformVersion &platformVersion = _versions.Add(version, new PlatformVersion(this));
		
	_name			= json["name"];
	_architecture	= json["architecture"];
	_cathegory		= json["cathegory"];

	platformVersion._version		= json["version"];
	platformVersion._url			= json["url"];
	platformVersion._archiveName	= json["archiveFileName"];
	platformVersion._checksum		= json["checksum"];
	
	// size is sometimes given as an integer (without quotes)
	// and sometimes as a string (quoted)
	Value size		= json["size"];
	if(size.Is<String>())
		platformVersion._size = ScanInt((String)(size));
	else
		platformVersion._size = (int)size;
	
	// read provided boards
	// we can't parse the boards file, as it may be not installed now
	// so we take board names from the json
	Value boardsData = json["boards"];
	for(int iBoard = 0; iBoard < boardsData.GetCount(); iBoard++)
		platformVersion._providedBoards.Add(boardsData[iBoard]["name"]);

	// read tools dependencies
	Value toolsDepsData = json["toolsDependencies"];
	for(int iDep = 0; iDep < toolsDepsData.GetCount(); iDep++)
	{
		Value toolDepData = toolsDepsData[iDep];
		PlatformVersion::ToolDep &toolDep = platformVersion._toolsDeps.Add();
		toolDep._name		= toolDepData["name"];
		toolDep._packager	= toolDepData["packager"];
		toolDep._version	= toolDepData["version"];
	}
	
	// sort versions, descending
	SortByKey(_versions, [](String a, String b) { return AVersion(a) > AVersion(b); });
	
	return true;
}

// install (or reinstall) platform files (and dependencies)
bool Platform::Install(String const &version)
{
	PlatformVersion &platformVersion = _versions.Get(version);
	
	// if requested version is already installed, do nothing
	if(_installed && _installedVersion == version && platformVersion.CheckInstall())
		return true;
	
	// if a previous version is installed, remove it
	if(_installed)
	{
		PlatformVersion &prevVersion = _versions.Get(_installedVersion);
		FileTools::DeleteFolderDeep(prevVersion.GetLocalFolder());
	}
	
	// clear installed flags
	_installed = false;
	_installedVersion = "";
	
	// download packed archive if not there
	String staging = platformVersion.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 = platformVersion.GetArchiveSize() / 10 + 20000;
		
		String prompt = Format(t_("Downloading platform '%s'"), _name);
		String packed = NetTools::Download(prompt, platformVersion.GetUrl(), timeout);
		if(packed.IsEmpty())
		{
			Exclamation(Format(t_("Error downloading package '%s'"), GetFileName(staging)));
			return false;
		}
		RealizeDirectory(GetFileFolder(staging));
		SaveFile(staging, packed);
	}
	
	// install the archive
	if(!FileTools::UnpackArduinoArchive(Format(t_("Installing platform '%s'"), GetFileName(staging)), staging, platformVersion.GetLocalFolder()))
		return false;
	
	// install dependent tools
	Vector<PlatformVersion::ToolDep> const &deps = platformVersion.GetToolsDeps();
	for(int iDep = 0; iDep < deps.GetCount(); iDep++)
	{
		// get current dependency data
		PlatformVersion::ToolDep const &dep = deps[iDep];
		
		// locate corresponding tool descriptor
		// try first in current package
		// if not found, try in another one
		Tool *tool = _package->FindTool(dep._name, dep._version);
		if(!tool)
		{
			for(int iPackage = 0; iPackage < Packages.GetCount(); iPackage++)
			{
				Package &package = Packages[iPackage];
				tool = package.FindTool(dep._name, dep._version);
				if(tool)
					break;
			}
		}
		if(!tool)
		{
			Exclamation(Format(t_("Could not find tool '%s' version '%s'"), dep._name, dep._version));
			
			// remove also installed platform files, as they're useless
			FileTools::DeleteFolderDeep(platformVersion.GetLocalFolder());
			
			return false;
		}
		
		// install it
		if(!tool->Install())
		{
			// remove also installed platform files, as they're useless
			FileTools::DeleteFolderDeep(platformVersion.GetLocalFolder());
			
			return false;
		}
	}

	// ok, platform and needed tools are installed now, remember it
	_installedVersion = version;
	_installed = true;

	// read provided boards
	// boards can't be read from json file -- names are useless (as usual)
	// we shall parse the 'boards.txt' file here
	platformVersion.ParseBoardsFile();
	
	return true;
}

// remove platform files (and dependencies)
bool Platform::Remove(void)
{
	// if not installed, just return
	if(!_installed)
		return true;
	
	// get currently installed version
	PlatformVersion &platformVersion = _versions.Get(_installedVersion);
	
	// remove its folder and sub-folders
	FileTools::DeleteFolderDeep(platformVersion.GetLocalFolder());
	
	// mark it as not installed
	_installed = false;
	_installedVersion = "";
	
	// cleanup unused tools
	Packages.CleanupTools();
	
	return true;
}

// get latest platform version available
String Platform::GetLastVersion(void) const
{
	AVersion last;
	for(int i = 0; i < _versions.GetCount(); i++)
	{
		AVersion cur = _versions.GetKey(i);
		if(cur > last)
			last = cur;
	}
	if(last != AVersion())
		return last.ToString();
	return "";
}

