WHITESPACE = _{ " " | "\t" | "\n" | "\r" }
COMMENT = _{ "//" ~ (!"\n" ~ ANY)* | "/*" ~ (!"*/" ~ ANY)* ~ "*/" }

// Identifiers and basic types
identifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
number = @{ "-"? ~ ASCII_DIGIT+ }
string = @{ "\"" ~ (!"\"" ~ ANY)* ~ "\"" }
bool = @{ "true" | "false" }
hex_string = @{ "0x" ~ ASCII_HEX_DIGIT+ }
wildcard = @{ "*" }
unit = { "()" }
utxo_ref = { "0x" ~ ASCII_HEX_DIGIT+ ~ "#" ~ ASCII_DIGIT+ }

// Type identifiers

primitive_type = {
    "Int" |
    "Bool" |
    "Bytes" |
    "AnyAsset" |
    "Address" |
    "UtxoRef"
}

custom_type = { identifier }
list_type = { "List<" ~ type ~ ">" }
map_type = { "Map<" ~ type ~ "," ~ type ~ ">"}

type = {
    primitive_type |
    list_type |
    map_type |
    custom_type
}

// Parameters
parameter = { identifier ~ ":" ~ type }
parameter_list = { "(" ~ (parameter ~ ("," ~ parameter)* ~ ","?)? ~ ")" }

// Asset definitions
asset_def = {
    "asset" ~ identifier ~ "=" ~ data_expr ~ "." ~ data_expr ~ ";"
}

// Party definitions
party_def = {
    "party" ~ identifier ~ ";"
}

// Policy definitions
policy_def_assign = {
    "=" ~ hex_string ~ ";"
}

policy_def_hash = { "hash" ~ ":" ~ data_expr }
policy_def_script = { "script" ~ ":" ~ data_expr }
policy_def_ref = { "ref" ~ ":" ~ data_expr }

policy_def_field = _{
    policy_def_hash |
    policy_def_ref |
    policy_def_script
}

policy_def_constructor = {
    "{" ~ (policy_def_field ~ ",")* ~ "}"
}

policy_def_value = _{  policy_def_assign | policy_def_constructor }

policy_def = {
    "policy" ~ identifier ~ policy_def_value
}

// Type definitions
record_field = { identifier ~ ":" ~ type }

record_def = {
    "type" ~ identifier ~ "{" ~
    (record_field ~ ",")* ~
    "}"
}

variant_case_struct = {
    identifier ~ "{" ~
    (record_field ~ ",")* ~
    "}"
}

variant_case_tuple = {
    identifier ~ "(" ~
    (type ~ ",")* ~
    ")"
}

variant_case_unit = {
    identifier
}

variant_case = _{
    variant_case_struct |
    variant_case_tuple |
    variant_case_unit
}

variant_def = {
    "type" ~ identifier ~ "{" ~
    (variant_case ~ ",")* ~
    "}"
}

alias_def = {
    "type" ~ identifier ~ "=" ~ type ~ ";"
}

type_def = _{
    record_def |
    variant_def |
    alias_def
}

static_asset_constructor = {
    identifier ~ "(" ~ data_expr ~ ("," ~ data_expr)? ~ ")"
}

any_asset_constructor = {
    "AnyAsset" ~ "(" ~ data_expr ~ "," ~ data_expr ~ "," ~ data_expr ~ ")"
}

concat_constructor = {
    "concat" ~ "(" ~ data_expr ~ "," ~ data_expr ~ ")"
}

min_utxo = { "min_utxo" ~ "(" ~ identifier ~ ")" }

tip_slot = { "tip_slot" ~ "(" ~ ")" }

slot_to_time = { "slot_to_time" ~ "(" ~ data_expr ~ ")" }

time_to_slot = { "time_to_slot" ~ "(" ~ data_expr ~ ")" }

