///|
pub(all) enum Direction {
  Input
  Output
  Inout
  Unspecified
} derive(Eq, Debug)

///|
pub(all) enum NetType {
  Wire
  Reg
  Logic
  Unspecified
} derive(Eq, Debug)

///|
pub(all) struct Range {
  msb : Int
  lsb : Int
  span : @source.Span
} derive(Eq, Debug)

///|
pub fn Range::new(msb : Int, lsb : Int, span : @source.Span) -> Range {
  { msb, lsb, span }
}

///|
pub fn Range::width(self : Range) -> Int? {
  let distance = if self.msb >= self.lsb {
    self.msb - self.lsb + 1
  } else {
    self.lsb - self.msb + 1
  }
  if distance > 0 {
    Some(distance)
  } else {
    None
  }
}

///|
pub(all) struct Port {
  name : String
  direction : Direction
  width : Range?
  span : @source.Span
} derive(Eq, Debug)

///|
pub fn Port::new(
  name : String,
  direction : Direction,
  width : Range?,
  span : @source.Span,
) -> Port {
  { name, direction, width, span }
}

///|
pub(all) struct Signal {
  name : String
  net_type : NetType
  width : Range?
  span : @source.Span
} derive(Eq, Debug)

///|
pub fn Signal::new(
  name : String,
  net_type : NetType,
  width : Range?,
  span : @source.Span,
) -> Signal {
  { name, net_type, width, span }
}

///|
pub(all) enum Expression {
  Name(String, @source.Span)
  Number(String, @source.Span)
  StringLiteral(String, @source.Span)
  Unary(String, Expression, @source.Span)
  Binary(Expression, String, Expression, @source.Span)
  Index(Expression, Expression, @source.Span)
  Unknown(@source.Span)
} derive(Eq, Debug)

///|
pub fn Expression::name(name : String, span : @source.Span) -> Expression {
  Name(name, span)
}

///|
pub fn Expression::number(value : String, span : @source.Span) -> Expression {
  Number(value, span)
}

///|
pub fn Expression::string(value : String, span : @source.Span) -> Expression {
  StringLiteral(value, span)
}

///|
pub fn Expression::binary(
  left : Expression,
  operator : String,
  right : Expression,
  span : @source.Span,
) -> Expression {
  Binary(left, operator, right, span)
}

///|
pub fn Expression::span(self : Expression) -> @source.Span {
  match self {
    Name(_, span) => span
    Number(_, span) => span
    StringLiteral(_, span) => span
    Unary(_, _, span) => span
    Binary(_, _, _, span) => span
    Index(_, _, span) => span
    Unknown(span) => span
  }
}

///|
pub(all) struct ContinuousAssign {
  target : Expression
  value : Expression
  span : @source.Span
} derive(Eq, Debug)

///|
pub fn ContinuousAssign::new(
  target : Expression,
  value : Expression,
  span : @source.Span,
) -> ContinuousAssign {
  { target, value, span }
}

///|
pub(all) enum EdgeKind {
  Any
  Posedge
  Negedge
} derive(Eq, Debug)

///|
pub(all) struct Sensitivity {
  edge : EdgeKind
  expression : Expression
  span : @source.Span
} derive(Eq, Debug)

///|
pub(all) struct CaseArm {
  choices : Array[Expression]
  body : Statement
  span : @source.Span
} derive(Eq, Debug)

///|
pub(all) enum Statement {
  Empty(@source.Span)
  Block(Array[Statement], @source.Span)
  Assignment(Expression, Expression, Bool, @source.Span)
  If(Expression, Statement, Statement?, @source.Span)
  Case(Expression, Array[CaseArm], @source.Span)
  Unknown(@source.Span)
} derive(Eq, Debug)

///|
pub fn Statement::span(self : Statement) -> @source.Span {
  match self {
    Empty(span) => span
    Block(_, span) => span
    Assignment(_, _, _, span) => span
    If(_, _, _, span) => span
    Case(_, _, span) => span
    Unknown(span) => span
  }
}

///|
pub(all) struct AlwaysBlock {
  sensitivity : Array[Sensitivity]
  body : Statement
  span : @source.Span
} derive(Eq, Debug)

///|
pub(all) struct Connection {
  port_name : String?
  expression : Expression
  span : @source.Span
} derive(Eq, Debug)

///|
pub(all) struct Instance {
  module_name : String
  instance_name : String
  connections : Array[Connection]
  span : @source.Span
} derive(Eq, Debug)

///|
pub(all) enum ModuleItem {
  Assign(ContinuousAssign)
  Always(AlwaysBlock)
  Instance(Instance)
  Statement(Statement)
  Unknown(@source.Span)
} derive(Eq, Debug)

///|
pub(all) struct Module {
  name : String
  ports : Array[Port]
  signals : Array[Signal]
  items : Array[ModuleItem]
  span : @source.Span
} derive(Eq, Debug)

///|
pub fn Module::new(
  name : String,
  ports : Array[Port],
  signals : Array[Signal],
  items : Array[ModuleItem],
  span : @source.Span,
) -> Module {
  { name, ports, signals, items, span }
}

///|
pub(all) struct Design {
  modules : Array[Module]
} derive(Eq, Debug)

///|
pub fn Design::new(modules : Array[Module]) -> Design {
  { modules, }
}