/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include "iceberg/table_update.h"

#include "iceberg/schema.h"
#include "iceberg/snapshot.h"
#include "iceberg/sort_order.h"
#include "iceberg/statistics_file.h"
#include "iceberg/table_metadata.h"
#include "iceberg/table_requirements.h"
#include "iceberg/util/checked_cast.h"
#include "iceberg/util/macros.h"

namespace iceberg {
TableUpdate::~TableUpdate() = default;
}

namespace iceberg::table {

// AssignUUID

void AssignUUID::ApplyTo(TableMetadataBuilder& builder) const {
  builder.AssignUUID(uuid_);
}

void AssignUUID::GenerateRequirements(TableUpdateContext& context) const {
  // AssignUUID does not generate additional requirements.
}

bool AssignUUID::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kAssignUUID) {
    return false;
  }
  const auto& other_assign = internal::checked_cast<const AssignUUID&>(other);
  return uuid_ == other_assign.uuid_;
}

std::unique_ptr<TableUpdate> AssignUUID::Clone() const {
  return std::make_unique<AssignUUID>(uuid_);
}

// UpgradeFormatVersion

void UpgradeFormatVersion::ApplyTo(TableMetadataBuilder& builder) const {
  builder.UpgradeFormatVersion(format_version_);
}

void UpgradeFormatVersion::GenerateRequirements(TableUpdateContext& context) const {
  // UpgradeFormatVersion doesn't generate any requirements
}

bool UpgradeFormatVersion::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kUpgradeFormatVersion) {
    return false;
  }
  const auto& other_upgrade = internal::checked_cast<const UpgradeFormatVersion&>(other);
  return format_version_ == other_upgrade.format_version_;
}

std::unique_ptr<TableUpdate> UpgradeFormatVersion::Clone() const {
  return std::make_unique<UpgradeFormatVersion>(format_version_);
}

// AddSchema

void AddSchema::ApplyTo(TableMetadataBuilder& builder) const {
  builder.AddSchema(schema_);
}

void AddSchema::GenerateRequirements(TableUpdateContext& context) const {
  context.RequireLastAssignedFieldIdUnchanged();
}

bool AddSchema::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kAddSchema) {
    return false;
  }
  const auto& other_add = internal::checked_cast<const AddSchema&>(other);
  if (!schema_ != !other_add.schema_) {
    return false;
  }
  if (schema_ && !(*schema_ == *other_add.schema_)) {
    return false;
  }
  return last_column_id_ == other_add.last_column_id_;
}

std::unique_ptr<TableUpdate> AddSchema::Clone() const {
  return std::make_unique<AddSchema>(schema_, last_column_id_);
}

// SetCurrentSchema

void SetCurrentSchema::ApplyTo(TableMetadataBuilder& builder) const {
  builder.SetCurrentSchema(schema_id_);
}

void SetCurrentSchema::GenerateRequirements(TableUpdateContext& context) const {
  context.RequireCurrentSchemaIdUnchanged();
}

bool SetCurrentSchema::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kSetCurrentSchema) {
    return false;
  }
  const auto& other_set = internal::checked_cast<const SetCurrentSchema&>(other);
  return schema_id_ == other_set.schema_id_;
}

std::unique_ptr<TableUpdate> SetCurrentSchema::Clone() const {
  return std::make_unique<SetCurrentSchema>(schema_id_);
}

// AddPartitionSpec

void AddPartitionSpec::ApplyTo(TableMetadataBuilder& builder) const {
  builder.AddPartitionSpec(spec_);
}

void AddPartitionSpec::GenerateRequirements(TableUpdateContext& context) const {
  context.RequireLastAssignedPartitionIdUnchanged();
}

bool AddPartitionSpec::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kAddPartitionSpec) {
    return false;
  }
  const auto& other_add = internal::checked_cast<const AddPartitionSpec&>(other);
  if (!spec_ != !other_add.spec_) {
    return false;
  }
  if (spec_ && *spec_ != *other_add.spec_) {
    return false;
  }
  return true;
}

std::unique_ptr<TableUpdate> AddPartitionSpec::Clone() const {
  return std::make_unique<AddPartitionSpec>(spec_);
}

// SetDefaultPartitionSpec

void SetDefaultPartitionSpec::ApplyTo(TableMetadataBuilder& builder) const {
  builder.SetDefaultPartitionSpec(spec_id_);
}

void SetDefaultPartitionSpec::GenerateRequirements(TableUpdateContext& context) const {
  context.RequireDefaultSpecIdUnchanged();
}

