/*
 * Copyright contributors to Hyperledger Besu
 *
 * Licensed 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 */
package org.hyperledger.besu.evm;

import java.util.Comparator;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** The enum Evm spec version. */
public enum EvmSpecVersion {
  /** Frontier evm spec version. */
  FRONTIER(0, true, "Frontier", "Finalized"),
  /** Homestead evm spec version. */
  HOMESTEAD(0, true, "Homestead", "Finalized"),
  /** Tangerine Whistle evm spec version. */
  TANGERINE_WHISTLE(0, true, "Tangerine Whistle", "Finalized"),
  /** Spurious Dragon evm spec version. */
  SPURIOUS_DRAGON(0, true, "Spuruous Dragon", "Finalized"),
  /** Byzantium evm spec version. */
  BYZANTIUM(0, true, "Byzantium", "Finalized"),
  /** Constantinople evm spec version. */
  CONSTANTINOPLE(0, true, "Constantinople", "Did not reach Mainnet"),
  /** Petersburg / ConstantinopleFix evm spec version. */
  PETERSBURG(0, true, "ConstantinopleFix", "Finalized (also called Petersburg)"),
  /** Istanbul evm spec version. */
  ISTANBUL(0, true, "Istanbul", "Finalized"),
  /** Berlin evm spec version */
  BERLIN(0, true, "Berlin", "Finalized"),
  /** London evm spec version. */
  LONDON(0, true, "London", "Finalized"),
  /** Paris evm spec version. */
  PARIS(0, true, "Merge", "Finalized (also called Paris)"),
  /** Shanghai evm spec version. */
  SHANGHAI(0, true, "Shanghai", "Finalized"),
  /** Cancun evm spec version. */
  CANCUN(0, true, "Cancun", "Finalized"),
  /** Prague evm spec version. */
  PRAGUE(0, false, "Prague", "In Development"),
  /** Osaka evm spec version. */
  OSAKA(0, false, "Osaka", "Placeholder"),
  /** Bogota evm spec version. */
  BOGOTA(0, false, "Bogota", "Placeholder"),
  /** Development fork for unscheduled EIPs */
  FUTURE_EIPS(1, false, "Future_EIPs", "Development, for accepted and unscheduled EIPs"),
  /** Development fork for EIPs not accepted to Mainnet */
  EXPERIMENTAL_EIPS(1, false, "Experimental_EIPs", "Development, for experimental EIPs");

  private static final Logger LOGGER = LoggerFactory.getLogger(EvmSpecVersion.class);

  /** The Spec finalized. */
  final boolean specFinalized;
  /** The Max eof version. */
  final int maxEofVersion;

  /** Public name matching execution-spec-tests name */
  final String name;
  /** A brief description of the state of the fork */
  final String description;

  /** The Version warned. */
  boolean versionWarned = false;

  EvmSpecVersion(
      final int maxEofVersion,
      final boolean specFinalized,
      final String name,
      final String description) {
    this.maxEofVersion = maxEofVersion;
    this.specFinalized = specFinalized;
    this.name = name;
    this.description = description;
  }

  /**
   * What is the "default" version of EVM that should be made. Newer versions of Besu will adjust
   * this to reflect mainnet fork development.
   *
   * @return the current mainnet for as of the release of this version of Besu
   */
  public static EvmSpecVersion defaultVersion() {
    return SHANGHAI;
  }

  /**
   * Gets max eof version.
   *
   * @return the max eof version
   */
  public int getMaxEofVersion() {
    return maxEofVersion;
  }

  /**
   * Name of the fork, in execution-spec-tests form
   *
   * @return name of the fork
   */
  public String getName() {
    return name;
  }

  /**
   * Description of the fork
   *
   * @return description
   */
  public String getDescription() {
    return description;
  }

  /** Maybe warn version. */
  @SuppressWarnings("AlreadyChecked") // false positive
  public void maybeWarnVersion() {
    if (versionWarned) {
      return;
    }

    if (!specFinalized) {
      LOGGER.error(
          "****** Not for Production Network Use ******\nExecuting code from EVM Spec Version {}, which has not been finalized.\n****** Not for Production Network Use ******",
          this.name());
    }
    versionWarned = true;
  }

  /**
   * Calculate a spec version from a text fork name.
   *
   * @param name The name of the fork, such as "shanghai" or "berlin"
   * @return the EVM spec version for that fork, or null if no fork matched.
   */
  public static EvmSpecVersion fromName(final String name) {
    for (var version : EvmSpecVersion.values()) {
      if (version.name().equalsIgnoreCase(name)) {
        return version;
      }
    }
    return null;
  }

  /**
   * The most recent deployed evm supported by the library. This will change across versions and
   * will be updated after mainnet activations.
   *
   * @return the most recently activated mainnet spec.
   */
  public static EvmSpecVersion mostRecent() {
    return Stream.of(EvmSpecVersion.values())
        .filter(v -> v.specFinalized)
        .max(Comparator.naturalOrder())
        .orElseThrow();
  }
}
