#include <siaapi.h>
#include <SQLiteCpp/Database.h>
#include <algorithm>
#include <siadriveconfig.h>
#include <filepath.h>

using namespace Sia::Api;
/*{
  // Settings that control the behavior of the renter.
  "settings": {
    // Allowance dictates how much the renter is allowed to spend in a given
    // period. Note that funds are spent on both storage and bandwidth.
    "allowance": {  
      // Amount of money allocated for contracts. Funds are spent on both
      // storage and bandwidth.
      "funds": "1234", // hastings

      // Number of hosts that contracts will be formed with.
      "hosts":24,

      // Duration of contracts formed, in number of blocks.
      "period": 6048, // blocks

      // If the current blockheight + the renew window >= the height the
      // contract is scheduled to end, the contract is renewed automatically.
      // Is always nonzero.
      "renewwindow": 3024 // blocks
    }
  },
  
  // Metrics about how much the Renter has spent on storage, uploads, and
  // downloads.
  "financialmetrics": {
    // How much money, in hastings, the Renter has spent on file contracts,
    // including fees.
    "contractspending": "1234", // hastings

    // Amount of money spent on downloads.
    "downloadspending": "5678", // hastings

    // Amount of money spend on storage.
    "storagespending": "1234", // hastings

    // Amount of money spent on uploads.
    "uploadspending": "5678", // hastings

    // Amount of money in the allowance that has not been spent.
    "unspent": "1234" // hastings
  }
}*/

CSiaApi::_CSiaRenter::_CSiaRenter(CSiaDriveConfig* siaDriveConfig) :
	CSiaBase(siaDriveConfig),
	_Funds(0),
	_Hosts(0),
	_Unspent(0),
	_TotalUsedBytes(0),
	_TotalUploadProgress(100),
  _Period(0),
  _RenewWindow(0),
  _currentAllowance({ 0,0,0,0 })
{
}

CSiaApi::_CSiaRenter::~_CSiaRenter()
{
}

void CSiaApi::_CSiaRenter::Refresh( )
{
  {
    json result;
    SiaCurlError cerror = CSiaCurl(GetSiaDriveConfig().GetHostConfig()).Get(L"/renter/prices", {}, result);
    _storageterabytemonth = ApiSuccess(cerror) ? result["storageterabytemonth"].get<std::string>() : "";
    _downloadterabyte = ApiSuccess(cerror) ? result["downloadterabyte"].get<std::string>() : "";
    _uploadterabyte = ApiSuccess(cerror) ? result["uploadterabyte"].get<std::string>() : "";
  }

  {
    json result;
    if (ApiSuccess(CSiaCurl(GetSiaDriveConfig().GetHostConfig()).Get(L"/renter", result)))
    {
      SiaCurrency funds = HastingsStringToSiaCurrency(result["settings"]["allowance"]["funds"].get<std::string>());
      SiaCurrency unspent = HastingsStringToSiaCurrency(result["financialmetrics"]["unspent"].get<std::string>());
      std::uint64_t hosts = result["settings"]["allowance"]["hosts"].get<std::uint64_t>();
      std::uint64_t period = result["settings"]["allowance"]["period"].get<std::uint64_t>();
      std::uint64_t renewWindow = result["settings"]["allowance"]["renewwindow"].get<std::uint64_t>();
      SetFunds(funds);
      SetHosts(hosts);
      SetUnspent(unspent);
      SetRenewWindow(renewWindow);
      SetPeriod(period);
      _currentAllowance = { funds, hosts, period, renewWindow };

      if (_currentAllowance.Funds == 0)
      {
        _currentAllowance.Funds = SIA_DEFAULT_MINIMUM_FUNDS;
        _currentAllowance.Hosts = SIA_DEFAULT_HOST_COUNT;
        _currentAllowance.Period = SIA_DEFAULT_CONTRACT_LENGTH;
        _currentAllowance.RenewWindowInBlocks = SIA_DEFAULT_RENEW_WINDOW;
      }
      if (ApiSuccess(RefreshFileTree()))
      {
        CSiaFileTreePtr fileTree;
        GetFileTree(fileTree);

        auto fileList = fileTree->GetFileList();
        if (fileList->size())
        {
          std::uint64_t total = std::accumulate(std::next(fileList->begin()), fileList->end(), fileList->at(0)->GetFileSize(), [](const std::uint64_t& sz, const CSiaFilePtr& file)
          {
            return sz + file->GetFileSize();
          });

          std::uint32_t totalProgress = std::accumulate(std::next(fileList->begin()), fileList->end(), fileList->at(0)->GetUploadProgress(), [](const std::uint32_t& progress, const CSiaFilePtr& file)
          {
            return progress + std::min(100u, file->GetUploadProgress());
          }) / static_cast<std::uint32_t>(fileList->size());

          SetTotalUsedBytes(total);
          SetTotalUploadProgress(totalProgress);
        }
        else
        {
          SetTotalUsedBytes(0);
          SetTotalUploadProgress(100);
        }
      }
      else
      {
        SetTotalUsedBytes(0);
        SetTotalUploadProgress(100);
      }
    }
    else
    {
      SetFunds(0);
      SetHosts(0);
      SetUnspent(0);
      SetTotalUsedBytes(0);
      SetTotalUploadProgress(100);
      SetPeriod(0);
      SetRenewWindow(0);
      _currentAllowance = { 0,0,0,0 };
    }
  }
}

