#include "client/API.h"

using namespace binom;

TcpClient& operator<<(TcpClient& client, ByteArray data) {
  client.sendData(data.begin(), data.length());
  return client;
}

TcpClient& operator<<(TcpClient& client, Variable data) {
  return client << data.serialize();
}

Variable API::encrypt(Variable data) {
  return vobj{
    {"act", ui8(ClientAction::encrypted)},
    {"content", BufferArray(Security::encrypt(data.serialize(), transport_aes256_key))}
  };
}

Variable API::auth(Variable data) {
  FileNodeVisitor node = db["session"];
  return encrypt(
        vobj{
          {"act", ui8(ClientAction::uuid_check)},
          {"login", node.getVariable("login")},
          {"uuid", node.getVariable("uuid")},
          {"content", data}
        });
}

void ApplicationProgrammingInterface::connectImpl(uint32_t host, uint16_t port) {
  if(client.connectTo(host, port) == SocketStatus::connected) {

    client << vobj{
      {"act", ui8(ClientAction::key_exchange)},
      {"key", BufferArray(Security::extractPublicKey(transport_ecdh_key_pair))}
    };

    client.setHandler([this](DataBuffer data){
      try {
        if(!data.isEmpty())
          routeOpenLayer(binom::Variable::deserialize(binom::ByteArray(data.data_ptr, data.size)));
      } catch(binom::Exception& e) {
        std::cerr << e.full() << '\n';
        emit recivedError(QString::fromStdString(e.full()));
      }
    });

    BufferArray server_name(sizeof(host)+sizeof(port), 0_ui8);
    *reinterpret_cast<uint32_t*>(server_name.getDataPointer()) = host;
    *reinterpret_cast<uint16_t*>(((byte*)server_name.getDataPointer())+sizeof(host)) = port;
    if(auto node = db["server"]; !node.contains(server_name)) {
      node.setVariable(server_name, vobj{
                         {"server_info", vobj{}},
                         {"account_list", vobj{}}
                       });
      server_node = node[server_name];
    } else server_node = node[server_name];
    db["active_session"].setVariable(0, server_name);

    qDebug() << "Client handler thread started";

    emit connectionStateChanged(true);
    return;
  } else {
    emit recivedError("Connection to server error!");
    return;
  }
}

API::ApplicationProgrammingInterface(std::string database_path)
  : QObject(nullptr),
    db(database_path + "/data",
       #include "client_db.h"
    ),
    transport_ecdh_key_pair(Security::genKey()) {
  if(auto active_session = db["active_session"]; active_session.getElementCount())
    tryConnect();
}

QVariant API::converBinOMtoQVariant(Variable variable) {
  switch (variable.type()) {
    case binom::VarType::byte: return QVariant(variable.getValue().asUi8());
    case binom::VarType::word: return QVariant(variable.getValue().asUi16());
    case binom::VarType::dword: return QVariant(variable.getValue().asUi32());
    case binom::VarType::qword: return QVariant(qulonglong(variable.getValue().asUi64()));
    case binom::VarType::byte_array: return QVariant(QByteArray((char*)variable.toBufferArray().getDataPointer(), int(variable.toBufferArray().getMemberCount())));
    case binom::VarType::word_array: {
      QVariantList list;
      for(auto val : variable.toBufferArray())
        list.push_back(val.asUi16());
      return list;
    }
    case binom::VarType::dword_array: {
      QVariantList list;
      for(auto val : variable.toBufferArray())
        list.push_back(val.asUi32());
      return list;
    }
    case binom::VarType::qword_array:{
      QVariantList list;
      for(auto val : variable.toBufferArray())
        list.push_back(qulonglong(val.asUi64()));
      return list;
    }
    case binom::VarType::array:{
      QVariantList list;
      for(auto element : variable.toArray())
        list.push_back(converBinOMtoQVariant(element));
      return list;
    }
    case binom::VarType::object: {
      QVariantMap map;
      for(auto named_element : variable.toObject())
        map.insert(QString::fromStdString(named_element.name), converBinOMtoQVariant(named_element.variable));
      return map;
    }
    default:
    case binom::VarType::end:
    case binom::VarType::invalid_type: return QVariant();
  }
}

QString API::getMimeType(QString url) {
  static QMimeDatabase mime_data;
  return mime_data.mimeTypeForFile(url).name();
}

void API::tryConnect() {
  FileNodeVisitor node = db.getRoot()("active_session");
  if(!node.getElementCount()) return;
  ByteArray host_data = node.getVariable(0).toBufferArray().toByteArray();
  connectImpl(host_data.get<uint32_t>(0), host_data.get<uint16_t>(0, sizeof (uint32_t)));
}

void API::tryAuthenticateByUUID() {
  if(db["active_session"].getElementCount() < 2) return;
  auto account = server_node[{"account_list", db["active_session"].getVariable(1).toBufferArray()}];
  if(account.isNull()) return;
  elif(!account.contains("uuid") || !account.contains("key")) return;

  private_aes256_key = *(Security::AES_t*)account.getVariable("key").toBufferArray().getDataPointer();
  if(!isConnectionSecured()) {
    emit recivedError("Connection isn't secured!");
    return;
  }

  client << encrypt(
              vobj{
                {"act", ui8(ClientAction::uuid_authentication)},
                {"login", *account.getName()},
                {"uuid", account.getVariable("uuid")},
              });
}

QVariant API::getServerInfo() const {
  if(server_node.isNull()) return QVariantMap();
  return converBinOMtoQVariant(server_node.getVariable("server_info"));
}


void API::requestUserList() {
//  FileNodeVisitor node = db.getRoot()("session");
//  client << auth(vobj{
//                   {"act", ui8(ClientAction::request)},
//                   {"request_type", ui8(RequestCode::get_user_profiles)}
//                 });
}

void API::requestServerInfo() {client << encrypt(vobj{{"act", ui8(ClientAction::server_info)}});}

QVariant API::getUserList() const {
  FileNodeVisitor node = const_cast<DynamicStorage&>(db);
  if(!node.contains({"session","user_list","list"})) return QVariantList();
  else node({"session","user_list","list"});
  QVariantList user_list;
  for(auto element : node)
    user_list.push_back(converBinOMtoQVariant(element.getVariable()));
  return user_list;
}

QVariant ApplicationProgrammingInterface::getProfile() {
  if(account_node.isNull()) return QVariant();
  if(!account_node.contains("profile")) return QVariant();
  return converBinOMtoQVariant(account_node.getVariable("profile"));
}
