//! returns: 0
//! Test passing trait objects as function parameters.
//!
//! Exercises: trait objects passed to non-method functions, including
//! both mutable and immutable trait object pointers as arguments.

record Circle {
    radius: i32,
}

record Square {
    side: i32,
}

trait Shape {
    fn (*Shape) area() -> i32;
}

trait Scalable {
    fn (*mut Scalable) scale(factor: i32);
}

instance Shape for Circle {
    fn (c: *Circle) area() -> i32 {
        return c.radius * c.radius * 3;
    }
}

instance Shape for Square {
    fn (s: *Square) area() -> i32 {
        return s.side * s.side;
    }
}

instance Scalable for Circle {
    fn (c: *mut Circle) scale(factor: i32) {
        c.radius = c.radius * factor;
    }
}

/// Accept an immutable trait object parameter.
fn getArea(s: *opaque Shape) -> i32 {
    return s.area();
}

/// Accept a mutable trait object parameter.
fn doubleSize(s: *mut opaque Scalable) {
    s.scale(2);
}

/// Accept two trait object parameters.
fn totalArea(a: *opaque Shape, b: *opaque Shape) -> i32 {
    return a.area() + b.area();
}

@default fn main() -> i32 {
    let c = Circle { radius: 5 };
    let s = Square { side: 4 };

    // Pass immutable trait objects to function.
    let cs: *opaque Shape = &c;
    let ca = getArea(cs);
    assert ca == 75;

    let ss: *opaque Shape = &s;
    let sa = getArea(ss);
    assert sa == 16;

    // Pass two different trait objects to same function.
    let total = totalArea(cs, ss);
    assert total == 91;

    // Pass mutable trait object to function.
    let mut c2 = Circle { radius: 3 };
    let sc: *mut opaque Scalable = &mut c2;
    doubleSize(sc);
    assert c2.radius == 6;
    return 0;
}