data_expr = { data_prefix* ~ data_primary ~ data_postfix* ~ (data_infix ~ data_prefix* ~ data_primary ~ data_postfix* )* }

    data_infix = _{ data_add | data_sub }
        data_add = { "+" }
        data_sub = { "-" }

    data_prefix = _{ data_negate }
        data_negate = { "!" }

    data_postfix = _{ data_property | data_index }
        data_property = { "." ~ identifier }
        data_index = { "[" ~ data_expr ~ "]" }

    data_primary = _{
        unit |
        utxo_ref |
        hex_string |
        number |
        bool |
        string |
        min_utxo |
        tip_slot |
        slot_to_time |
        time_to_slot |
        struct_constructor |
        list_constructor |
        map_constructor |
        concat_constructor |
        any_asset_constructor |
        static_asset_constructor |
        identifier |
        "(" ~ data_expr ~ ")"
    }

spread_expression = { "..." ~ data_expr }

record_constructor_field = {
    identifier ~ ":" ~ data_expr
}

explicit_variant_case_constructor = {
    "::" ~ identifier ~ "{" ~
    (record_constructor_field ~ ",")* ~
    spread_expression? ~
    "}"
}

implicit_variant_case_constructor = {
    "{" ~
    (record_constructor_field ~ ",")* ~
    spread_expression? ~
    "}"
}

variant_case_constructor = _{
    explicit_variant_case_constructor |
    implicit_variant_case_constructor
}

struct_constructor = {
    identifier ~ variant_case_constructor
}

list_constructor = {
    "[" ~ (data_expr ~ ",")* ~ data_expr? ~ "]"
}

map_field = {
    data_expr ~ ":" ~ data_expr
}

map_constructor = {
    "{" ~  (map_field ~ ",")+ ~ "}"
}

// input block

input_many = { "*" }
output_optional = { "?" }
input_block_from = { "from" ~ ":" ~ data_expr }
input_block_datum_is = { "datum_is" ~ ":" ~ type }
input_block_min_amount = { "min_amount" ~ ":" ~ data_expr }
input_block_redeemer = { "redeemer" ~ ":" ~ data_expr }
input_block_ref = { "ref" ~ ":" ~ data_expr }

input_block_field = _{
    input_block_from |
    input_block_datum_is |
    input_block_min_amount |
    input_block_redeemer |
    input_block_ref
}

input_block = {
    "input" ~ input_many? ~ identifier ~ "{" ~
    (input_block_field ~ ",")* ~
    "}"
}

collateral_block_field = _{
    input_block_from |
    input_block_min_amount |
    input_block_ref
}

collateral_block = {
    "collateral" ~ "{" ~
    (collateral_block_field ~ ",")* ~
    "}"
}

reference_block = {
    "reference" ~ identifier ~ ("*")? ~ "{" ~
    input_block_ref ~ "," ~
    "}"
}

output_block_to = { "to" ~ ":" ~ data_expr }
output_block_amount = { "amount" ~ ":" ~ data_expr }
output_block_datum = { "datum" ~ ":" ~ data_expr }

output_block_field = _{
    output_block_to |
    output_block_amount |
    output_block_datum
}

output_block = {
    "output" ~ output_optional? ~ identifier? ~ "{" ~
    (output_block_field ~ ",")* ~
    "}"
}

validity_until_slot = {"until_slot" ~ ":" ~ data_expr}
validity_since_slot = {"since_slot" ~ ":" ~ data_expr}

validity_block_field = _{
    validity_until_slot |
    validity_since_slot
}

validity_block = {
    "validity" ~ "{" ~
    (validity_block_field ~ ",")* ~
    "}"
}

mint_block_amount = { "amount" ~ ":" ~ data_expr }
mint_block_redeemer = { "redeemer" ~ ":" ~ data_expr }

mint_block_field = _{
    mint_block_amount |
    mint_block_redeemer
}

mint_block = {
    "mint" ~ "{" ~
    (mint_block_field ~ ",")* ~
    "}"
}

burn_block = {
    "burn" ~ "{" ~
    (mint_block_field ~ ",")* ~
    "}"
}

signers_block = {
    "signers" ~ "{" ~
        (data_expr ~ ",")* ~
    "}"
}

cardano_stake_delegation_certificate = {
    "stake_delegation_certificate" ~ "{" ~
    (record_constructor_field ~ ",")* ~
    "}"
}

