use crate::{SysFuncIdx, FUEL_DENOM_RATE, QUADRATIC_DIVISOR, QUADRATIC_WORD_FUEL_COST};
use rwasm::{LinearFuelParams, QuadraticFuelParams, SyscallFuelParams};

/// In this file, we define the fuel procedures that will be inserted by the rwasm translator
/// before the builtin calls. Each fuel procedure is a set of rwasm Opcodes that will be
/// executed. Fuel procedures can potentially access the variables of the function they are
/// inserted into.
/// Word is defined as 32 bytes, the same as in the EVM.
macro_rules! no_fuel {
    () => {
        SyscallFuelParams::None
    };
}

macro_rules! const_fuel {
    ($base:expr) => {
        SyscallFuelParams::Const($base as u64)
    };
}

macro_rules! linear_fuel {
    ($param_index:expr, $base:expr, $linear:expr) => {
        SyscallFuelParams::LinearFuel(LinearFuelParams {
            base_fuel: $base,
            param_index: $param_index,
            word_cost: $linear,
        })
    };
}

macro_rules! quadratic_fuel {
    ($local_depth:expr, $word_cost:expr, $divisor:expr) => {
        SyscallFuelParams::QuadraticFuel(QuadraticFuelParams {
            divisor: $divisor,
            local_depth: $local_depth,
            word_cost: $word_cost,
            fuel_denom_rate: FUEL_DENOM_RATE as u32,
        })
    };
}

// Common fuel cost constants
pub const STATE_FUEL_COST: u32 = 20 * FUEL_DENOM_RATE as u32;
pub const COPY_BASE_FUEL_COST: u32 = 20 * FUEL_DENOM_RATE as u32;
pub const COPY_WORD_FUEL_COST: u32 = 3 * FUEL_DENOM_RATE as u32;
pub const DEBUG_LOG_BASE_FUEL_COST: u32 = 50 * FUEL_DENOM_RATE as u32;
pub const DEBUG_LOG_WORD_FUEL_COST: u32 = 16 * FUEL_DENOM_RATE as u32;
pub const CHARGE_FUEL_BASE_COST: u32 = 20 * FUEL_DENOM_RATE as u32;
pub const KECCAK_BASE_FUEL_COST: u32 = 30 * FUEL_DENOM_RATE as u32;
pub const KECCAK_WORD_FUEL_COST: u32 = 6 * FUEL_DENOM_RATE as u32;
pub const SHA256_BASE_FUEL_COST: u32 = 60 * FUEL_DENOM_RATE as u32;
pub const SHA256_WORD_FUEL_COST: u32 = 12 * FUEL_DENOM_RATE as u32;
pub const BLAKE3_BASE_FUEL_COST: u32 = 60 * FUEL_DENOM_RATE as u32;
pub const BLAKE3_WORD_FUEL_COST: u32 = 12 * FUEL_DENOM_RATE as u32;

// Ed25519
pub const ED25519_DECOMPRESS_COST: u32 = 5_000 * FUEL_DENOM_RATE as u32;
pub const ED25519_ADD_COST: u32 = 1_500 * FUEL_DENOM_RATE as u32;

// Tower field (BN254/BLS12-381)
pub const FP1_ADD_COST: u32 = 6 * FUEL_DENOM_RATE as u32;
pub const FP1_MUL_COST: u32 = 24 * FUEL_DENOM_RATE as u32;
pub const FP2_ADD_COST: u32 = 12 * FUEL_DENOM_RATE as u32;
pub const FP2_MUL_COST: u32 = 48 * FUEL_DENOM_RATE as u32;
pub const FP1_BLS_ADD_COST: u32 = 8 * FUEL_DENOM_RATE as u32;
pub const FP1_BLS_MUL_COST: u32 = 32 * FUEL_DENOM_RATE as u32;
pub const FP2_BLS_ADD_COST: u32 = 16 * FUEL_DENOM_RATE as u32;
pub const FP2_BLS_MUL_COST: u32 = 64 * FUEL_DENOM_RATE as u32;

// Secp256k1
pub const SECP256K1_ADD_COST: u32 = 150 * FUEL_DENOM_RATE as u32;
pub const SECP256K1_DOUBLE_COST: u32 = 150 * FUEL_DENOM_RATE as u32;
pub const SECP256K1_DECOMPRESS_COST: u32 = 4_000 * FUEL_DENOM_RATE as u32;