SiaApiError CSiaApi::_CSiaRenter::RefreshFileTree( )
{
  SiaApiError ret;
  CSiaFileTreePtr tempTree(new CSiaFileTree(&GetSiaDriveConfig()));
  json result;  
  SiaCurlError cerror = CSiaCurl(GetSiaDriveConfig().GetHostConfig()).Get(L"/renter/files", result);
  if (ApiSuccess(cerror))
  {
    tempTree->BuildTree(result);
     _fileTree = tempTree;
  }
  else
  {
    ret = { SiaApiErrorCode::RequestError, cerror.GetReason() };
  }

  return ret;
}

SiaApiError CSiaApi::_CSiaRenter::RenameFolder(const SString& siaPath, const SString& newSiaPath, std::unordered_map<SString, SString>* renamedMap)
{
  CSiaFileTreePtr fileTree;
  auto ret = GetFileTree(fileTree);
  if (ApiSuccess(ret))
  {
    auto list = fileTree->GetFileList();
    for (auto file : *list)
    {
      if (file->GetSiaPath().BeginsWith(siaPath + '/'))
      {
        SString tempNewSiaPath = FormatToSiaPath(FilePath(newSiaPath, file->GetSiaPath().SubString(siaPath.Length())));
        ret = RenameFile(file->GetSiaPath(), tempNewSiaPath);
        if (renamedMap && ApiSuccess(ret))
        {
          renamedMap->insert({file->GetSiaPath(), tempNewSiaPath });
        }
      }

      if (!ApiSuccess(ret))
        break;
    }
  }

  return ret;
}

SiaApiError CSiaApi::_CSiaRenter::RenameFile(const SString& siaPath, const SString& newSiaPath)
{
  SiaApiError ret;
  json result;
  SiaCurlError cerror = CSiaCurl(GetSiaDriveConfig().GetHostConfig()).Post(L"/renter/rename/" + siaPath, { { L"newsiapath", newSiaPath } }, result);
  if (!ApiSuccess(cerror))
  {
    ret = { SiaApiErrorCode::RequestError, cerror.GetReason() };
  }

  RefreshFileTree();

  return ret;
}

SiaApiError CSiaApi::_CSiaRenter::FileExists(const SString& siaPath, bool& exists) const
{
	CSiaFileTreePtr siaFileTree;
	SiaApiError ret = GetFileTree(siaFileTree);
	if (ApiSuccess(ret))
	{
		exists = siaFileTree->FileExists(siaPath);
	}

	return ret;
}

