#include <siadriveapp.h>
#include <include/views/cef_browser_view.h>
#include <include/views/cef_window.h>
#include <include/wrapper/cef_helpers.h>
#include <include/wrapper/cef_closure_task.h>
#include <autothread.h>
#include <siaapi.h>
#include <siacurl.h>
#include <siadriveconfig.h>
#include <siadokandrive.h>
#include <eventsystem.h>
#include <siadrivehandler.h>
#include <threadpool.h>
#include <algorithm>

using namespace Sia;
using namespace Sia::Api;

// TODO Change this
static ThreadPool<10> _threadPool;

class FunctionHandler : 
  public CefV8Handler 
{
public:
  FunctionHandler(const CSiaApi& siaApi, 
                  bool& appStarted, 
                  std::shared_ptr<CSiaDriveConfig>& siaDriveConfig, 
                  std::unique_ptr<Api::Dokan::CSiaDokanDrive>& siaDrive, 
                  std::function<void(CefRefPtr<CefV8Context> context)> refreshCallback,
                  bool& formingContracts) :
    _siaApi(siaApi),
    _siaDriveConfig(siaDriveConfig.get()),
    _siaDrive(siaDrive),
    _appStarted(appStarted),
    _refreshCallback(refreshCallback),
    _formingContracts(formingContracts)
  { 
  }

public:
  ~FunctionHandler()
  {
  }

private:
  const CSiaApi& _siaApi;
  CSiaDriveConfig* _siaDriveConfig;
  std::unique_ptr<Api::Dokan::CSiaDokanDrive>& _siaDrive;
  bool& _appStarted;
  std::function<void(CefRefPtr<CefV8Context> context)> _refreshCallback;
  bool& _formingContracts;

private:
  void RenterSettingsCallback(CefRefPtr<CefV8Context> context, CefRefPtr<CefV8Value> cb, SiaApiError ret) const
  {
    _formingContracts = false;
    auto msg = CefProcessMessage::Create("setFormingContracts");
    msg->GetArgumentList()->SetBool(0, false);
    context->GetBrowser()->SendProcessMessage(PID_BROWSER, msg);

    CefV8ValueList args;
    args.push_back(CefV8Value::CreateBool(ApiSuccess(ret)));
    args.push_back(CefV8Value::CreateString(ret.GetReason().str()));
    cb->ExecuteFunctionWithContext(context, nullptr, args);
  }

  void MountCallback(CefRefPtr<CefV8Context> context, CefRefPtr<CefV8Value> cb) const
  {
    CefV8ValueList args;
    args.push_back(CefV8Value::CreateBool(true));
    if (!args[0]->GetBoolValue())
    {
      args.push_back(CefV8Value::CreateString("Failed to mount"));
    }
    cb->ExecuteFunctionWithContext(context, nullptr, args);
  }

  void UnmountCallback(CefRefPtr<CefV8Context> context, CefRefPtr<CefV8Value> cb) const
  {
    CefV8ValueList args;
    args.push_back(CefV8Value::CreateBool(true));
    if (!args[0]->GetBoolValue())
    {
      args.push_back(CefV8Value::CreateString("Failed to unmount"));
    }
    cb->ExecuteFunctionWithContext(context, nullptr, args);
  }

  void UnlockCallback(SiaApiError error, CefRefPtr<CefV8Context> context, CefRefPtr<CefV8Value> cb, const SString& password) const
  {
    CefV8ValueList args;
    args.push_back(CefV8Value::CreateBool(ApiSuccess(error)));
    if (!args[0]->GetBoolValue())
    {
      args.push_back(CefV8Value::CreateString("Failed to unlock wallet"));
    }
    _refreshCallback(context);
    cb->ExecuteFunctionWithContext(context, nullptr, args);
  }
  
  void CreateWalletCallback(CefRefPtr<CefV8Context> context, CefRefPtr<CefV8Value> password, CefRefPtr<CefV8Value> cb) const
  {
    SString customPassword;
    if (!password->IsNull() && password->IsString() && !password->GetStringValue().empty())
    {
      customPassword = password->GetStringValue().ToWString();
    }

    SString seed;
    SiaApiError error = _siaApi.GetWallet()->Create(customPassword, SiaSeedLanguage::English, seed);

    CefV8ValueList args;
    args.push_back(CefV8Value::CreateBool(ApiSuccess(error)));
    if (args[0]->GetBoolValue())
    {
      args.push_back(CefV8Value::CreateString(seed.str()));
    }
    else
    {
      args.push_back(CefV8Value::CreateString("Failed to create new wallet"));
    }
    cb->ExecuteFunctionWithContext(context, nullptr, args);
  }

  void DriveMountEndedCallback(CefRefPtr<CefV8Context> context)
  {
    context->Enter();
    if (_appStarted)
    {
      auto global = context->GetGlobal();
      auto uiUpdate = global->GetValue("uiUpdate");
      auto notifyDriveUnmounted = uiUpdate->GetValue("notifyDriveUnmounted");;

      CefV8ValueList args;
      notifyDriveUnmounted->ExecuteFunction(nullptr, args);
    }
    context->Exit();
  }

public:
  virtual bool Execute(const CefString& name,
    CefRefPtr<CefV8Value> object,
    const CefV8ValueList& arguments,
    CefRefPtr<CefV8Value>& retval,
    CefString& exception) OVERRIDE 
  {
    CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
    if (name == "autoUnlockWallet")
    {
      SString stored = GetRegistry("Unlock", "", true);
      retval = CefV8Value::CreateBool(!stored.IsNullOrEmpty());
      if (!stored.IsNullOrEmpty())
      {
        SString password = SecureDecryptString("Unlock", stored);
        CefRefPtr<CefV8Value> cb = arguments[0];

        // Don't hang UI while unlocking
        _threadPool.AddJobW([this, context, password, cb]()
        {
          auto ret = _siaApi.GetWallet()->Unlock(password);
          if (ApiSuccess(ret))
          {
            while (_appStarted && _siaApi.GetWallet()->GetLocked())
            {
              ::Sleep(10);
            }
          }
          CefPostTask(TID_RENDERER, base::Bind(&FunctionHandler::UnlockCallback, this, ret, context, cb, password));
        });
      }
      return true;
    }
    else if (name == "walletSend")
    {
      SString address = arguments[0]->GetStringValue().ToWString();
      SString amount = arguments[1]->GetStringValue().ToWString();
      CefRefPtr<CefV8Value> cb = arguments[2];

      SiaCurrency currency(amount.str());
      auto ret = _siaApi.GetWallet()->Send(address, currency); 
      
      CefV8ValueList args;
      args.push_back(CefV8Value::CreateBool(ApiSuccess(ret)));
      args.push_back(CefV8Value::CreateString(ret.GetReason().str()));
      cb->ExecuteFunctionWithContext(context, nullptr, args);

      return true;
    }
    else if (name == "unlockWallet") 
    {
      retval = CefV8Value::CreateBool(true);
      SString password = arguments[0]->GetStringValue().ToWString();
      CefRefPtr<CefV8Value> cb = arguments[1];

      // Don't hang UI while unlocking
      _threadPool.AddJobW([this, context, password, cb]()
      {
        auto ret = _siaApi.GetWallet()->Unlock(password);
        if (ApiSuccess(ret))
        {
          while (_appStarted && _siaApi.GetWallet()->GetLocked())
          {
            ::Sleep(10);
          }

          if (_siaDriveConfig->GetStoreUnlockPassword())
          {
            SString encrypted = SecureEncryptString("Unlock", password);
            SetRegistry("Unlock", encrypted);
          }
        }
        CefPostTask(TID_RENDERER, base::Bind(&FunctionHandler::UnlockCallback, this, ret, context, cb, password));
      });

      return true;
    }
    else if (name == "autoMountDrive")
    {
      retval = CefV8Value::CreateBool(true);
      if (_siaDriveConfig->GetAutoMountOnUnlock())
      {
        const auto lastMountLocation = _siaDriveConfig->GetLastMountLocation();
        if (!lastMountLocation.empty())
        {
          CEventSystem::EventSystem.AddOneShotEventConsumer([this, context](const CEvent& event) -> bool
          {
            if (event.GetEventName() == "DriveMountEnded")
            {
              CefPostTask(TID_RENDERER, base::Bind(&FunctionHandler::DriveMountEndedCallback, this, context));
              return true;
            }
            return false;
          });

          auto global = context->GetGlobal();
          auto uiUpdate = global->GetValue("uiUpdate");
          auto notifyDriveMounting = uiUpdate->GetValue("notifyDriveMounting");;

          CefV8ValueList args2;
          args2.push_back(CefV8Value::CreateString(lastMountLocation));
          notifyDriveMounting->ExecuteFunction(nullptr, args2);
          _threadPool.AddJobW([this, lastMountLocation, context]()
          {
            _siaDrive->Mount(lastMountLocation[0], _siaDriveConfig->GetCacheFolder(), 0);
          });
        }
      }

      return true;
    }
    else if (name == "createWallet")
    {
      retval = CefV8Value::CreateBool(true);
      auto password = arguments[0];
      auto cb = arguments[1];

      CefPostTask(TID_RENDERER, base::Bind(&FunctionHandler::CreateWalletCallback, this, context, password, cb));
      return true;
    }
    else if (name == "mountDrive")
    {
      CEventSystem::EventSystem.AddOneShotEventConsumer([this, context](const CEvent& event) -> bool
      {
        if (event.GetEventName() == "DriveMountEnded")
        {
          CefPostTask(TID_RENDERER, base::Bind(&FunctionHandler::DriveMountEndedCallback, this, context));
          return true;
        }
        return false;
      });

      retval = CefV8Value::CreateBool(true);
      SString drive = arguments[0]->GetStringValue().ToWString();
      CefRefPtr<CefV8Value> cb = arguments[1];
      _threadPool.AddJobW([this, drive, context, cb]()
      {
        _siaDrive->Mount(drive[0], _siaDriveConfig->GetCacheFolder(), 0);
        CefPostTask(TID_RENDERER, base::Bind(&FunctionHandler::MountCallback, this, context, cb));
      });

      return true;
    }
    else if (name == "unmountDrive")
    {
      retval = CefV8Value::CreateBool(true);
      CefRefPtr<CefV8Value> cb = arguments[0];
      _threadPool.AddJobW([this, context, cb]()
      {
        _siaDrive->Unmount();
        CefPostTask(TID_RENDERER, base::Bind(&FunctionHandler::UnmountCallback, this, context, cb));
      });

      return true;
    }
    else if (name == "setRenterSettings")
    {
      CefRefPtr<CefV8Value> vAllowance = arguments[0];
      CefRefPtr<CefV8Value> cb = arguments[1];
      SiaRenterAllowance allowance({ 
        SiaCurrency(vAllowance->GetValue("Funds")->GetStringValue().ToWString()), 
        SString::ToUInt32(vAllowance->GetValue("Hosts")->GetStringValue().ToWString()), 
        SString::ToUInt32(vAllowance->GetValue("Period")->GetStringValue().ToWString()), 
        SString::ToUInt32(vAllowance->GetValue("RenewWindowInBlocks")->GetStringValue().ToWString())
      });
      _formingContracts = true;

      auto msg = CefProcessMessage::Create("setFormingContracts");
      msg->GetArgumentList()->SetBool(0, true);
      context->GetBrowser()->SendProcessMessage(PID_BROWSER, msg);

      _threadPool.AddJobW([this, allowance, context, cb]()
      {
        auto ret = _siaApi.GetRenter()->SetAllowance(allowance);
        CefPostTask(TID_RENDERER, base::Bind(&FunctionHandler::RenterSettingsCallback, this, context, cb, ret));
      });

      return true;
    }
    else if (name == "setSiaSettings")
    {
      auto global = context->GetGlobal();
      auto uiState = global->GetValue("uiState");

      CefRefPtr<CefV8Value> settings = arguments[0];
      CefRefPtr<CefV8Value> cb = arguments[1];
      const bool storePwd = _siaDriveConfig->GetStoreUnlockPassword();
      _siaDriveConfig->SetLaunchFileMgrOnMount(settings->GetValue("LaunchFileMgrOnMount")->GetBoolValue());
      _siaDriveConfig->SetAutoStartOnLogon(settings->GetValue("AutoStartOnLogon")->GetBoolValue());
      _siaDriveConfig->SetLaunchBundledSiad(settings->GetValue("LaunchBundledSiad")->GetBoolValue());
      _siaDriveConfig->SetLockWalletOnExit(settings->GetValue("LockWalletOnExit")->GetBoolValue());
      _siaDriveConfig->SetApiPort(SString::ToUInt32(settings->GetValue("ApiPort")->GetStringValue().ToWString()));
      _siaDriveConfig->SetHostPort(SString::ToUInt32(settings->GetValue("HostPort")->GetStringValue().ToWString()));
      _siaDriveConfig->SetRpcPort(SString::ToUInt32(settings->GetValue("RpcPort")->GetStringValue().ToWString()));
      _siaDriveConfig->SetCloseToTray(settings->GetValue("CloseToTray")->GetBoolValue());
      _siaDriveConfig->SetStoreUnlockPassword(settings->GetValue("StoreUnlockPassword")->GetBoolValue());
      _siaDriveConfig->SetAutoMountOnUnlock(settings->GetValue("AutoMountOnUnlock")->GetBoolValue());
      _siaDriveConfig->SetConfirmApplicationExit(settings->GetValue("ConfirmApplicationExit")->GetBoolValue());
      if (!storePwd && _siaDriveConfig->GetStoreUnlockPassword())
      {
        // Lock to prompt for password
        _siaApi.GetWallet()->Lock(); 
        while (_appStarted && !_siaApi.GetWallet()->GetLocked())
        {
          ::Sleep(10);
        }
        uiState->SetValue("isWalletLocked", CefV8Value::CreateBool(true), V8_PROPERTY_ATTRIBUTE_NONE);
      }
      else if (!_siaDriveConfig->GetStoreUnlockPassword())
      {
        SetRegistry("Unlock", "");
      }

      uiState->SetValue("storeUnlockPassword", CefV8Value::CreateBool(_siaDriveConfig->GetStoreUnlockPassword()), V8_PROPERTY_ATTRIBUTE_READONLY);

      CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("settingsUpdated");
      msg->GetArgumentList()->SetString(0, _siaDriveConfig->ToString().str());
      context->GetBrowser()->SendProcessMessage(PID_BROWSER, msg);

      CefV8ValueList args;
      args.push_back(CefV8Value::CreateBool(true));
      cb->ExecuteFunctionWithContext(context, nullptr, args);

      return true;
    }
    else if (name == "calculateEstimatedStorage")
    {
      CefRefPtr<CefV8Value> funds = arguments[0];
      CefRefPtr<CefV8Value> cb = arguments[1];

      SiaCurrency resultInBytes;
      _siaApi.GetRenter()->CalculateEstimatedStorage(funds->GetStringValue().ToWString(), resultInBytes);

      CefV8ValueList args;
      args.push_back(CefV8Value::CreateString(BytesToFriendlyDisplay(resultInBytes).str()));
      cb->ExecuteFunctionWithContext(context, nullptr, args);

      return true;
    }
    // Function does not exist.
    return false;
  }

private:
  IMPLEMENT_REFCOUNTING(FunctionHandler);
};

