import std/[unittest, strutils]
import ../bootstrap/[lexer, parser, sema]

proc checkSource(source: string): tuple[hasErrors: bool, diagnostics: seq[SemaDiagnostic]] =
  let lexRes = lexer.tokenize(source, "test.bux")
  if lexRes.hasErrors:
    return (true, @[])
  let parseRes = parser.parse(lexRes.tokens, "test.bux")
  if parseRes.diagnostics.len > 0:
    return (true, @[])
  let (semaRes, _) = sema.analyzeFull(parseRes.module)
  return (semaRes.hasErrors, semaRes.diagnostics)

suite "Borrow Checker":
  test "@[Checked] function with &mut allows mutation":
    let res = checkSource("""
@[Checked]
func Inc(p: &mut int) {
  *p = *p + 1;
}
@[Checked]
func Main() -> int {
  var x: int = 5;
  Inc(&x);
  return x;
}
""")
    check(not res.hasErrors)
  test "@[Checked] function rejects write through &T":
    let res = checkSource("""
@[Checked]
func BadWrite(p: &int) {
  *p = 42;
}
@[Checked]
func Main() -> int {
  var x: int = 5;
  BadWrite(&x);
  return x;
}
""")
    check(res.hasErrors)

  test "unchecked function allows write through raw pointer":
    let res = checkSource("""
func RawWrite(p: *int) {
  *p = 42;
}
func Main() -> int {
  var x: int = 5;
  RawWrite(&x);
  return x;
}
""")
    check(not res.hasErrors)
  test "&T allows reading":
    let res = checkSource("""
@[Checked]
func Get(p: &int) -> int {
  return *p;
}
@[Checked]
func Main() -> int {
  var x: int = 5;
  return Get(&x);
}
""")
    check(not res.hasErrors)
  test "own T type parses and resolves":
    let res = checkSource("""
struct Box {
  value: int;
}
func TakeOwn(b: own Box) -> int {
  return b.value;
}
func Main() -> int {
  var b: Box = Box { value: 42 };
  return TakeOwn(b);
}
""")
    check(not res.hasErrors)
  test "@[Checked] rejects double mutable borrow in call":
    let res = checkSource("""
@[Checked]
func Swap(a: &mut int, b: &mut int) {
  let tmp = *a;
  *a = *b;
  *b = tmp;
}
@[Checked]
func Main() -> int {
  var x: int = 10;
  Swap(&x, &x);
  return 0;
}
""")
    check(res.hasErrors)
    check(res.diagnostics[0].message.contains("mutable borrow"))

  test "@[Checked] rejects use after move":
    let res = checkSource("""
struct Box {
  value: int;
}
@[Checked]
func Consume(b: own Box) -> int {
  return b.value;
}
@[Checked]
func Main() -> int {
  var b: Box = Box { value: 42 };
  let x: int = Consume(b);
  return b.value;
}
""")
    check(res.hasErrors)
    check(res.diagnostics[0].message.contains("moved"))

  test "@[Checked] allows reinitialization after move":
    let res = checkSource("""
struct Box {
  value: int;
}
@[Checked]
func Take(b: own Box) -> int {
  return b.value;
}
@[Checked]
func Main() -> int {
  var b: own Box = Box { value: 1 };
  Take(b);
  b = Box { value: 2 };
  return b.value;
}
""")
    check(not res.hasErrors)
  test "@[Checked] move in assignment":
    let res = checkSource("""
struct Box {
  value: int;
}
@[Checked]
func Main() -> int {
  var a: own Box = Box { value: 1 };
  var b: own Box = a;
  return a.value;
}
""")
    check(res.hasErrors)
    check(res.diagnostics[0].message.contains("moved"))

  test "@[Checked] move in return":
    let res = checkSource("""
struct Box {
  value: int;
}
@[Checked]
func Give() -> own Box {
  var x: own Box = Box { value: 42 };
  return x;
}
@[Checked]
func Main() -> int {
  let b: own Box = Give();
  return b.value;
}
""")
    check(not res.hasErrors)

  test "borrow &mut expr parses and type-checks":
    let res = checkSource("""
struct Point {
  x: int;
  y: int;
}
func Main() -> int {
  var p: Point;
  p.x = 10;
  let r: &mut Point = borrow &mut p;
  return 0;
}
""")
    check(not res.hasErrors)

  test "@[Shared] attribute parses without errors":
    let res = checkSource("""
@[Shared]
func SharedFunc() -> int {
  return 42;
}
func Main() -> int {
  return SharedFunc();
}
""")
    check(not res.hasErrors)

  test "@[Checked] rejects two let-bound &mut of same var":
    let res = checkSource("""
@[Checked]
func Main() -> int {
  var x: int = 1;
  let a: &mut int = &x;
  let b: &mut int = &x;
  return 0;
}
""")
    check(res.hasErrors)
    check(res.diagnostics[0].message.contains("already mutably borrowed"))

  test "@[Checked] rejects assign while mutably borrowed":
    let res = checkSource("""
@[Checked]
func Main() -> int {
  var x: int = 1;
  let a: &mut int = &x;
  x = 2;
  return *a;
}
""")
    check(res.hasErrors)
    check(res.diagnostics[0].message.contains("mutably borrowed"))

  test "@[Checked] rejects shared borrow while mutably borrowed":
    let res = checkSource("""
@[Checked]
func Main() -> int {
  var x: int = 1;
  let a: &mut int = &x;
  let b: &int = &x;
  return 0;
}
""")
    check(res.hasErrors)
    check(res.diagnostics[0].message.contains("shared-borrow") or
          res.diagnostics[0].message.contains("mutably borrowed"))

  test "borrow & expr with shared ref":
    let res = checkSource("""
struct Point {
  x: int;
  y: int;
}
func Main() -> int {
  var p: Point;
  p.x = 10;
  let r: &Point = borrow &p;
  let val: int = (*r).x;
  return val;
}
""")
    check(not res.hasErrors)