SiaApiError CSiaApi::_CSiaRenter::DownloadFile(const SString& siaPath, const SString& location) const
{
  SiaApiError ret;
	json result;
  auto cerror = CSiaCurl(GetSiaDriveConfig().GetHostConfig()).Get(L"/renter/download/" + siaPath, { { L"destination", location } }, result);
	if (!ApiSuccess(cerror))
	{
    ret = { SiaApiErrorCode::RequestError, cerror.GetReason() };
	}

	return ret;
}

SiaApiError CSiaApi::_CSiaRenter::GetFileTree(CSiaFileTreePtr& siaFileTree) const
{
  siaFileTree = _fileTree;
  if (!siaFileTree)
  {
    siaFileTree.reset(new CSiaFileTree(&GetSiaDriveConfig()));
  }

	return SiaApiErrorCode::Success;
}

SiaRenterAllowance CSiaApi::_CSiaRenter::GetAllowance() const
{
  return _currentAllowance;
}

SiaApiError CSiaApi::_CSiaRenter::SetAllowance(const SiaRenterAllowance& renterAllowance)
{
  SiaApiError ret;

  json result;
  auto cerror = CSiaCurl(GetSiaDriveConfig().GetHostConfig()).Post(L"/renter",
  { 
    {       "funds", SiaCurrencyToHastingsString(renterAllowance.Funds) },
    {       "hosts", SString::FromUInt64(renterAllowance.Hosts) },
    {      "period", SString::FromUInt64(renterAllowance.Period) },
    { "renewwindow", SString::FromUInt64 (renterAllowance.RenewWindowInBlocks) }
  }, result);
  if (!ApiSuccess(cerror))
  {
    ret = { SiaApiErrorCode::RequestError, cerror.GetReason() };
  }

  return ret;
}

SiaApiError CSiaApi::_CSiaRenter::CalculateEstimatedStorage(const SiaCurrency& funds, SiaCurrency& resultInBytes) const
{
  SiaApiError ret;
  if (_storageterabytemonth > 0 && funds > 0)
  {
    Hastings fundsHastings = SiaCurrencyToHastings(funds);
    ttmath::Parser<SiaCurrency> parser;
    parser.Parse((fundsHastings.ToWString() + " / " + _storageterabytemonth.ToWString() + " * 1e12").str());
    resultInBytes = parser.stack[0].value;
  }
  else
  {
    resultInBytes = 0;
  }

  return ret;
}

SiaApiError CSiaApi::_CSiaRenter::CalculateEstimatedStorageCost(SiaCurrency& result) const
{
  SiaApiError ret;
  if (_storageterabytemonth > 0)
  {
    ttmath::Parser<SiaCurrency> parser;
    parser.Parse(("(" + _storageterabytemonth.ToWString() + SString(" / 1000) / (10 ^ 24)")).str());
    result = parser.stack[0].value;
  }
  else
  {
    result = 0;
  }

  return ret;
}

SiaApiError CSiaApi::_CSiaRenter::CalculateEstimatedDownloadCost(SiaCurrency& result) const
{
  SiaApiError ret;
  if (_downloadterabyte > 0)
  {
    ttmath::Parser<SiaCurrency> parser;
    parser.Parse(("(" + _downloadterabyte.ToWString() + SString(" / 1000) / (10 ^ 24)")).str());
    result = parser.stack[0].value;
  }
  else
  {
    result = 0;
  }

  return ret;
}

SiaApiError CSiaApi::_CSiaRenter::CalculateEstimatedUploadCost(SiaCurrency& result) const
{
  SiaApiError ret;
  if (_uploadterabyte > 0)
  {
    ttmath::Parser<SiaCurrency> parser;
    parser.Parse(("(" + _uploadterabyte.ToWString() + SString(" / 1000) / (10 ^ 24)")).str());
    result = parser.stack[0].value;
  }
  else
  {
    result = 0;
  }

  return ret;
}