module TypeChecker

open Ast
open Getters
open AstUtils
open Printer
open System.Collections.Generic

let GetClass name decls =
  match decls |> List.tryFind (function Interface(n,_,_) when n = name -> true | _ -> false) with
  | Some(cl) -> cl
  | None -> Interface(name,[],[])

let GetModel name decls =
  match decls |> List.tryFind (function DataModel(n,_,_,_,_) when n = name -> true | _ -> false) with
  | Some(m) -> m
  | None -> DataModel(name,[],[],[],BoolLiteral(true))

let GetCode name decls =
  match decls |> List.tryFind (function Code(n,_) when n = name -> true | _ -> false) with
  | Some(c) -> c
  | None -> Code(name,[])

let IsUserType prog tpo =
  match tpo with
  | Some(tp) ->
      let tpname = match tp with
                   | NamedType(tname,_) -> tname
                   | InstantiatedType(tname, _) -> tname
                   | _ -> ""
      match prog with
      | Program(components) -> components |> List.filter (function Component(Interface(name,_,_),_,_) when name = tpname -> true
                                                                   | _                                               -> false) |> List.isEmpty |> not
  | None -> false

let TypeCheck prog =
  match prog with
  | SProgram(decls) ->
      let componentNames = decls |> List.choose (function Interface(name,_,_) -> Some(name) | _ -> None)
      let clist = componentNames |> List.map (fun name -> Component(GetClass name decls, GetModel name decls, GetCode name decls))
      Some(Program(clist))

let MethodArgChecker prog meth varName =
  let ins = GetMethodInArgs meth
  let outs = GetMethodOutArgs meth
  ins @ outs |> List.choose (fun var -> if GetVarName var = varName then GetVarType var |> FindComponentForTypeOpt prog else None) |> Utils.ListToOption

// TODO: implement this
let rec InferType prog thisComp checkLocalFunc expr =
  let __FindVar comp fldName =
    let var = FindVar comp fldName |> Utils.ExtractOption
    let c = FindComponentForType prog (Utils.ExtractOption (GetVarType var)) |> Utils.ExtractOption
    Some(c)

  try
    match expr with
    | ObjLiteral("this") -> Some(thisComp)
    | ObjLiteral("null") -> None
    | IdLiteral(id) -> __FindVar thisComp id
    | VarLiteral(id) -> checkLocalFunc id
    | Dot(discr, fldName) ->
        match InferType prog thisComp checkLocalFunc discr with
        | Some(comp) -> __FindVar comp fldName
        | None -> None
    | _ -> None
  with
  | ex -> None