bool SetDefaultPartitionSpec::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kSetDefaultPartitionSpec) {
    return false;
  }
  const auto& other_set = internal::checked_cast<const SetDefaultPartitionSpec&>(other);
  return spec_id_ == other_set.spec_id_;
}

std::unique_ptr<TableUpdate> SetDefaultPartitionSpec::Clone() const {
  return std::make_unique<SetDefaultPartitionSpec>(spec_id_);
}

// RemovePartitionSpecs

void RemovePartitionSpecs::ApplyTo(TableMetadataBuilder& builder) const {
  builder.RemovePartitionSpecs(spec_ids_);
}

void RemovePartitionSpecs::GenerateRequirements(TableUpdateContext& context) const {
  context.RequireDefaultSpecIdUnchanged();
  context.RequireNoBranchesChanged();
}

bool RemovePartitionSpecs::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kRemovePartitionSpecs) {
    return false;
  }
  const auto& other_remove = internal::checked_cast<const RemovePartitionSpecs&>(other);
  return spec_ids_ == other_remove.spec_ids_;
}

std::unique_ptr<TableUpdate> RemovePartitionSpecs::Clone() const {
  return std::make_unique<RemovePartitionSpecs>(spec_ids_);
}

// RemoveSchemas

void RemoveSchemas::ApplyTo(TableMetadataBuilder& builder) const {
  builder.RemoveSchemas(schema_ids_);
}

void RemoveSchemas::GenerateRequirements(TableUpdateContext& context) const {
  context.RequireCurrentSchemaIdUnchanged();
  context.RequireNoBranchesChanged();
}

bool RemoveSchemas::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kRemoveSchemas) {
    return false;
  }
  const auto& other_remove = internal::checked_cast<const RemoveSchemas&>(other);
  return schema_ids_ == other_remove.schema_ids_;
}

std::unique_ptr<TableUpdate> RemoveSchemas::Clone() const {
  return std::make_unique<RemoveSchemas>(schema_ids_);
}

// AddSortOrder

void AddSortOrder::ApplyTo(TableMetadataBuilder& builder) const {
  builder.AddSortOrder(sort_order_);
}

void AddSortOrder::GenerateRequirements(TableUpdateContext& context) const {
  // AddSortOrder doesn't generate any requirements
}

bool AddSortOrder::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kAddSortOrder) {
    return false;
  }
  const auto& other_add = internal::checked_cast<const AddSortOrder&>(other);
  if (!sort_order_ != !other_add.sort_order_) {
    return false;
  }
  if (sort_order_ && !(*sort_order_ == *other_add.sort_order_)) {
    return false;
  }
  return true;
}

std::unique_ptr<TableUpdate> AddSortOrder::Clone() const {
  return std::make_unique<AddSortOrder>(sort_order_);
}

// SetDefaultSortOrder

void SetDefaultSortOrder::ApplyTo(TableMetadataBuilder& builder) const {
  builder.SetDefaultSortOrder(sort_order_id_);
}

void SetDefaultSortOrder::GenerateRequirements(TableUpdateContext& context) const {
  context.RequireDefaultSortOrderIdUnchanged();
}

bool SetDefaultSortOrder::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kSetDefaultSortOrder) {
    return false;
  }
  const auto& other_set = internal::checked_cast<const SetDefaultSortOrder&>(other);
  return sort_order_id_ == other_set.sort_order_id_;
}

std::unique_ptr<TableUpdate> SetDefaultSortOrder::Clone() const {
  return std::make_unique<SetDefaultSortOrder>(sort_order_id_);
}

// AddSnapshot

void AddSnapshot::ApplyTo(TableMetadataBuilder& builder) const {
  builder.AddSnapshot(snapshot_);
}

void AddSnapshot::GenerateRequirements(TableUpdateContext& context) const {
  // AddSnapshot doesn't generate any requirements
}

bool AddSnapshot::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kAddSnapshot) {
    return false;
  }
  const auto& other_add = internal::checked_cast<const AddSnapshot&>(other);
  if (!snapshot_ != !other_add.snapshot_) {
    return false;
  }
  if (snapshot_ && *snapshot_ != *other_add.snapshot_) {
    return false;
  }
  return true;
}

std::unique_ptr<TableUpdate> AddSnapshot::Clone() const {
  return std::make_unique<AddSnapshot>(snapshot_);
}

// RemoveSnapshots

