#include "Tool.h"
#include "Package.h"

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


#include <Archive/Archive.h>

// constructor
Tool::Tool(Package *package)
{
	_package = package;
	
	_installed = false;
}

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

// parse tool data contained in a Value object
bool Tool::ParseJSON(Value const &json)
{
	_name			= json["name"];
	_version		= json["version"];
	
	// read provided systems
	Value systemsData = json["systems"];
	for(int isystem = 0; isystem < systemsData.GetCount(); isystem++)
	{
		Value systemData 		= systemsData[isystem];
		ToolSystem &system 		= _systems.Add();
		system._checksum		= systemData["checksum"];
		system._host			= systemData["host"];
		system._archiveName		= systemData["archiveFileName"];
		system._url				= systemData["url"];

		// size is sometimes given as an integer (without quotes)
		// and sometimes as a string (quoted)
		Value size		= systemData["size"];
		if(size.Is<String>())
			system._size = ScanInt(size.ToString());
		else
			system._size = (int)size;
	}
	
	return true;
}

// get current OS system data, NULL if none
Tool::ToolSystem const *Tool::GetSystem(void) const
{
	Array<String> myPlatform;
	#if defined(PLATFORM_WIN32)
		myPlatform << "i686-mingw32";
	#elif defined(PLATFORM_OSX11)
		#if defined(CPU_IA64)
			myPlatform << "x86_64-apple-darwin" << "i386-apple-darwin" << "i386-apple-darwin11";
		#elif defined(CPU_IA32)
			myPlatform << "i386-apple-darwin" << "i386-apple-darwin11";
		#else
			#error "Unsupported platform"
		#endif
	#elif defined(PLATFORM_LINUX)
		#if defined(CPU_IA64)
			myPlatform << "x86_64-linux-gnu" << "x86_64-pc-linux-gnu" ;
		#elif defined(CPU_IA32)
			myPlatform << "i686-linux-gnu" << "i686-pc-linux-gnu";
		#elif defined(__arm__)
			myPlatform = "i686-arm-gnu" << "i686-arm-gnueabihf";
		#else
			#error "Unsupported platform"
		#endif
	#else
		#error "Unsupported platform"
	#endif
	
	for(int iSystem = 0; iSystem < _systems.GetCount(); iSystem++)
		for(int iPlat = 0; iPlat < myPlatform.GetCount(); iPlat++)
			if(_systems[iSystem]._host == myPlatform[iPlat])
				return &_systems[iSystem];

	return NULL;
}

// get local root folder
String Tool::GetLocalFolder(void) const
{
	ToolSystem const *sys = GetSystem();
	if(!sys)
		return "";

	String s = AppendFileName(Settings.GetIdeRoot(), "packages");
	s = AppendFileName(s, _package->GetName());
	s = AppendFileName(s, "tools");
	s = AppendFileName(s, _name);
	s = AppendFileName(s, _version);
	return s;
}

// get local staging archive
String Tool::GetStagingArchivePath(void) const
{
	ToolSystem const *sys = GetSystem();
	if(!sys)
		return "";
	
	String s = AppendFileName(Settings.GetIdeRoot(), "staging");
	s = AppendFileName(s, "tools");
	s = AppendFileName(s, sys->_archiveName);
	return s;
}

// check if is installed
bool Tool::CheckInstall(void)
{
	_installed = DirectoryExists(GetLocalFolder());
	return _installed;
}

// install (or reinstall) tool files
bool Tool::Install(void)
{
	// do nothing if already installed
	if(_installed)
		return true;
	
	ToolSystem const *sys = GetSystem();
	if(!sys)
	{
		Exclamation(Format(t_("Could not find host files for tool '%s' version '%s'"), _name, _version));
		return false;
	}
	
	// if already installed, we want to reinstall it
	// so wipe it first
	if(CheckInstall())
		FileTools::DeleteFolderDeep(GetLocalFolder());
	
	// check if staging file is there
	String staging = 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 = sys->_size / 10 + 20000;
		String prompt = Format(t_("Downloading tool '%s'"), _name);
		String packed = NetTools::Download(prompt, sys->_url, timeout);
		if(packed.IsEmpty())
		{
			Exclamation(Format(t_("Error downloading tool '%s'"), GetFileName(staging)));
			return false;
		}
		RealizeDirectory(GetFileFolder(staging));
		SaveFile(staging, packed);
	}

	// install the archive
	_installed = FileTools::UnpackArduinoArchive(Format(t_("Installing tool '%s'"), GetFileName(staging)), staging, GetLocalFolder());
	return _installed;
}

// remove tool files
bool Tool::Remove(void)
{
	// if not installed, just return
	if(!_installed)
		return true;
	
	// remove its folder and sub-folders
	FileTools::DeleteFolderDeep(GetLocalFolder());
	
	// mark it as not installed
	_installed = false;
	
	return true;
}

// get variables needed for build/upload
// runtime.tools.xtensa-lx106-elf-gcc.path
VectorMap<String, String> Tool::GetVariables(void) const
{
	VectorMap<String, String> res;
	res.Add("runtime.tools." + _name + ".path", GetLocalFolder());
	return res;
}
