using Base: @kwdef
import Base: isequal, hash

const stdcomment = "// Autogenerated by Bitstream DSL compiler"

struct Variable
    name::Symbol
    type::Symbol
end

getname(x::Variable) = string(x.name)
gettype(x::Variable) = x.type

"""
    Operation

An operation is represented by a list of input types,
a list of output types, and an operator.

# Examples:
```julia
# x represents the addition operation on a pair of
#  stochastic bits (which returns a stochastic bit)
x = Operation([:SBit, :SBit], [:SBit], :+)
```
"""
struct Operation
    inputs::Vector{Symbol}
    outputs::Vector{Symbol}
    operator::Symbol
end
isequal(x::Operation, y::Operation) =
    all(x.inputs .== y.inputs) && all(x.outputs .== y.outputs) && (x.operator == y.operator)
hash(x::Operation) = hash(hash(hash(x.inputs), hash(x.outputs)), hash(x.operator))

opalias(x::Operation) = Operation(x.inputs, x.outputs, opalias(x.operator))
opalias(x) = (x == :÷) ? :div : x

"""
    AbstractHandler

Inherit from this type to create a Verilog generation handler.

This object is responsible for managing all instantiations
of its associated Verilog module.
It should also update the netlist as it adds nets.
"""
abstract type AbstractHandler end

_handlermap = Dict{Operation, Type}()

"""
    register(op::Operation, handler::AbstractHandler)

Register a mapping from an operation to a handler.

# Examples:
```julia
# register a stochastic add to an add handler
register(Operation([:SBit, :SBit], [:SBit], :+), SAddHandler)
```
"""
register(op::Operation, handler::Type{T}) where T <: AbstractHandler =
    _handlermap[opalias(op)] = handler

gethandler(op::Operation) = haskey(_handlermap, op) ? _handlermap[op] :
    error("""
        Cannot find handler for $(op.operator) ($(op.inputs)) -> $(op.outputs).
        Perhaps you forgot to register a handler for your custom operation?
        """)

"""
    HW.allowconstreplace(::Type{T}) where T <: AbstractHandler

Override this function to block constant replacement for the inputs to your handler.

For example, `x + 2` is a node with inputs: `[:x, :2]`.
The constant input could replaced with a Verilog string `Symbol("2'b10")`.
Overriding the return value of this function to `false` will prevent this replacement from happening.
"""
allowconstreplace(::Type{T}) where T <: AbstractHandler = true

"""
    HW.extractrtinfo!(handler::AbstractHandler, innames, outname, opname, inputs, output, op)

Override this function to extract runtime information.

# Fields:
- `handler::AbstractHandler`: an instance of your handler
- `innames::Vector{Symbol}`: the names of the arguments into the node
- `outname::Symbol`: the name of the return out of the node
- `opname::Symbol`: the name of the operator stored in the DFG at this node
- `inputs`: a vector of the actual input argument objects into the node
- `output`: the actual return object from the node
- `op`: the actual function called in Julia (for callable structs this is the struct instance itself)
"""
extractrtinfo!(handler::AbstractHandler, innames, outname, opname, inputs, output, op) = nothing

maptype(x) = @capture(x, Matrix{T_}) ? [Symbol("Array{$T,2}")] :
             @capture(x, Vector{T_}) ? [Symbol("Array{$T,1}")] :
             @capture(x, Number) ? vcat(maptype(:Integer), maptype(:Real)) :
             @capture(x, Real) ? [:Float16, :Float32, :Float64] :
             @capture(x, Integer) ? [:UInt8, :Int8, :UInt16, :Int16, :UInt32, :Int32, :UInt64, :Int64, :UInt128, :Int128] :
             [Symbol(x)]

function register(intypes, outtypes, op, handler::Symbol)
    nintypes = length(intypes)
    typematrix = maptype.(vcat(intypes, outtypes))

    for types in Base.product(typematrix...)
        register(Operation([types[1:nintypes]...], [types[(nintypes + 1):end]...], op), @eval($handler))
    end
end

"""
    HW.@register

A convenience macro for registering a specific handler to a certain operation type.

# Examples

The following code will map any call to `+` with the specified argument/return types
to the `SAddHandler` object.
```julia
@register(SAddHandler, +, begin
    [SBit, SBit] => [SBit]
    [SBit, Vector{SBit}] => [Vector{SBit}]
    [Vector{SBit}, SBit] => [Vector{SBit}]
    [Vector{SBit}, Vector{SBit}] => [Vector{SBit}]
    [SBit, Matrix{SBit}] => [Matrix{SBit}]
    [Matrix{SBit}, SBit] => [Matrix{SBit}]
    [Matrix{SBit}, Matrix{SBit}] => [Matrix{SBit}]
end)
```
"""
macro register(handler, op, ex)
    @capture(unblock(rmlines(ex)), rules__) || error("Could not parse registration.")
    for rule in rules
        @capture(rule, ins_ => outs_) ||
            error("Could not parse registration $rule (use format '[:SBit, :SBit] => [:SBit]')")
        (@capture(ins, [intypes__]) && @capture(outs, [outtypes__])) ||
            error("Specify registration as array (use format '[:SBit, :SBit] => [:SBit]'")
        register(intypes, outtypes, op, handler)
    end

    return :(nothing)
end

include("handlers/utils.jl")
include("handlers/saddhandler.jl")
include("handlers/ssubhandler.jl")
include("handlers/smulthandler.jl")
include("handlers/sdivhandler.jl")
include("handlers/sfdivhandler.jl")
include("handlers/ssqrthandler.jl")
include("handlers/sl2normhandler.jl")
include("handlers/transposehandler.jl")
include("handlers/daddhandler.jl")
include("handlers/dsubhandler.jl")
include("handlers/dmulthandler.jl")
include("handlers/fxpaddhandler.jl")
include("handlers/fxpsubhandler.jl")
include("handlers/fxpmulthandler.jl")
include("handlers/sdmhandler.jl")
include("handlers/delaybufferhandler.jl")