cardano_vote_delegation_certificate = {
    "vote_delegation_certificate" ~ "{" ~
    "drep" ~ ":" ~ data_expr ~ "," ~
    "stake" ~ ":" ~ data_expr ~ "," ~
    "}"
}


cardano_withdrawal_from = {
    "from" ~ ":" ~ data_expr
}

cardano_withdrawal_amount = {
    "amount" ~ ":" ~ data_expr
}

cardano_withdrawal_redeemer = {
    "redeemer" ~ ":" ~ data_expr
}

cardano_withdrawal_field = _{
    cardano_withdrawal_from |
    cardano_withdrawal_amount |
    cardano_withdrawal_redeemer
}

cardano_withdrawal_block = {
    "withdrawal" ~ "{" ~  (cardano_withdrawal_field ~ ",")+ ~ "}"
}

cardano_plutus_witness_version = {
    "version" ~ ":" ~ data_expr
}

cardano_plutus_witness_script = {
    "script" ~ ":" ~ data_expr
}

cardano_plutus_witness_field = _{
    cardano_plutus_witness_version |
    cardano_plutus_witness_script
}

cardano_plutus_witness_block = {
    "plutus_witness" ~ "{" ~ (cardano_plutus_witness_field ~ ",")+ ~ "}"
}

cardano_native_witness_script = {
    "script" ~ ":" ~ data_expr
}

cardano_native_witness_field = _{
    cardano_native_witness_script
}

cardano_native_witness_block = {
    "native_witness" ~ "{" ~ (cardano_native_witness_field ~ ",")+ ~ "}"
}

cardano_treasury_donation_block = {
    "treasury_donation" ~ "{" ~ "coin" ~ ":" ~ data_expr ~ "," ~ "}"
}

cardano_publish_block_to = { "to" ~ ":" ~ data_expr }
cardano_publish_block_amount = { "amount" ~ ":" ~ data_expr }
cardano_publish_block_datum = { "datum" ~ ":" ~ data_expr }
cardano_publish_block_version = { "version" ~ ":" ~ data_expr }
cardano_publish_block_script = { "script" ~ ":" ~ data_expr }


cardano_publish_block_field = _{
    cardano_publish_block_to |
    cardano_publish_block_amount |
    cardano_publish_block_datum |
    cardano_publish_block_version |
    cardano_publish_block_script
}

cardano_publish_block = {
    "publish" ~ identifier? ~ "{" ~
    (cardano_publish_block_field ~ ",")* ~
    "}"
}

cardano_block = {
    "cardano" ~ "::" ~ (
        cardano_stake_delegation_certificate |
        cardano_vote_delegation_certificate |
        cardano_withdrawal_block |
        cardano_plutus_witness_block |
        cardano_native_witness_block |
        cardano_treasury_donation_block |
        cardano_publish_block
    )
}

bitcoin_block = {
    "bitcoin" ~ "::" ~ identifier
}

chain_specific_block = {
    cardano_block |
    bitcoin_block
}

metadata_block_field = {
    data_expr ~ ":" ~ data_expr
}

metadata_block = {
    "metadata" ~ "{" ~  (metadata_block_field ~ ",")+ ~ "}"
}

locals_assign = { identifier ~ ":" ~ data_expr }

locals_block = {
    "locals" ~ "{" ~ (locals_assign ~ ",")+ ~ "}"
}

tx_body_block = _{
    locals_block |
    reference_block |
    input_block |
    collateral_block |
    burn_block |
    mint_block |
    output_block |
    chain_specific_block |
    signers_block |
    metadata_block |
    validity_block
}

env_field = { identifier ~ ":" ~ type }

env_def = {
    "env" ~ "{" ~ (env_field ~ ",")+ ~ "}"
}

// Transaction definition
tx_def = {
    "tx" ~ identifier ~ parameter_list ~ "{" ~ tx_body_block* ~ "}"
}

// Program
program = {
    SOI ~
    (env_def | asset_def | party_def | policy_def | type_def | tx_def)* ~
    EOI
}