void RemoveSnapshots::ApplyTo(TableMetadataBuilder& builder) const {
  builder.RemoveSnapshots(snapshot_ids_);
}

void RemoveSnapshots::GenerateRequirements(TableUpdateContext& context) const {
  // RemoveSnapshots doesn't generate any requirements
}

bool RemoveSnapshots::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kRemoveSnapshots) {
    return false;
  }
  const auto& other_remove = internal::checked_cast<const RemoveSnapshots&>(other);
  return snapshot_ids_ == other_remove.snapshot_ids_;
}

std::unique_ptr<TableUpdate> RemoveSnapshots::Clone() const {
  return std::make_unique<RemoveSnapshots>(snapshot_ids_);
}

// RemoveSnapshotRef

void RemoveSnapshotRef::ApplyTo(TableMetadataBuilder& builder) const {
  builder.RemoveRef(ref_name_);
}

void RemoveSnapshotRef::GenerateRequirements(TableUpdateContext& context) const {
  // RemoveSnapshotRef doesn't generate any requirements
}

bool RemoveSnapshotRef::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kRemoveSnapshotRef) {
    return false;
  }
  const auto& other_remove = internal::checked_cast<const RemoveSnapshotRef&>(other);
  return ref_name_ == other_remove.ref_name_;
}

std::unique_ptr<TableUpdate> RemoveSnapshotRef::Clone() const {
  return std::make_unique<RemoveSnapshotRef>(ref_name_);
}

// SetSnapshotRef

void SetSnapshotRef::ApplyTo(TableMetadataBuilder& builder) const {
  std::shared_ptr<SnapshotRef> ref;
  if (type_ == SnapshotRefType::kBranch) {
    ICEBERG_ASSIGN_OR_THROW(
        ref, SnapshotRef::MakeBranch(snapshot_id_, min_snapshots_to_keep_,
                                     max_snapshot_age_ms_, max_ref_age_ms_));
  } else {
    ICEBERG_ASSIGN_OR_THROW(ref, SnapshotRef::MakeTag(snapshot_id_, max_ref_age_ms_));
  }
  builder.SetRef(ref_name_, std::move(ref));
}

void SetSnapshotRef::GenerateRequirements(TableUpdateContext& context) const {
  bool added = context.AddChangedRef(ref_name_);
  if (added && context.base() != nullptr && !context.is_replace()) {
    const auto& refs = context.base()->refs;
    auto it = refs.find(ref_name_);
    // Require that the ref does not exist (nullopt) or is the same as the base snapshot
    std::optional<int64_t> base_snapshot_id =
        (it != refs.end()) ? std::make_optional(it->second->snapshot_id) : std::nullopt;
    context.AddRequirement(
        std::make_unique<table::AssertRefSnapshotID>(ref_name_, base_snapshot_id));
  }
}

bool SetSnapshotRef::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kSetSnapshotRef) {
    return false;
  }
  const auto& other_set = internal::checked_cast<const SetSnapshotRef&>(other);
  return ref_name_ == other_set.ref_name_ && snapshot_id_ == other_set.snapshot_id_ &&
         type_ == other_set.type_ &&
         min_snapshots_to_keep_ == other_set.min_snapshots_to_keep_ &&
         max_snapshot_age_ms_ == other_set.max_snapshot_age_ms_ &&
         max_ref_age_ms_ == other_set.max_ref_age_ms_;
}

std::unique_ptr<TableUpdate> SetSnapshotRef::Clone() const {
  return std::make_unique<SetSnapshotRef>(ref_name_, snapshot_id_, type_,
                                          min_snapshots_to_keep_, max_snapshot_age_ms_,
                                          max_ref_age_ms_);
}

// SetProperties

void SetProperties::ApplyTo(TableMetadataBuilder& builder) const {
  builder.SetProperties(updated_);
}

void SetProperties::GenerateRequirements(TableUpdateContext& context) const {
  // SetProperties doesn't generate any requirements
}

bool SetProperties::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kSetProperties) {
    return false;
  }
  const auto& other_set = internal::checked_cast<const SetProperties&>(other);
  return updated_ == other_set.updated_;
}

std::unique_ptr<TableUpdate> SetProperties::Clone() const {
  return std::make_unique<SetProperties>(updated_);
}

// RemoveProperties

void RemoveProperties::ApplyTo(TableMetadataBuilder& builder) const {
  builder.RemoveProperties(removed_);
}

void RemoveProperties::GenerateRequirements(TableUpdateContext& context) const {
  // RemoveProperties doesn't generate any requirements
}