// Secp256r1
pub const SECP256R1_ADD_COST: u32 = 150 * FUEL_DENOM_RATE as u32;
pub const SECP256R1_DOUBLE_COST: u32 = 150 * FUEL_DENOM_RATE as u32;
pub const SECP256R1_DECOMPRESS_COST: u32 = 4_000 * FUEL_DENOM_RATE as u32;

// BN254
pub const BN254_ADD_COST: u32 = 150 * FUEL_DENOM_RATE as u32;
pub const BN254_DOUBLE_COST: u32 = 150 * FUEL_DENOM_RATE as u32;
pub const BN254_MUL_COST: u32 = 6_000 * FUEL_DENOM_RATE as u32;
pub const BN254_PAIRING_COST: u32 = 45_000 * FUEL_DENOM_RATE as u32;
pub const BN254_G1_COMPRESS_COST: u32 = 600 * FUEL_DENOM_RATE as u32;
pub const BN254_G1_DECOMPRESS_COST: u32 = 600 * FUEL_DENOM_RATE as u32;
pub const BN254_G2_COMPRESS_COST: u32 = 1_200 * FUEL_DENOM_RATE as u32;
pub const BN254_G2_DECOMPRESS_COST: u32 = 1_200 * FUEL_DENOM_RATE as u32;

// BLS12-381
pub const BLS_G1_ADD_COST: u32 = 600 * FUEL_DENOM_RATE as u32;
pub const BLS_G2_ADD_COST: u32 = 4_500 * FUEL_DENOM_RATE as u32;
pub const BLS_PAIRING_COST: u32 = 45_000 * FUEL_DENOM_RATE as u32;
pub const BLS_MAP_G1_COST: u32 = 8_000 * FUEL_DENOM_RATE as u32;
pub const BLS_MAP_G2_COST: u32 = 80_000 * FUEL_DENOM_RATE as u32;
pub const BLS_G1_DOUBLE_COST: u32 = 600 * FUEL_DENOM_RATE as u32;
pub const BLS_G1_DECOMPRESS_COST: u32 = 600 * FUEL_DENOM_RATE as u32;

// Big integer
pub const UINT256_MUL_MOD_COST: u32 = 8 * FUEL_DENOM_RATE as u32;
pub const UINT256_X2048_MUL_COST: u32 = 5_000 * FUEL_DENOM_RATE as u32;

/// Calldata surcharge threshold (128 KB).
///
/// Ethereum L1 rejects transactions above this size at the RPC level.
/// Fluent L2 accepts large calldata but applies a quadratic surcharge above this threshold
/// to bound block data to L1 blob capacity.
pub const CALLDATA_QUADRATIC_THRESHOLD: u64 = 128 * 1024;

/// Divisor in `surcharge = 3*words + words²/DIVISOR`.
/// Chosen so that 14 × 128 KB exceeds 100M gas (max block gas):
/// `DIVISOR < words²(53,248) / (100M - 7.4M linear) ≈ 30.6`
pub const CALLDATA_QUADRATIC_DIVISOR: u64 = 30;

/// Returns quadratic surcharge for `input_len` bytes above [`CALLDATA_QUADRATIC_THRESHOLD`].
pub fn calldata_quadratic_surcharge(input_len: u64) -> u64 {
    if input_len <= CALLDATA_QUADRATIC_THRESHOLD {
        return 0;
    }
    let excess = input_len - CALLDATA_QUADRATIC_THRESHOLD;
    let words = excess.div_ceil(32);
    3 * words + words * words / CALLDATA_QUADRATIC_DIVISOR
}