CSiaDriveApp::CSiaDriveApp()
{
}

CSiaDriveApp::~CSiaDriveApp()
{
}

void CSiaDriveApp::ExecuteSetter(CefRefPtr<CefV8Context> context, CefRefPtr<CefV8Value> obj, const SString& method, const SString& value)
{
  ExecuteSetter(context, obj, method, CefV8Value::CreateString(value.str()));
}

void CSiaDriveApp::ExecuteSetter(CefRefPtr<CefV8Context> context, CefRefPtr<CefV8Value> obj, const SString& method, CefRefPtr<CefV8Value> value)
{
  CefRefPtr<CefV8Value> setConfirmed = obj->GetValue(method.str());
  CefV8ValueList args;
  args.push_back(value);
  setConfirmed->ExecuteFunctionWithContext(context, nullptr, args);
}

bool CSiaDriveApp::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
{
  CEF_REQUIRE_RENDERER_THREAD();
  bool ret = false;

  const std::string& messageName = message->GetName();
  if (messageName == "handleCloseRequest")
  {
    if (_appStarted)
    {
      bool close = message->GetArgumentList()->GetValue(0)->GetBool();
      if (!close)
      {
        if (_siaDriveConfig->GetCloseToTray())
        {
          CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("closeToTray");
          browser->SendProcessMessage(PID_BROWSER, msg);
        }
      }

      if (close && !_formingContracts)
      {
        if (_siaDriveConfig->GetConfirmApplicationExit())
        {
          auto context = browser->GetMainFrame()->GetV8Context();
          context->Enter();
          CefRefPtr<CefV8Value> global = context->GetGlobal();
          auto uiActions = global->GetValue("uiUpdate");
          CefRefPtr<CefV8Value> displayPrompt = uiActions->GetValue("displayPrompt");
          CefV8ValueList args;
          args.push_back(CefV8Value::CreateString("Leaving so soon?"));
          close = displayPrompt->ExecuteFunctionWithContext(context, nullptr, args)->GetBoolValue();
          context->Exit();
        }

        if (close)
        {
          this->ShutdownServices(browser);
        }
      }
    }
    
    ret = true;
  }

  return ret;
}

void CSiaDriveApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context)
{
  CEF_REQUIRE_RENDERER_THREAD();
  _appStarted = true;

  _siaDriveConfig.reset(new CSiaDriveConfig(true));
  _siaApi.reset(new CSiaApi(_siaDriveConfig.get()));
  _siaApi->StartBackgroundRefresh();

  CefRefPtr<CefV8Value> global = context->GetGlobal();

  CefRefPtr<FunctionHandler> handler(new FunctionHandler(*_siaApi, _appStarted, _siaDriveConfig, _siaDrive, [this](CefRefPtr<CefV8Context> context) {this->SiaApiRefreshCallback(context); }, _formingContracts));
  
  CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(nullptr, nullptr);
  obj->SetValue("clientVersion", CefV8Value::CreateString(SIADRIVE_VERSION_STRING), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("isOnline", CefV8Value::CreateBool(_siaApi->GetWallet()->GetConnected()), V8_PROPERTY_ATTRIBUTE_NONE); 
  CefRefPtr<CefV8Value> defaultFunds = CefV8Value::CreateObject(nullptr, nullptr);
  defaultFunds->SetValue("Funds", CefV8Value::CreateString(SIA_DEFAULT_MINIMUM_FUNDS.ToString()), V8_PROPERTY_ATTRIBUTE_NONE);
  defaultFunds->SetValue("Hosts", CefV8Value::CreateString(SString::FromUInt32(SIA_DEFAULT_HOST_COUNT).str()), V8_PROPERTY_ATTRIBUTE_NONE);
  defaultFunds->SetValue("Period", CefV8Value::CreateString(SString::FromUInt32(SIA_DEFAULT_CONTRACT_LENGTH).str()), V8_PROPERTY_ATTRIBUTE_NONE);
  defaultFunds->SetValue("RenewWindowInBlocks", CefV8Value::CreateString(SString::FromUInt32(SIA_DEFAULT_RENEW_WINDOW).str()), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("defaultRenterSettings", defaultFunds, V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("storeUnlockPassword", CefV8Value::CreateBool(_siaDriveConfig->GetStoreUnlockPassword()), V8_PROPERTY_ATTRIBUTE_READONLY);
  global->SetValue("uiState", obj, V8_PROPERTY_ATTRIBUTE_NONE);

  obj = CefV8Value::CreateObject(nullptr, nullptr);
  obj->SetValue("walletSend", CefV8Value::CreateFunction("walletSend", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("autoMountDrive", CefV8Value::CreateFunction("autoMountDrive", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("autoUnlockWallet", CefV8Value::CreateFunction("autoUnlockWallet", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("unlockWallet", CefV8Value::CreateFunction("unlockWallet", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("createWallet", CefV8Value::CreateFunction("createWallet", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("startApp", CefV8Value::CreateFunction("startApp", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("stopApp", CefV8Value::CreateFunction("stopApp", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("mountDrive", CefV8Value::CreateFunction("mountDrive", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("unmountDrive", CefV8Value::CreateFunction("unmountDrive", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("shutdown", CefV8Value::CreateFunction("shutdown", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("setRenterSettings", CefV8Value::CreateFunction("setRenterSettings", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("setSiaSettings", CefV8Value::CreateFunction("setSiaSettings", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  obj->SetValue("calculateEstimatedStorage", CefV8Value::CreateFunction("calculateEstimatedStorage", handler), V8_PROPERTY_ATTRIBUTE_NONE);
  global->SetValue("appActions", obj, V8_PROPERTY_ATTRIBUTE_NONE);

  _refreshThread.reset(new CAutoThread(_siaDriveConfig.get(), [this, context](CSiaDriveConfig* siaDriveConfig)
  {
    this->SiaApiRefreshCallback(context);
  }));
  _refreshThread->StartAutoThread();

  CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("notifyAllowClose");
  context->GetBrowser()->SendProcessMessage(PID_BROWSER, msg);
}

void CSiaDriveApp::OnContextInitialized() 
{
	CEF_REQUIRE_UI_THREAD();

	CefWindowInfo windowInfo;
#ifdef _WIN32
	windowInfo.SetAsPopup(nullptr, "SiaDrive");
#endif
  windowInfo.width = 800;
  windowInfo.height = 690;

  CefRefPtr<CSiaDriveHandler> _handler(new CSiaDriveHandler());
	SString url = "file:///./htdocs/index.html";
	CefBrowserSettings browserSettings;
	CefBrowserHost::CreateBrowser(windowInfo, _handler, url.str(), browserSettings, nullptr);
}

void CSiaDriveApp::ShutdownServices(CefRefPtr<CefBrowser> browser)
{
  if (_appStarted)
  {
    _appStarted = false;
    {
      auto context = browser->GetMainFrame()->GetV8Context();
      context->Enter();
      auto global = context->GetGlobal();
      auto uiActions = global->GetValue("uiUpdate");
      
      CefRefPtr<CefV8Value> displayShutdownWindow = uiActions->GetValue("displayShutdownWindow");
      CefV8ValueList args;
      displayShutdownWindow->ExecuteFunctionWithContext(context, nullptr, args);
      context->Exit();
    }

    std::thread([this, browser]() {
      _threadPool.JoinAll();

      if (_refreshThread)
      {
        _refreshThread->StopAutoThread();
      }

      if (_siaDrive)
      {
        _siaDrive->Unmount();
        _siaDrive->Shutdown();
      }

      if (_siaApi)
      {
        _siaApi->StopBackgroundRefresh();
      }

      if (_siaDriveConfig && _siaDriveConfig->GetLockWalletOnExit())
      {
        _siaApi->GetWallet()->Lock();
      }

      CefPostTask(TID_RENDERER, base::Bind(&CSiaDriveApp::ReleaseObjects, this, browser));
    }).detach();
  }
}

void CSiaDriveApp::ReleaseObjects(CefRefPtr<CefBrowser> browser)
{
  _refreshThread.reset(nullptr);
  _siaDrive.reset(nullptr);
  _siaApi.reset(nullptr);
  _siaDriveConfig.reset();

  CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("shutdownServicesComplete");
  browser->SendProcessMessage(PID_BROWSER, msg);
}

void CSiaDriveApp::SiaApiRefreshCallback(CefRefPtr<CefV8Context> context)
{
  if (CefCurrentlyOn(TID_RENDERER))
  {
    context->Enter();
    auto global = context->GetGlobal();
    auto uiState = global->GetValue("uiState");
    bool wasOnline = uiState->GetValue("isOnline")->GetBoolValue();
    bool isOnline = _siaApi->GetWallet()->GetConnected();
    uiState->SetValue("isOnline", CefV8Value::CreateBool(isOnline), V8_PROPERTY_ATTRIBUTE_NONE);
    
    if (isOnline)
    {
      auto uiActions = global->GetValue("uiUpdate");
      auto renterActions = uiActions->GetValue("Renter");
      auto walletActions = uiActions->GetValue("Wallet");
      bool locked = _siaApi->GetWallet()->GetLocked();
      bool created = _siaApi->GetWallet()->GetCreated();
      
      // Update UI state, server version and settings
      uiState->SetValue("isWalletLocked", CefV8Value::CreateBool(locked), V8_PROPERTY_ATTRIBUTE_NONE);
      uiState->SetValue("isWalletConfigured", CefV8Value::CreateBool(created), V8_PROPERTY_ATTRIBUTE_NONE);
      if (created && !locked)
      {
        uiState->SetValue("allocatedRenterFunds", CefV8Value::CreateString(_siaApi->GetRenter()->GetFunds().ToString()), V8_PROPERTY_ATTRIBUTE_NONE);

        json settings = {
          { "AutoMountOnUnlock", _siaDriveConfig->GetAutoMountOnUnlock() },
          { "AutoStartOnLogon", _siaDriveConfig->GetAutoStartOnLogon() },
          { "CloseToTray", _siaDriveConfig->GetCloseToTray() },
          { "ConfirmApplicationExit", _siaDriveConfig->GetConfirmApplicationExit() },
          { "LaunchFileMgrOnMount", _siaDriveConfig->GetLaunchFileMgrOnMount() },
          { "LockWalletOnExit", _siaDriveConfig->GetLockWalletOnExit() },
          { "StoreUnlockPassword", _siaDriveConfig->GetStoreUnlockPassword() },
          { "LaunchBundledSiad", _siaDriveConfig->GetLaunchBundledSiad() },
          { "ApiPort", _siaDriveConfig->GetApiPort() },
          { "HostPort", _siaDriveConfig->GetHostPort() },
          { "RpcPort", _siaDriveConfig->GetRpcPort() }
        };
        ExecuteSetter(context, uiActions, "setSiaSettings", CefV8Value::CreateString(settings.dump()));

        // Display wallet data
        auto confirmedBalance = _siaApi->GetWallet()->GetConfirmedBalance();
        auto unconfirmedBalance = _siaApi->GetWallet()->GetUnconfirmedBalance();
        auto totalBalance = confirmedBalance + unconfirmedBalance;
        ExecuteSetter(context, walletActions, "setConfirmedBalance", SiaCurrencyToString(confirmedBalance));
        ExecuteSetter(context, walletActions, "setUnconfirmedBalance", SiaCurrencyToString(unconfirmedBalance));
        ExecuteSetter(context, walletActions, "setTotalBalance", SiaCurrencyToString(totalBalance));
        ExecuteSetter(context, walletActions, "setLockWalletOnExit", CefV8Value::CreateBool(_siaDriveConfig->GetLockWalletOnExit()));
        if (_walletReceiveAddress.IsNullOrEmpty())
        {
          _walletReceiveAddress = _siaApi->GetWallet()->GetReceiveAddress();
          ExecuteSetter(context, walletActions, "setReceiveAddress", _walletReceiveAddress);
        }

        // Funding
        SiaCurrency allocatedFunds = _siaApi->GetRenter()->GetFunds();
        SiaCurrency unspentFunds = _siaApi->GetRenter()->GetUnspent();
        ExecuteSetter(context, renterActions, "setAllocatedFunds", SiaCurrencyToString(allocatedFunds));
        ExecuteSetter(context, renterActions, "setUsedFunds", SiaCurrencyToString(allocatedFunds - unspentFunds));
        ExecuteSetter(context, renterActions, "setAvailableFunds", SiaCurrencyToString(unspentFunds));
        ExecuteSetter(context, renterActions, "setHostCount", SString::FromUInt64(_siaApi->GetRenter()->GetHosts()));
        global->GetValue("uiState")->SetValue("allocatedRenterFunds", CefV8Value::CreateString(SiaCurrencyToString(allocatedFunds).str()), V8_PROPERTY_ATTRIBUTE_NONE);

        // Space
        SiaCurrency totalUsed = _siaApi->GetRenter()->GetTotalUsedBytes() ? _siaApi->GetRenter()->GetTotalUsedBytes() : 0.0;
        SiaCurrency totalAvailable;
        _siaApi->GetRenter()->CalculateEstimatedStorage(allocatedFunds, totalAvailable);
        SiaCurrency estCost;
        _siaApi->GetRenter()->CalculateEstimatedStorageCost(estCost);
        SiaCurrency estDlCost;
        _siaApi->GetRenter()->CalculateEstimatedDownloadCost(estDlCost);
        SiaCurrency estUlCost;
        _siaApi->GetRenter()->CalculateEstimatedUploadCost(estUlCost);

        auto totalRemain = totalAvailable > 0 ? totalAvailable - totalUsed : 0;
        ExecuteSetter(context, renterActions, "setEstimatedSpace", BytesToFriendlyDisplay(totalAvailable));
        ExecuteSetter(context, renterActions, "setEstimatedCost", SiaCurrencyToString(estCost));
        ExecuteSetter(context, renterActions, "setEstimatedDownloadCost", SiaCurrencyToString(estDlCost));
        ExecuteSetter(context, renterActions, "setEstimatedUploadCost", SiaCurrencyToString(estUlCost));
        ExecuteSetter(context, renterActions, "setAvailableSpace", BytesToFriendlyDisplay(totalRemain));
        ExecuteSetter(context, renterActions, "setUsedSpace", BytesToFriendlyDisplay(totalUsed));

        // Upload Progress
        /*
        SetRenterTotalUploadProgress(_siaApi->GetRenter()->GetTotalUploadProgress());
        */

        // Mount
        auto uiUpdate = global->GetValue("uiUpdate");
        auto drives = GetAvailableDrives();
        auto driveList = CefV8Value::CreateArray(drives.size());
        for (size_t i = 0; i < drives.size(); i++)
        {
          driveList->SetValue(i, CefV8Value::CreateString((drives[i] + ":\\").str()));
        }
        ExecuteSetter(context, uiUpdate, "setAvailableDriveLetters", driveList);

        // Renter settings
        auto allowance = _siaApi->GetRenter()->GetAllowance();
        auto obj = global->CreateObject(nullptr, nullptr);
        obj->SetValue("Funds", CefV8Value::CreateString(SiaCurrencyToString(allowance.Funds).str()), V8_PROPERTY_ATTRIBUTE_NONE);
        obj->SetValue("Hosts", CefV8Value::CreateString(SString::FromUInt64(allowance.Hosts).str()), V8_PROPERTY_ATTRIBUTE_NONE);
        obj->SetValue("Period", CefV8Value::CreateString(SString::FromUInt64(allowance.Period).str()), V8_PROPERTY_ATTRIBUTE_NONE);
        obj->SetValue("RenewWindowInBlocks", CefV8Value::CreateString(SString::FromUInt64(allowance.RenewWindowInBlocks).str()), V8_PROPERTY_ATTRIBUTE_NONE);
        ExecuteSetter(context, renterActions, "setAllowance", obj);

        // Display block height
        ExecuteSetter(context, uiActions, "setBlockHeight", SString::FromUInt64(_siaApi->GetConsensus()->GetHeight()));

        // Upload status
        auto uploadFileList = _siaDrive->GetUploadFileList();
        ExecuteSetter(context, renterActions, "setUploadProgress", uploadFileList->dump());

        if (!_siaDrive)
        {
#ifdef _WIN32
          _siaDrive.reset(new Dokan::CSiaDokanDrive(_siaApi.get(), _siaDriveConfig.get()));
#else
          a
#endif
        }
      }
    }

    if (isOnline != wasOnline)
    {
      CefRefPtr<CefV8Value> reloadApplication = global->GetValue("uiUpdate")->GetValue("reloadApplication");
      CefV8ValueList args;
      args.push_back(CefV8Value::CreateBool(isOnline));
      reloadApplication->ExecuteFunctionWithContext(context, nullptr, args);
      if (isOnline)
      {
        auto uiActions = global->GetValue("uiUpdate");
        ExecuteSetter(context, uiActions, "setServerVersion", _siaApi->GetServerVersion()); 
      }

      if (!isOnline && _siaDrive)
      {
        _siaDrive->Unmount();
      }
    }

    context->Exit();
  }
  else
  {
    CefPostTask(TID_RENDERER, base::Bind(&CSiaDriveApp::SiaApiRefreshCallback, this, context));
  }
}
