package shared

type Type interface {
  IsPrimitive() bool
  IsStruct() bool
  IsPointer() bool
}

func GetSizeOfType(t Type) int {
  if t == PRIMITIVE_U8 || t == PRIMITIVE_I8 {
    return 4
  }
  if t == PRIMITIVE_U16 || t == PRIMITIVE_I16 {
    return 4
  }
  if t == PRIMITIVE_U32 || t == PRIMITIVE_I32 {
    return 4
  }
  if t == PRIMITIVE_U64 || t == PRIMITIVE_I64 {
    return 8
  }
  if t == PRIMITIVE_CHAR {
    return 4
  }
  if t == PRIMITIVE_BOOL {
    return 4
  }
  if t == PRIMITIVE_CSTRING {
    return 8
  }
  if st, ok := t.(Struct); ok {
    maxSize := 0
    for _, field := range st.Fields {
      size := GetSizeOfType(field.R)
      if size > maxSize {
        maxSize = size
      }
    }
    return maxSize * len(st.Fields)
  }
  if _, ok := t.(Pointer); ok {
    return 8
  }
  return 0
}

func IsNumericType(t Type) bool {
  return t == PRIMITIVE_U8 ||
    t == PRIMITIVE_I8 ||
    t == PRIMITIVE_U16 ||
    t == PRIMITIVE_I16 ||
    t == PRIMITIVE_U32 ||
    t == PRIMITIVE_I32 ||
    t == PRIMITIVE_U64 ||
    t == PRIMITIVE_I64 ||
    t == PRIMITIVE_UNTYPED_INT
}

func IsUnsignedType(ts ...Type) bool {
  for _, t := range ts {
    if !(t == PRIMITIVE_U8 ||
      t == PRIMITIVE_U16 ||
      t == PRIMITIVE_U32 ||
      t == PRIMITIVE_U64) {
      return false
    }
  }
  return true
}

func IsSignedType(ts ...Type) bool {
  for _, t := range ts {
    if !(t == PRIMITIVE_I8 ||
      t == PRIMITIVE_I16 ||
      t == PRIMITIVE_I32 ||
      t == PRIMITIVE_I64) {
      return false
    }
  }
  return true
}

func BiggerNumericType(t1, t2 Type) Type {
  if t1 == PRIMITIVE_U64 || t2 == PRIMITIVE_U64 {
    return PRIMITIVE_U64
  }
  if t1 == PRIMITIVE_I64 || t2 == PRIMITIVE_I64 {
    return PRIMITIVE_I64
  }
  if t1 == PRIMITIVE_U32 || t2 == PRIMITIVE_U32 {
    return PRIMITIVE_U32
  }
  if t1 == PRIMITIVE_I32 || t2 == PRIMITIVE_I32 {
    return PRIMITIVE_I32
  }
  if t1 == PRIMITIVE_U16 || t2 == PRIMITIVE_U16 {
    return PRIMITIVE_U16
  }
  if t1 == PRIMITIVE_I16 || t2 == PRIMITIVE_I16 {
    return PRIMITIVE_I16
  }
  if t1 == PRIMITIVE_U8 || t2 == PRIMITIVE_U8 {
    return PRIMITIVE_U8
  }
  if t1 == PRIMITIVE_I8 || t2 == PRIMITIVE_I8 {
    return PRIMITIVE_I8
  }
  return PRIMITIVE_UNTYPED_INT
}

func CanCoerceTo(t1, t2 Type) bool {
  if t1 == PRIMITIVE_UNTYPED_INT && IsNumericType(t2) {
    return true
  }

  if t1 == t2 {
    return true
  }

  if (IsSignedType(t1, t2) || IsUnsignedType(t1, t2)) && BiggerNumericType(t1, t2) == t2 {
    return true
  }

  return false
}

func CanCastTo(t1, t2 Type) bool {
  if t1 == t2 {
    return true
  }

  if t1 == PRIMITIVE_CHAR && t2 == PRIMITIVE_U8 || t1 == PRIMITIVE_U8 && t2 == PRIMITIVE_CHAR {
    return true
  }

  if IsNumericType(t1) && IsNumericType(t2) {
    return true
  }

  return false
}

type Primitive string

const (
  PRIMITIVE_VOID Primitive = "void"

  PRIMITIVE_UNTYPED_INT Primitive = "_untyped_int"

  PRIMITIVE_U8  Primitive = "u8"
  PRIMITIVE_U16 Primitive = "u16"
  PRIMITIVE_U32 Primitive = "u32"
  PRIMITIVE_U64 Primitive = "u64"

  PRIMITIVE_I8  Primitive = "i8"
  PRIMITIVE_I16 Primitive = "i16"
  PRIMITIVE_I32 Primitive = "i32"
  PRIMITIVE_I64 Primitive = "i64"

  PRIMITIVE_BOOL Primitive = "bool"

  PRIMITIVE_CHAR    Primitive = "char"
  PRIMITIVE_CSTRING Primitive = "cstring"
)

func (pt Primitive) IsPrimitive() bool { return true }
func (pt Primitive) IsStruct() bool    { return false }
func (pt Primitive) IsPointer() bool   { return false }

type Struct struct {
  Fields []Pair[string, Type]
}

func (st Struct) IsPrimitive() bool { return false }
func (st Struct) IsStruct() bool    { return true }
func (st Struct) IsPointer() bool   { return false }

type Pointer struct {
  To    Type
  Const bool
}

func (pt Pointer) IsPrimitive() bool { return false }
func (pt Pointer) IsStruct() bool    { return false }
func (pt Pointer) IsPointer() bool   { return true }

type TypeTable map[string]Type

func NewTypeTable() TypeTable {
  tt := TypeTable{}

  tt.Define(string(PRIMITIVE_VOID), PRIMITIVE_VOID)
  tt.Define(string(PRIMITIVE_UNTYPED_INT), PRIMITIVE_UNTYPED_INT)

  tt.Define(string(PRIMITIVE_U8), PRIMITIVE_U8)
  tt.Define(string(PRIMITIVE_U16), PRIMITIVE_U16)
  tt.Define(string(PRIMITIVE_U32), PRIMITIVE_U32)
  tt.Define(string(PRIMITIVE_U64), PRIMITIVE_U64)

  tt.Define(string(PRIMITIVE_I8), PRIMITIVE_I8)
  tt.Define(string(PRIMITIVE_I16), PRIMITIVE_I16)
  tt.Define(string(PRIMITIVE_I32), PRIMITIVE_I32)
  tt.Define(string(PRIMITIVE_I64), PRIMITIVE_I64)

  tt.Define(string(PRIMITIVE_BOOL), PRIMITIVE_BOOL)

  tt.Define(string(PRIMITIVE_CHAR), PRIMITIVE_CHAR)
  tt.Define(string(PRIMITIVE_CSTRING), PRIMITIVE_CSTRING)

  tt.Define("string", Struct{Fields: []Pair[string, Type]{
    {"data", PRIMITIVE_CSTRING},
    {"len", PRIMITIVE_U64},
  }})

  return tt
}

func (tt TypeTable) Define(name string, t Type) {
  tt[name] = t
}

func (tt TypeTable) Lookup(name string) (Type, bool) {
  t, ok := tt[name]
  return t, ok
}