//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// 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/files/FileLocation.h"

#include "td/telegram/files/FileType.h"
#include "td/telegram/net/DcId.h"
#include "td/telegram/PhotoSizeSource.hpp"
#include "td/telegram/Version.h"

#include "td/utils/common.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/Variant.h"

namespace td {

template <class StorerT>
void PartialRemoteFileLocation::store(StorerT &storer) const {
  using td::store;
  store(file_id_, storer);
  store(part_count_, storer);
  store(part_size_, storer);
  store(ready_part_count_, storer);
  store(is_big_, storer);
}

template <class ParserT>
void PartialRemoteFileLocation::parse(ParserT &parser) {
  using td::parse;
  parse(file_id_, parser);
  parse(part_count_, parser);
  parse(part_size_, parser);
  parse(ready_part_count_, parser);
  parse(is_big_, parser);
}

template <class StorerT>
void PhotoRemoteFileLocation::store(StorerT &storer) const {
  using td::store;
  store(id_, storer);
  store(access_hash_, storer);
  store(volume_id_, storer);
  store(source_, storer);
  store(local_id_, storer);
}

template <class ParserT>
void PhotoRemoteFileLocation::parse(ParserT &parser) {
  using td::parse;
  parse(id_, parser);
  parse(access_hash_, parser);
  parse(volume_id_, parser);
  if (parser.version() >= static_cast<int32>(Version::AddPhotoSizeSource)) {
    parse(source_, parser);
  } else {
    int64 secret;
    parse(secret, parser);
    source_ = PhotoSizeSource(secret);
  }
  parse(local_id_, parser);
}

template <class StorerT>
void PhotoRemoteFileLocation::AsKey::store(StorerT &storer) const {
  using td::store;
  if (!is_unique) {
    store(key.id_, storer);
  }
  store(key.volume_id_, storer);
  store(key.local_id_, storer);
}

template <class StorerT>
void WebRemoteFileLocation::store(StorerT &storer) const {
  using td::store;
  store(url_, storer);
  store(access_hash_, storer);
}

template <class ParserT>
void WebRemoteFileLocation::parse(ParserT &parser) {
  using td::parse;
  parse(url_, parser);
  parse(access_hash_, parser);
}

template <class StorerT>
void WebRemoteFileLocation::AsKey::store(StorerT &storer) const {
  td::store(key.url_, storer);
}

template <class StorerT>
void CommonRemoteFileLocation::store(StorerT &storer) const {
  using td::store;
  store(id_, storer);
  store(access_hash_, storer);
}

template <class ParserT>
void CommonRemoteFileLocation::parse(ParserT &parser) {
  using td::parse;
  parse(id_, parser);
  parse(access_hash_, parser);
}

template <class StorerT>
void CommonRemoteFileLocation::AsKey::store(StorerT &storer) const {
  td::store(key.id_, storer);
}

template <class StorerT>
void FullRemoteFileLocation::store(StorerT &storer) const {
  using ::td::store;
  bool has_file_reference = !file_reference_.empty();
  auto type = key_type();
  if (has_file_reference) {
    type |= FILE_REFERENCE_FLAG;
  }
  store(type, storer);
  store(dc_id_.get_value(), storer);
  if (has_file_reference) {
    store(file_reference_, storer);
  }
  variant_.visit([&](auto &&value) {
    using td::store;
    store(value, storer);
  });
}

template <class ParserT>
void FullRemoteFileLocation::parse(ParserT &parser) {
  using ::td::parse;
  int32 raw_type;
  parse(raw_type, parser);
  bool is_web = (raw_type & WEB_LOCATION_FLAG) != 0;
  raw_type &= ~WEB_LOCATION_FLAG;
  bool has_file_reference = (raw_type & FILE_REFERENCE_FLAG) != 0;
  raw_type &= ~FILE_REFERENCE_FLAG;
  if (raw_type < 0 || raw_type >= static_cast<int32>(FileType::Size)) {
    return parser.set_error("Invalid FileType in FullRemoteFileLocation");
  }
  file_type_ = static_cast<FileType>(raw_type);
  int32 dc_id_value;
  parse(dc_id_value, parser);
  dc_id_ = DcId::from_value(dc_id_value);

  if (has_file_reference) {
    parse(file_reference_, parser);
    if (file_reference_ == FileReferenceView::invalid_file_reference()) {
      file_reference_.clear();
    }
  }
  if (is_web) {
    variant_ = WebRemoteFileLocation();
    return web().parse(parser);
  }

  switch (location_type()) {
    case LocationType::Web:
      UNREACHABLE();
      break;
    case LocationType::Photo:
      variant_ = PhotoRemoteFileLocation();
      photo().parse(parser);
      if (parser.get_error() != nullptr) {
        return;
      }
      switch (photo().source_.get_type()) {
        case PhotoSizeSource::Type::Legacy:
          break;
        case PhotoSizeSource::Type::Thumbnail:
          if (photo().source_.get_file_type() != file_type_ ||
              (file_type_ != FileType::Photo && file_type_ != FileType::Thumbnail &&
               file_type_ != FileType::EncryptedThumbnail)) {
            parser.set_error("Invalid FileType in PhotoRemoteFileLocation Thumbnail");
          }
          break;
        case PhotoSizeSource::Type::DialogPhotoSmall:
        case PhotoSizeSource::Type::DialogPhotoBig:
          if (file_type_ != FileType::ProfilePhoto) {
            parser.set_error("Invalid FileType in PhotoRemoteFileLocation DialogPhoto");
          }
          break;
        case PhotoSizeSource::Type::StickerSetThumbnail:
          if (file_type_ != FileType::Thumbnail) {
            parser.set_error("Invalid FileType in PhotoRemoteFileLocation StickerSetThumbnail");
          }
          break;
        default:
          UNREACHABLE();
          break;
      }
      return;
    case LocationType::Common:
      variant_ = CommonRemoteFileLocation();
      return common().parse(parser);
    case LocationType::None:
      break;
  }
  parser.set_error("Invalid FileType in FullRemoteFileLocation");
}

template <class StorerT>
void FullRemoteFileLocation::AsKey::store(StorerT &storer) const {
  using td::store;
  store(key.key_type(), storer);
  key.variant_.visit([&](auto &&value) {
    using td::store;
    store(value.as_key(false), storer);
  });
}

template <class StorerT>
void FullRemoteFileLocation::AsUnique::store(StorerT &storer) const {
  using td::store;

  int32 type = [key = &key] {
    if (key->is_web()) {
      return 0;
    }
    switch (key->file_type_) {
      case FileType::Photo:
      case FileType::ProfilePhoto:
      case FileType::Thumbnail:
      case FileType::EncryptedThumbnail:
      case FileType::Wallpaper:
        return 1;
      case FileType::Video:
      case FileType::VoiceNote:
      case FileType::Document:
      case FileType::Sticker:
      case FileType::Audio:
      case FileType::Animation:
      case FileType::VideoNote:
      case FileType::Background:
        return 2;
      case FileType::SecureRaw:
      case FileType::Secure:
        return 3;
      case FileType::Encrypted:
        return 4;
      case FileType::Temp:
        return 5;
      case FileType::None:
      case FileType::Size:
      default:
        UNREACHABLE();
        return -1;
    }
  }();
  store(type, storer);
  key.variant_.visit([&](auto &&value) {
    using td::store;
    store(value.as_key(true), storer);
  });
}

template <class StorerT>
void RemoteFileLocation::store(StorerT &storer) const {
  td::store(variant_, storer);
}

template <class ParserT>
void RemoteFileLocation::parse(ParserT &parser) {
  td::parse(variant_, parser);
}

template <class StorerT>
void PartialLocalFileLocation::store(StorerT &storer) const {
  using td::store;
  store(file_type_, storer);
  store(path_, storer);
  store(part_size_, storer);
  int32 deprecated_ready_part_count = -1;
  store(deprecated_ready_part_count, storer);
  store(iv_, storer);
  store(ready_bitmask_, storer);
}

template <class ParserT>
void PartialLocalFileLocation::parse(ParserT &parser) {
  using td::parse;
  parse(file_type_, parser);
  if (file_type_ < FileType::Thumbnail || file_type_ >= FileType::Size) {
    return parser.set_error("Invalid type in PartialLocalFileLocation");
  }
  parse(path_, parser);
  parse(part_size_, parser);
  int32 deprecated_ready_part_count;
  parse(deprecated_ready_part_count, parser);
  parse(iv_, parser);
  if (deprecated_ready_part_count == -1) {
    parse(ready_bitmask_, parser);
  } else {
    CHECK(0 <= deprecated_ready_part_count);
    CHECK(deprecated_ready_part_count <= (1 << 22));
    ready_bitmask_ = Bitmask(Bitmask::Ones{}, deprecated_ready_part_count).encode();
  }
}

template <class StorerT>
void FullLocalFileLocation::store(StorerT &storer) const {
  using td::store;
  store(file_type_, storer);
  store(mtime_nsec_, storer);
  store(path_, storer);
}

template <class ParserT>
void FullLocalFileLocation::parse(ParserT &parser) {
  using td::parse;
  parse(file_type_, parser);
  if (file_type_ < FileType::Thumbnail || file_type_ >= FileType::Size) {
    return parser.set_error("Invalid type in FullLocalFileLocation");
  }
  parse(mtime_nsec_, parser);
  parse(path_, parser);
}

template <class StorerT>
void PartialLocalFileLocationPtr::store(StorerT &storer) const {
  td::store(*location_, storer);
}

template <class ParserT>
void PartialLocalFileLocationPtr::parse(ParserT &parser) {
  td::parse(*location_, parser);
}

template <class StorerT>
void LocalFileLocation::store(StorerT &storer) const {
  td::store(variant_, storer);
}

template <class ParserT>
void LocalFileLocation::parse(ParserT &parser) {
  td::parse(variant_, parser);
}

template <class StorerT>
void FullGenerateFileLocation::store(StorerT &storer) const {
  using td::store;
  store(file_type_, storer);
  store(original_path_, storer);
  store(conversion_, storer);
}

template <class ParserT>
void FullGenerateFileLocation::parse(ParserT &parser) {
  using td::parse;
  parse(file_type_, parser);
  parse(original_path_, parser);
  parse(conversion_, parser);
}

template <class StorerT>
void GenerateFileLocation::store(StorerT &storer) const {
  td::store(type_, storer);
  switch (type_) {
    case Type::Empty:
      return;
    case Type::Full:
      return td::store(full_, storer);
  }
}

template <class ParserT>
void GenerateFileLocation::parse(ParserT &parser) {
  td::parse(type_, parser);
  switch (type_) {
    case Type::Empty:
      return;
    case Type::Full:
      return td::parse(full_, parser);
  }
  return parser.set_error("Invalid type in GenerateFileLocation");
}

}  // namespace td