bool RemoveProperties::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kRemoveProperties) {
    return false;
  }
  const auto& other_remove = internal::checked_cast<const RemoveProperties&>(other);
  return removed_ == other_remove.removed_;
}

std::unique_ptr<TableUpdate> RemoveProperties::Clone() const {
  return std::make_unique<RemoveProperties>(removed_);
}

// SetLocation

void SetLocation::ApplyTo(TableMetadataBuilder& builder) const {
  builder.SetLocation(location_);
}

void SetLocation::GenerateRequirements(TableUpdateContext& context) const {
  // SetLocation doesn't generate any requirements
}

bool SetLocation::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kSetLocation) {
    return false;
  }
  const auto& other_set = internal::checked_cast<const SetLocation&>(other);
  return location_ == other_set.location_;
}

std::unique_ptr<TableUpdate> SetLocation::Clone() const {
  return std::make_unique<SetLocation>(location_);
}

// SetStatistics

int64_t SetStatistics::snapshot_id() const { return statistics_file_->snapshot_id; }

void SetStatistics::ApplyTo(TableMetadataBuilder& builder) const {
  builder.SetStatistics(statistics_file_);
}

void SetStatistics::GenerateRequirements(TableUpdateContext& context) const {
  // SetStatistics doesn't generate any requirements
}

bool SetStatistics::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kSetStatistics) {
    return false;
  }
  const auto& other_set = internal::checked_cast<const SetStatistics&>(other);
  if (!statistics_file_ != !other_set.statistics_file_) {
    return false;
  }
  if (statistics_file_ && !(*statistics_file_ == *other_set.statistics_file_)) {
    return false;
  }
  return true;
}

std::unique_ptr<TableUpdate> SetStatistics::Clone() const {
  return std::make_unique<SetStatistics>(statistics_file_);
}

// RemoveStatistics

void RemoveStatistics::ApplyTo(TableMetadataBuilder& builder) const {
  builder.RemoveStatistics(snapshot_id_);
}

void RemoveStatistics::GenerateRequirements(TableUpdateContext& context) const {
  // RemoveStatistics doesn't generate any requirements
}

bool RemoveStatistics::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kRemoveStatistics) {
    return false;
  }
  const auto& other_remove = internal::checked_cast<const RemoveStatistics&>(other);
  return snapshot_id_ == other_remove.snapshot_id_;
}

std::unique_ptr<TableUpdate> RemoveStatistics::Clone() const {
  return std::make_unique<RemoveStatistics>(snapshot_id_);
}

// SetPartitionStatistics

int64_t SetPartitionStatistics::snapshot_id() const {
  return partition_statistics_file_->snapshot_id;
}

void SetPartitionStatistics::ApplyTo(TableMetadataBuilder& builder) const {
  builder.SetPartitionStatistics(partition_statistics_file_);
}

void SetPartitionStatistics::GenerateRequirements(TableUpdateContext& context) const {
  // SetPartitionStatistics doesn't generate any requirements
}

bool SetPartitionStatistics::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kSetPartitionStatistics) {
    return false;
  }
  const auto& other_set = internal::checked_cast<const SetPartitionStatistics&>(other);
  if (!partition_statistics_file_ != !other_set.partition_statistics_file_) {
    return false;
  }
  if (partition_statistics_file_ &&
      !(*partition_statistics_file_ == *other_set.partition_statistics_file_)) {
    return false;
  }
  return true;
}

std::unique_ptr<TableUpdate> SetPartitionStatistics::Clone() const {
  return std::make_unique<SetPartitionStatistics>(partition_statistics_file_);
}

// RemovePartitionStatistics

void RemovePartitionStatistics::ApplyTo(TableMetadataBuilder& builder) const {
  builder.RemovePartitionStatistics(snapshot_id_);
}

void RemovePartitionStatistics::GenerateRequirements(TableUpdateContext& context) const {
  // RemovePartitionStatistics doesn't generate any requirements
}

bool RemovePartitionStatistics::Equals(const TableUpdate& other) const {
  if (other.kind() != Kind::kRemovePartitionStatistics) {
    return false;
  }
  const auto& other_remove =
      internal::checked_cast<const RemovePartitionStatistics&>(other);
  return snapshot_id_ == other_remove.snapshot_id_;
}

std::unique_ptr<TableUpdate> RemovePartitionStatistics::Clone() const {
  return std::make_unique<RemovePartitionStatistics>(snapshot_id_);
}

}  // namespace iceberg::table
