//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2019
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once

#include "td/telegram/DialogId.h"
#include "td/telegram/files/FileEncryptionKey.h"
#include "td/telegram/files/FileLocation.h"
#include "td/telegram/files/FileLocation.hpp"
#include "td/telegram/files/FileManager.h"
#include "td/telegram/files/FileType.h"
#include "td/telegram/Version.h"

#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
#include "td/utils/tl_helpers.h"

namespace td {

enum class FileStoreType : int32 { Empty, Url, Generate, Local, Remote };

template <class StorerT>
void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const {
  auto file_store_type = FileStoreType::Empty;
  auto file_view = get_file_view(file_id);
  if (file_view.empty() || ttl <= 0) {
  } else if (file_view.has_remote_location()) {
    file_store_type = FileStoreType::Remote;
  } else if (file_view.has_url()) {
    file_store_type = FileStoreType::Url;
  } else if (file_view.has_generate_location()) {
    file_store_type = FileStoreType::Generate;
  } else if (file_view.has_local_location()) {
    file_store_type = FileStoreType::Local;
  }

  store(file_store_type, storer);

  bool has_encryption_key = false;
  bool has_expected_size =
      file_store_type == FileStoreType::Remote && file_view.size() == 0 && file_view.expected_size() != 0;
  bool has_secure_key = false;
  if (file_store_type != FileStoreType::Empty) {
    has_encryption_key = !file_view.empty() && file_view.is_encrypted_secret();
    has_secure_key = !file_view.empty() && file_view.is_encrypted_secure();
    BEGIN_STORE_FLAGS();
    STORE_FLAG(has_encryption_key);
    STORE_FLAG(has_expected_size);
    STORE_FLAG(has_secure_key);
    END_STORE_FLAGS();
  }

  switch (file_store_type) {
    case FileStoreType::Empty:
      break;
    case FileStoreType::Url:
      store(file_view.get_type(), storer);
      store(file_view.url(), storer);
      store(file_view.owner_dialog_id(), storer);
      break;
    case FileStoreType::Remote: {
      store(file_view.remote_location(), storer);
      if (has_expected_size) {
        store(narrow_cast<int32>(file_view.expected_size()), storer);
      } else {
        store(narrow_cast<int32>(file_view.size()), storer);
      }
      store(file_view.remote_name(), storer);
      store(file_view.owner_dialog_id(), storer);
      break;
    }
    case FileStoreType::Local: {
      store(file_view.local_location(), storer);
      store(narrow_cast<int32>(file_view.size()), storer);
      store(static_cast<int32>(file_view.get_by_hash()), storer);
      store(file_view.owner_dialog_id(), storer);
      break;
    }
    case FileStoreType::Generate: {
      auto generate_location = file_view.generate_location();
      FileId from_file_id;
      bool have_file_id = false;
      if (generate_location.conversion_ == "#_file_id#") {
        break;
      } else if (begins_with(generate_location.conversion_, "#file_id#")) {
        // It is not the best possible way to serialize file_id
        from_file_id =
            FileId(to_integer<int32>(Slice(generate_location.conversion_).remove_prefix(Slice("#file_id#").size())), 0);
        generate_location.conversion_ = "#_file_id#";
        have_file_id = true;
      }
      store(generate_location, storer);
      store(static_cast<int32>(file_view.expected_size()), storer);
      store(static_cast<int32>(0), storer);
      store(file_view.owner_dialog_id(), storer);

      if (have_file_id) {
        store_file(from_file_id, storer, ttl - 1);
      }
      break;
    }
    default:
      UNREACHABLE();
  }
  if (has_encryption_key) {
    store(file_view.encryption_key(), storer);
  } else if (has_secure_key) {
    store(file_view.encryption_key(), storer);
  }
}

template <class ParserT>
FileId FileManager::parse_file(ParserT &parser) {
  if (parser.version() < static_cast<int32>(Version::StoreFileId)) {
    return FileId();
  }

  FileStoreType file_store_type;
  parse(file_store_type, parser);

  bool has_encryption_key = false;
  bool has_expected_size = false;
  bool has_secure_key = false;
  if (file_store_type != FileStoreType::Empty) {
    if (parser.version() >= static_cast<int32>(Version::StoreFileEncryptionKey)) {
      BEGIN_PARSE_FLAGS();
      PARSE_FLAG(has_encryption_key);
      PARSE_FLAG(has_expected_size);
      PARSE_FLAG(has_secure_key);
      END_PARSE_FLAGS();
    }
  }

  auto file_id = [&] {
    switch (file_store_type) {
      case FileStoreType::Empty:
        return FileId();
      case FileStoreType::Remote: {
        FullRemoteFileLocation full_remote_location;
        parse(full_remote_location, parser);
        int32 size = 0;
        int32 expected_size = 0;
        if (has_expected_size) {
          parse(expected_size, parser);
        } else {
          parse(size, parser);
        }
        string name;
        parse(name, parser);
        DialogId owner_dialog_id;
        if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
          parse(owner_dialog_id, parser);
        }
        return register_remote(full_remote_location, FileLocationSource::FromBinlog, owner_dialog_id, size,
                               expected_size, name);
      }
      case FileStoreType::Local: {
        FullLocalFileLocation full_local_location;
        parse(full_local_location, parser);
        int32 size;
        parse(size, parser);
        int32 get_by_hash;
        parse(get_by_hash, parser);
        DialogId owner_dialog_id;
        if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
          parse(owner_dialog_id, parser);
        }
        auto r_file_id = register_local(full_local_location, owner_dialog_id, size, get_by_hash != 0);
        if (r_file_id.is_ok()) {
          return r_file_id.move_as_ok();
        }
        LOG(ERROR) << "Can't resend local file " << full_local_location << " of size " << size << " owned by "
                   << owner_dialog_id;
        return register_empty(full_local_location.file_type_);
      }
      case FileStoreType::Generate: {
        FullGenerateFileLocation full_generated_location;
        parse(full_generated_location, parser);
        int32 expected_size;
        parse(expected_size, parser);
        int32 zero;
        parse(zero, parser);
        DialogId owner_dialog_id;
        if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
          parse(owner_dialog_id, parser);
        }
        if (begins_with(full_generated_location.conversion_, "#file_id#")) {
          LOG(ERROR) << "Can't resend message with '#file_id#...' location";
          return register_empty(full_generated_location.file_type_);
        }
        if (full_generated_location.conversion_ == "#_file_id#") {
          auto file_id = parse_file(parser);
          if (file_id.empty()) {
            return register_empty(full_generated_location.file_type_);
          }
          auto download_file_id = dup_file_id(file_id);
          full_generated_location.conversion_ = PSTRING() << "#file_id#" << download_file_id.get();
        }

        auto r_file_id = register_generate(full_generated_location.file_type_, FileLocationSource::FromBinlog,
                                           full_generated_location.original_path_, full_generated_location.conversion_,
                                           owner_dialog_id, expected_size);
        if (r_file_id.is_ok()) {
          return r_file_id.move_as_ok();
        }
        return register_empty(full_generated_location.file_type_);
      }
      case FileStoreType::Url: {
        FileType type;
        string url;
        parse(type, parser);
        parse(url, parser);
        DialogId owner_dialog_id;
        if (parser.version() >= static_cast<int32>(Version::StoreFileOwnerId)) {
          parse(owner_dialog_id, parser);
        }
        return register_url(url, type, FileLocationSource::FromBinlog, owner_dialog_id);
      }
    }
    return FileId();
  }();

  if (has_encryption_key) {
    FileEncryptionKey encryption_key;
    encryption_key.parse(FileEncryptionKey::Type::Secret, parser);
    set_encryption_key(file_id, std::move(encryption_key));
  } else if (has_secure_key) {
    FileEncryptionKey encryption_key;
    encryption_key.parse(FileEncryptionKey::Type::Secure, parser);
    set_encryption_key(file_id, std::move(encryption_key));
  }

  return file_id;
}

}  // namespace td