/*
 * Copyright 2016 Miroslav Janíček
 *
 * 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.
 */

package org.classdump.luna.compiler;

import java.util.Objects;

/**
 * An immutable class encapsulating the settings of the compilation of Lua to Java bytecode
 * by {@link LuaCompiler}.
 *
 * <p>The settings are the following</p>
 * <ul>
 * <li><b>CPU accounting</b> ({@link CPUAccountingMode}): controls whether and how
 * the functions generated by the compiler account for number of ticks spent in execution
 * and pause when their time slice has expired;</li>
 * <li><b>const folding</b> (boolean): when {@code true}, constants are folded at compile
 * time (note that this does not have an influence on the number of ticks counted);</li>
 * <li><b>const caching</b> (boolean): when {@code true}, boxed numeric constants are stored
 * as static fields rather than being instantiated (and boxed) at execution time;</li>
 * <li><b>node size limit</b> (int): when positive, long functions are split up into smaller
 * Java methods (each containing at most the specified number of IR nodes); otherwise,
 * a single method containing the entire function code is generated. Java class files
 * impose a strict limit of 64 kB per method: this setting allows the compilation
 * of arbitrarily-long Lua functions.</li>
 * </ul>
 *
 * <p>To obtain the settings with sensible defaults, use {@link CompilerSettings#defaultSettings()}.
 * To obtain the default settings with CPU accounting disabled,
 * use {@link CompilerSettings#defaultNoAccountingSettings()}.
 * </p>
 */
public final class CompilerSettings {

  /**
   * The default CPU accounting mode.
   */
  public static final CPUAccountingMode DEFAULT_CPU_ACCOUNTING_MODE = CPUAccountingMode.IN_EVERY_BASIC_BLOCK;
  /**
   * The default const folding mode.
   */
  public static final boolean DEFAULT_CONST_FOLDING_MODE = true;
  /**
   * The default const caching mode.
   */
  public static final boolean DEFAULT_CONST_CACHING_MODE = true;
  /**
   * The default byte string mode.
   */
  public static final boolean DEFAULT_BYTE_STRING_MODE = true;
  /**
   * The default method size limit.
   */
  public static final int DEFAULT_NODE_SIZE_LIMIT = 2000;
  private final CPUAccountingMode cpuAccountingMode;
  private final boolean constFolding;
  private final boolean constCaching;
  private final boolean byteStrings;
  private final int nodeSizeLimit;
  CompilerSettings(
      CPUAccountingMode cpuAccountingMode,
      boolean constFolding,
      boolean constCaching,
      boolean byteStrings,
      int nodeSizeLimit) {

    this.cpuAccountingMode = Objects.requireNonNull(cpuAccountingMode);
    this.constFolding = constFolding;
    this.constCaching = constCaching;
    this.byteStrings = byteStrings;
    this.nodeSizeLimit = nodeSizeLimit;
  }

  /**
   * Returns the compiler settings with the given parameters.
   *
   * <p>When {@code nodeSizeLimit} is non-positive, no chunking of the body method
   * will be performed.</p>
   *
   * @param cpuAccountingMode CPU accounting mode, must not be {@code null}
   * @param constFolding const folding mode
   * @param constCaching const caching mode
   * @param byteStrings byte string mode
   * @param nodeSizeLimit node size limit
   * @return the corresponding compiler settings
   * @throws NullPointerException if {@code cpuAccountingMode} is {@code null}
   */
  public static CompilerSettings of(
      CPUAccountingMode cpuAccountingMode,
      boolean constFolding,
      boolean constCaching,
      boolean byteStrings,
      int nodeSizeLimit) {

    return new CompilerSettings(
        cpuAccountingMode, constFolding, constCaching, byteStrings, nodeSizeLimit);
  }

  /**
   * Returns the default compiler settings.
   *
   * @return the default compiler settings
   */
  public static CompilerSettings defaultSettings() {
    return CompilerSettings.of(
        DEFAULT_CPU_ACCOUNTING_MODE,
        DEFAULT_CONST_FOLDING_MODE,
        DEFAULT_CONST_CACHING_MODE,
        DEFAULT_BYTE_STRING_MODE,
        DEFAULT_NODE_SIZE_LIMIT);
  }

  /**
   * Returns the default compiler settings without CPU accounting.
   *
   * @return the default compiler settings without CPU accounting
   */
  public static CompilerSettings defaultNoAccountingSettings() {
    return defaultSettings().withCPUAccountingMode(CPUAccountingMode.NO_CPU_ACCOUNTING);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }

    CompilerSettings that = (CompilerSettings) o;

    return this.cpuAccountingMode == that.cpuAccountingMode
        && this.constFolding == that.constFolding
        && this.constCaching == that.constCaching
        && this.byteStrings == that.byteStrings
        && this.nodeSizeLimit == that.nodeSizeLimit;
  }

  @Override
  public int hashCode() {
    int result = cpuAccountingMode.hashCode();
    result = 31 * result + (constFolding ? 1 : 0);
    result = 31 * result + (constCaching ? 1 : 0);
    result = 31 * result + (byteStrings ? 1 : 0);
    result = 31 * result + nodeSizeLimit;
    return result;
  }

  /**
   * Returns the CPU accounting mode.
   *
   * @return the CPU accounting mode
   */
  public CPUAccountingMode cpuAccountingMode() {
    return cpuAccountingMode;
  }

  /**
   * Returns the const folding mode.
   *
   * @return the const folding mode
   */
  public boolean constFolding() {
    return constFolding;
  }

  /**
   * Returns the const caching mode.
   *
   * @return the const caching mode
   */
  public boolean constCaching() {
    return constCaching;
  }

  public boolean byteStrings() {
    return byteStrings;
  }

  /**
   * Returns the node size limit.
   *
   * @return the node size limit
   */
  public int nodeSizeLimit() {
    return nodeSizeLimit;
  }

  /**
   * Returns compiler settings derived from this compiler settings by updating
   * the CPU accounting mode to {@code mode}.
   *
   * @param mode new CPU accounting mode, must not be {@code null}
   * @return settings derived from {@code this} by updating the CPU accounting mode to {@code mode}
   * @throws NullPointerException if {@code mode} is {@code null}
   */
  public CompilerSettings withCPUAccountingMode(CPUAccountingMode mode) {
    return mode != this.cpuAccountingMode
        ? new CompilerSettings(mode, constFolding, constCaching, byteStrings, nodeSizeLimit)
        : this;
  }

  /**
   * Returns compiler settings derived from this compiler settings by updating
   * the const folding mode to {@code mode}.
   *
   * @param mode new const folding mode
   * @return settings derived from {@code this} by updating the const folding mode to {@code mode}
   */
  public CompilerSettings withConstFolding(boolean mode) {
    return mode != this.constFolding
        ? new CompilerSettings(cpuAccountingMode, mode, constCaching, byteStrings, nodeSizeLimit)
        : this;
  }

  /**
   * Returns compiler settings derived from this compiler settings by updating
   * the const caching mode to {@code mode}.
   *
   * @param mode new const caching mode
   * @return settings derived from {@code this} by updating the const caching mode to {@code mode}
   */
  public CompilerSettings withConstCaching(boolean mode) {
    return mode != this.constCaching
        ? new CompilerSettings(cpuAccountingMode, constFolding, mode, byteStrings, nodeSizeLimit)
        : this;
  }

  /**
   * Returns compiler settings derived from this compiler settings by updating
   * the byte string mode to {@code mode}.
   *
   * @param mode new byte string mode
   * @return settings derived from {@code this} by updating the byte string mode to {@code mode}
   */
  public CompilerSettings withByteStrings(boolean mode) {
    return mode != this.byteStrings
        ? new CompilerSettings(cpuAccountingMode, constFolding, constCaching, mode, nodeSizeLimit)
        : this;
  }

  /**
   * Returns compiler settings derived from this compiler settings by updating
   * the node size limit to {@code limit}.
   *
   * @param limit new node size limit
   * @return settings derived from {@code this} by updating the node size limit to {@code limit}
   */
  public CompilerSettings withNodeSizeLimit(int limit) {
    return limit != this.nodeSizeLimit
        ? new CompilerSettings(cpuAccountingMode, constFolding, constCaching, byteStrings, limit)
        : this;
  }

  /**
   * CPU accounting mode.
   */
  public enum CPUAccountingMode {

    /**
     * Do not do CPU accounting.
     */
    NO_CPU_ACCOUNTING,

    /**
     * Check CPU time usage at the beginning of every basic block.
     *
     * <p>At the beginning of every basic block, update the ticks by invoking
     * {@link org.classdump.luna.runtime.ExecutionContext#registerTicks(int)}
     * and potentially pause by invoking
     * {@link org.classdump.luna.runtime.ExecutionContext#pauseIfRequested()}.</p>
     */
    IN_EVERY_BASIC_BLOCK

  }

}