pub(crate) fn calculate_syscall_fuel(sys_func_idx: SysFuncIdx) -> SyscallFuelParams {
    use SysFuncIdx::*;
    match sys_func_idx {
        // input/output & state control (0x00)
        EXIT => no_fuel!(),
        STATE => const_fuel!(STATE_FUEL_COST),
        READ_INPUT => linear_fuel!(1, COPY_BASE_FUEL_COST, COPY_WORD_FUEL_COST),
        INPUT_SIZE => const_fuel!(STATE_FUEL_COST),
        WRITE_OUTPUT => linear_fuel!(1, COPY_BASE_FUEL_COST, COPY_WORD_FUEL_COST),
        OUTPUT_SIZE => const_fuel!(STATE_FUEL_COST),
        READ_OUTPUT => linear_fuel!(1, COPY_BASE_FUEL_COST, COPY_WORD_FUEL_COST),
        EXEC => quadratic_fuel!(3, QUADRATIC_WORD_FUEL_COST, QUADRATIC_DIVISOR),
        RESUME => no_fuel!(),
        FORWARD_OUTPUT => linear_fuel!(1, COPY_BASE_FUEL_COST, COPY_WORD_FUEL_COST),
        FUEL => const_fuel!(STATE_FUEL_COST),
        DEBUG_LOG => linear_fuel!(1, DEBUG_LOG_BASE_FUEL_COST, DEBUG_LOG_WORD_FUEL_COST),
        CHARGE_FUEL => const_fuel!(CHARGE_FUEL_BASE_COST),
        ENTER_UNCONSTRAINED => no_fuel!(),
        EXIT_UNCONSTRAINED => no_fuel!(),
        // TODO: use correct fuel calculations here, once we implement `WRITE_FD`, no fuel for now
        WRITE_FD => no_fuel!(),

        // hashing functions (0x01)
        KECCAK256 => linear_fuel!(2, KECCAK_BASE_FUEL_COST, KECCAK_WORD_FUEL_COST),
        KECCAK256_PERMUTE => const_fuel!(KECCAK_BASE_FUEL_COST),
        POSEIDON => linear_fuel!(2, 100, 20),
        SHA256_EXTEND => const_fuel!(SHA256_BASE_FUEL_COST),
        SHA256_COMPRESS => const_fuel!(SHA256_BASE_FUEL_COST),
        SHA256 => linear_fuel!(2, SHA256_BASE_FUEL_COST, SHA256_WORD_FUEL_COST),
        BLAKE3 => linear_fuel!(2, BLAKE3_BASE_FUEL_COST, BLAKE3_WORD_FUEL_COST),

        // ed25519 (0x02)
        ED25519_DECOMPRESS => const_fuel!(ED25519_DECOMPRESS_COST),
        ED25519_ADD => const_fuel!(ED25519_ADD_COST),

        // fp1/fp2 tower field (0x03)
        TOWER_FP1_BN254_ADD | TOWER_FP1_BN254_SUB => const_fuel!(FP1_ADD_COST),
        TOWER_FP1_BN254_MUL => const_fuel!(FP1_MUL_COST),
        TOWER_FP1_BLS12381_ADD | TOWER_FP1_BLS12381_SUB => const_fuel!(FP1_BLS_ADD_COST),
        TOWER_FP1_BLS12381_MUL => const_fuel!(FP1_BLS_MUL_COST),
        TOWER_FP2_BN254_ADD | TOWER_FP2_BN254_SUB => const_fuel!(FP2_ADD_COST),
        TOWER_FP2_BN254_MUL => const_fuel!(FP2_MUL_COST),
        TOWER_FP2_BLS12381_ADD | TOWER_FP2_BLS12381_SUB => const_fuel!(FP2_BLS_ADD_COST),
        TOWER_FP2_BLS12381_MUL => const_fuel!(FP2_BLS_MUL_COST),

        // secp256k1 (0x04)
        SECP256K1_ADD => const_fuel!(SECP256K1_ADD_COST),
        SECP256K1_DECOMPRESS => const_fuel!(SECP256K1_DECOMPRESS_COST),
        SECP256K1_DOUBLE => const_fuel!(SECP256K1_DOUBLE_COST),
        // secp256r1 (0x05)
        SECP256R1_ADD => const_fuel!(SECP256R1_ADD_COST),
        SECP256R1_DECOMPRESS => const_fuel!(SECP256R1_DECOMPRESS_COST),
        SECP256R1_DOUBLE => const_fuel!(SECP256R1_DOUBLE_COST),

        // bls12381 (0x06)
        BLS12381_ADD => const_fuel!(BLS_G1_ADD_COST),
        BLS12381_DECOMPRESS => const_fuel!(BLS_G1_DECOMPRESS_COST),
        BLS12381_DOUBLE => const_fuel!(BLS_G1_DOUBLE_COST),

        // bn254 (0x07)
        BN254_ADD => const_fuel!(BN254_ADD_COST),
        BN254_DOUBLE => const_fuel!(BN254_DOUBLE_COST),

        // uint256 (0x08)
        UINT256_MUL_MOD => const_fuel!(UINT256_MUL_MOD_COST),
        UINT256_X2048_MUL => const_fuel!(UINT256_X2048_MUL_COST),
    }
}
