package scientific.matplot

import std.math.*
import std.unittest.*
import std.unittest.testmacro.*

import scientific.numbers.*
import scientific.linear.*
import scientific.stats.random.*
import scientific.stats.normal.*
import scientific.stats.summary.mean

/* Type for X, Y, Z, C: Float64 */

/* Surface */
foreign func c_surf(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

foreign func c_surf_color(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>, C: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

/* Surface with contour */
foreign func c_surfc(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

foreign func c_surfc_color(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>, C: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

/* Returns pointer to AxisType */
foreign func c_colorbar(b: Bool): CPointer<Unit>

/* Takes and returns pointer to Surface */
foreign func c_face_alpha(s: CPointer<Unit>, face_alpha: Float32): CPointer<Unit>
foreign func c_edge_color(s: CPointer<Unit>, c: CString): CPointer<Unit>
foreign func c_edge_color_f4(s: CPointer<Unit>, r: Float32, g: Float32, b: Float32, a: Float32): CPointer<Unit>
foreign func c_lighting(s: CPointer<Unit>, lighting: Bool): CPointer<Unit>
foreign func c_palette_map_at_surface(s: CPointer<Unit>, b: Bool): CPointer<Unit>
foreign func c_contour_base(s: CPointer<Unit>, b: Bool): CPointer<Unit>
foreign func c_hidden_3d(s: CPointer<Unit>, b: Bool): CPointer<Unit>

/* Obtain contour line-spec for Surface */
foreign func c_contour_line_spec(s: CPointer<Unit>): CPointer<Unit>

/* Takes and returns pointer to Line spec */
foreign func c_line_spec_color(line_spec: CPointer<Unit>, c: CString): Unit

/* Mesh */
foreign func c_mesh(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

foreign func c_mesh_color(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>, C: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

/* Mesh with contour */
foreign func c_meshc(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

foreign func c_meshc_color(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>, C: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

/* Mesh with curtain */
foreign func c_meshz(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

foreign func c_meshz_color(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>, C: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>
    
/* Waterfall */
foreign func c_waterfall(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

foreign func c_waterfall_color(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>, C: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

/* Fence */
foreign func c_fence(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

foreign func c_fence_color(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>, C: CPointer<Unit>,
    row: Int64, col: Int64): CPointer<Unit>

/* Ribbon */
foreign func c_ribbon(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>,
    row: Int64, col: Int64, width: Float64): CPointer<Unit>

foreign func c_ribbon_color(
    X: CPointer<Unit>, Y: CPointer<Unit>, Z: CPointer<Unit>, C: CPointer<Unit>,
    row: Int64, col: Int64, width: Float64): CPointer<Unit>

/* Special test function */
public func peaks(x: Float64, y: Float64): Float64 {
    return 3.0 * pow(1.0 - x, 2.0) * exp(-pow(x, 2.0) - pow(y + 1.0, 2.0)) -
           10.0 * (x / 5.0 - pow(x, 3.0) - pow(y, 5.0)) *
                exp(-pow(x, 2.0) - pow(y, 2.0)) -
           1.0 / 3.0 * exp(-pow(x + 1.0, 2.0) - pow(y, 2.0))
}

public class LineSpec {
    var ptr: CPointer<Unit> = CPointer<Unit>()

    /* Input is a pointer to matplot::line_spec */
    init(ptr: CPointer<Unit>) {
        this.ptr = ptr
    }

    public func color(c: String): Unit {
        var cstr = unsafe { LibC.mallocCString(c) }
        unsafe { c_line_spec_color(this.ptr, cstr) }
        unsafe { LibC.free(cstr) }
    }
}

public class Surface {
    var ptr: CPointer<Unit> = CPointer<Unit>()

    /* Input is a pointer to matplot::surface */
    init(ptr: CPointer<Unit>) {
        this.ptr = ptr
    }

    public func face_alpha(face_alpha: Float32): Surface {
        this.ptr = unsafe { c_face_alpha(this.ptr, face_alpha) }
        return this
    }

    public func edge_color(str: String): Surface {
        var cstr = unsafe { LibC.mallocCString(str) }
        this.ptr = unsafe { c_edge_color(this.ptr, cstr) }
        unsafe { LibC.free(cstr) }
        return this
    }

    public func edge_color(r: Float32, g: Float32, b: Float32, a: Float32): Surface {
        this.ptr = unsafe { c_edge_color_f4(this.ptr, r, g, b, a) }
        return this
    }

    public func lighting(lighting: Bool): Surface {
        this.ptr = unsafe { c_lighting(this.ptr, lighting) }
        return this
    }

    public func palette_map_at_surface(b: Bool): Surface {
        this.ptr = unsafe { c_palette_map_at_surface(this.ptr, b) }
        return this
    }

    public func contour_base(b: Bool): Surface {
        this.ptr = unsafe { c_contour_base(this.ptr, b) }
        return this
    }

    public func hidden_3d(b: Bool): Surface {
        this.ptr = unsafe { c_hidden_3d(this.ptr, b) }
        return this
    }

    public func contour_line_spec(): LineSpec {
        let handle = unsafe { c_contour_line_spec(this.ptr) }
        return LineSpec(handle)
    }
}

public func colorbar(b: Bool): AxisType {
    let handle = unsafe { c_colorbar(b) }
    return AxisType(handle)
}

public func colorbar(): AxisType {
    return colorbar(true)
}

public func surf(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape()) {
        throw IllegalArgumentException("surf: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_surf(x.ptr, y.ptr, z.ptr, row, col) }
    return Surface(handle)
}

public func surf(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>, c: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape() || x.shape() != c.shape()) {
        throw IllegalArgumentException("surf: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_surf_color(x.ptr, y.ptr, z.ptr, c.ptr, row, col) }
    return Surface(handle)
}

public func surfc(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape()) {
        throw IllegalArgumentException("surfc: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_surfc(x.ptr, y.ptr, z.ptr, row, col) }
    return Surface(handle)
}

public func surfc(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>, c: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape() || x.shape() != c.shape()) {
        throw IllegalArgumentException("surfc: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_surfc_color(x.ptr, y.ptr, z.ptr, c.ptr, row, col) }
    return Surface(handle)
}

public func mesh(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape()) {
        throw IllegalArgumentException("mesh: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_mesh(x.ptr, y.ptr, z.ptr, row, col) }
    return Surface(handle)
}

public func mesh(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>, c: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape() || x.shape() != c.shape()) {
        throw IllegalArgumentException("mesh: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_mesh_color(x.ptr, y.ptr, z.ptr, c.ptr, row, col) }
    return Surface(handle)
}

public func meshc(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape()) {
        throw IllegalArgumentException("meshc: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_meshc(x.ptr, y.ptr, z.ptr, row, col) }
    return Surface(handle)
}

public func meshc(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>, c: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape() || x.shape() != c.shape()) {
        throw IllegalArgumentException("meshc: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_meshc_color(x.ptr, y.ptr, z.ptr, c.ptr, row, col) }
    return Surface(handle)
}

public func meshz(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape()) {
        throw IllegalArgumentException("meshz: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_meshz(x.ptr, y.ptr, z.ptr, row, col) }
    return Surface(handle)
}

public func meshz(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>, c: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape() || x.shape() != c.shape()) {
        throw IllegalArgumentException("meshz: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_meshz_color(x.ptr, y.ptr, z.ptr, c.ptr, row, col) }
    return Surface(handle)
}

public func fsurf(f: (Float64, Float64) -> Float64, xmin!: Float64 = -5.0, xmax!: Float64 = 5.0,
    ymin!: Float64 = -5.0, ymax!: Float64 = 5.0, mesh_density!: Int64 = 40): Surface {
    let x = linspace(xmin, xmax, num: mesh_density)
    let y = linspace(ymin, ymax, num: mesh_density)
    let (X, Y, Z) = meshgrid(x, y, f)
    return surf(X, Y, Z)
}

public func fsurf(fx: (Float64, Float64) -> Float64, fy: (Float64, Float64) -> Float64,
    fz: (Float64, Float64) -> Float64, smin!: Float64 = -5.0, smax!: Float64 = 5.0,
    tmin!: Float64 = -5.0, tmax!: Float64 = 5.0, mesh_density!: Int64 = 40): Surface {
    let s = linspace(smin, smax, num: mesh_density)
    let t = linspace(tmin, tmax, num: mesh_density)
    let (S, T) = meshgrid(s, t)
    let X = apply2(S, T, fx)
    let Y = apply2(S, T, fy)
    let Z = apply2(S, T, fz)
    return surf(X, Y, Z)
}

public func fmesh(f: (Float64, Float64) -> Float64, xmin!: Float64 = -5.0, xmax!: Float64 = 5.0,
    ymin!: Float64 = -5.0, ymax!: Float64 = 5.0, mesh_density!: Int64 = 40): Surface {
    let x = linspace(xmin, xmax, num: mesh_density)
    let y = linspace(ymin, ymax, num: mesh_density)
    let (X, Y, Z) = meshgrid(x, y, f)
    return mesh(X, Y, Z)
}

public func fmesh(fx: (Float64, Float64) -> Float64, fy: (Float64, Float64) -> Float64,
    fz: (Float64, Float64) -> Float64, smin!: Float64 = -5.0, smax!: Float64 = 5.0,
    tmin!: Float64 = -5.0, tmax!: Float64 = 5.0, mesh_density!: Int64 = 40): Surface {
    let s = linspace(smin, smax, num: mesh_density)
    let t = linspace(tmin, tmax, num: mesh_density)
    let (S, T) = meshgrid(s, t)
    let X = apply2(S, T, fx)
    let Y = apply2(S, T, fy)
    let Z = apply2(S, T, fz)
    return mesh(X, Y, Z)
}

public func waterfall(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape()) {
        throw IllegalArgumentException("waterfall: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_waterfall(x.ptr, y.ptr, z.ptr, row, col) }
    return Surface(handle)
}

public func waterfall(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>, c: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape() || x.shape() != c.shape()) {
        throw IllegalArgumentException("waterfall: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_waterfall_color(x.ptr, y.ptr, z.ptr, c.ptr, row, col) }
    return Surface(handle)
}

public func fence(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape()) {
        throw IllegalArgumentException("fence: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_fence(x.ptr, y.ptr, z.ptr, row, col) }
    return Surface(handle)
}

public func fence(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>, c: Vector<Float64>): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape() || x.getRows() != c.size()) {
        throw IllegalArgumentException("fence: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_fence_color(x.ptr, y.ptr, z.ptr, c.ptr, row, col) }
    return Surface(handle)
}

// TODO: still has bugs
public func ribbon(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>, width!:Float64 = 0.75): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape()) {
        throw IllegalArgumentException("waterfall: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_ribbon(x.ptr, y.ptr, z.ptr, row, col, width) }
    return Surface(handle)
}

// TODO: still has bugs
public func ribbon(x: Matrix<Float64>, y: Matrix<Float64>, z: Matrix<Float64>, c: Matrix<Float64>, width!:Float64 = 0.75): Surface {
    if (x.shape() != y.shape() || x.shape() != z.shape() || x.shape() != c.shape()) {
        throw IllegalArgumentException("waterfall: dimension mismatch")
    }
    let row = x.getRows()
    let col = x.getCols()
    let handle = unsafe { c_ribbon_color(x.ptr, y.ptr, z.ptr, c.ptr, row, col, width) }
    return Surface(handle)
}

public func testSurface1() {
    let x = linspace(-5.0, 5.0, num: 40)
    let y = linspace(-5.0, 5.0, num: 40)
    let (X, Y, Z) = meshgrid(x, y, {x: Float64, y: Float64 =>
        20.0 + pow(x, 2.0) - 10.0 * cos(2.0 * Float64.getPI() * x) +
               pow(y, 2.0) - 10.0 * cos(2.0 * Float64.getPI() * y)})
    surf(X, Y, Z)
    save("./tests/imgs/surface/surface_1.svg", "svg")
    clear()
}

public func testSurface2() {
    let x = linspace(1.0, 10.0, num: 30)
    let y = linspace(1.0, 20.0, num: 30)
    let (X, Y, Z) = meshgrid(x, y, {x: Float64, y: Float64 => sin(x) + cos(y)})
    surf(X, Y, Z)
    save("./tests/imgs/surface/surface_2.svg", "svg")
    clear()
}

public func testSurface3() {
    let x = linspace(1.0, 10.0, num: 30)
    let y = linspace(1.0, 20.0, num: 30)
    let (X, Y, Z) = meshgrid(x, y, {x: Float64, y: Float64 => sin(x) + cos(y)})
    let C = apply2(X, Y, {x: Float64, y: Float64 => x * y})
    surf(X, Y, Z, C)
    colorbar()
    save("./tests/imgs/surface/surface_3.svg", "svg")
    clear()
}

public func testSurface4() {
    let x = linspace(-5.0, 5.0, num:30)
    let (X, Y, Z) = meshgrid(x, x, {x: Float64, y: Float64 => y * sin(x) - x * cos(y)})
    surf(X, Y, Z).face_alpha(0.5).edge_color("none")
    save("./tests/imgs/surface/surface_4.svg", "svg")
    clear()
}

public func testSurface5() {
    let x = linspace(1.0, 10.0, num: 30)
    let y = linspace(1.0, 20.0, num: 30)
    let (X, Y, Z) = meshgrid(x, y, {x: Float64, y: Float64 => sin(x) + cos(y)})
    surf(X, Y, Z).lighting(true)
    save("./tests/imgs/surface/surface_5.svg", "svg")
    clear()
}

public func testSurface6() {
    let x = linspace(1.0, 10.0, num: 30)
    let y = linspace(1.0, 20.0, num: 30)
    let (X, Y, Z) = meshgrid(x, y, {x: Float64, y: Float64 => sin(x) + cos(y)})
    surf(X, Y, Z).edge_color("none").lighting(true)
    save("./tests/imgs/surface/surface_6.svg", "svg")
    clear()
}

public func testSurface7() {
    let x = linspace(1.0, 10.0, num: 19)
    let y = linspace(1.0, 20.0, num: 20)
    let (X, Y, Z) = meshgrid(x, y, {x: Float64, y: Float64 => 5.0})
    surf(X, Y, Z)
    save("./tests/imgs/surface/surface_7.svg", "svg")
    clear()
}

public func testSurfaceContour1() {
    let x = linspace(-5.0, 5.0, num: 70)
    let y = linspace(-5.0, 5.0, num: 70)
    let (X, Y, Z) = meshgrid(x, y, {x: Float64, y: Float64 =>
        20.0 + pow(x, 2.0) - 10.0 * cos(2.0 * Float64.getPI() * x) +
               pow(y, 2.0) - 10.0 * cos(2.0 * Float64.getPI() * y)})
    surfc(X, Y, Z)
    save("./tests/imgs/surface_contour/surface_contour_1.svg", "svg")
    clear()
}

public func testSurfaceContour2() {
    let x = linspace(1.0, 10.0, num: 30)
    let y = linspace(1.0, 20.0, num: 30)
    let (X, Y, Z) = meshgrid(x, y, {x: Float64, y: Float64 => sin(x) + cos(y)})
    surfc(X, Y, Z)
    save("./tests/imgs/surface_contour/surface_contour_2.svg", "svg")
    clear()
}

public func testSurfaceContour3() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    let C = apply2(X, Y, {x: Float64, y: Float64 => x * y})
    surfc(X, Y, Z, C)
    colorbar()
    save("./tests/imgs/surface_contour/surface_contour_3.svg", "svg")
    clear()
}

public func testSurfaceContour4() {
    let x = linspace(-5.0, 5.0, num: 30)
    let (X, Y, Z) = meshgrid(x, x, {x: Float64, y: Float64 => y * sin(x) - x * cos(y)})
    surfc(X, Y, Z).edge_color(0.0, 1.0, 0.0, 0.0)
    colormap(0.0, 0.0, 1.0)
    save("./tests/imgs/surface_contour/surface_contour_4.svg", "svg")
    clear()
}

public func testMesh1() {
    let x = linspace(-8.0, 8.0, num: 40)
    let (X, Y) = meshgrid(x, x)
    let size = x.size()
    let Z = empty<Float64>(size, size)
    for (i in 0..size) {
        for (j in 0..size) {
            let R = sqrt(pow(X[i,j], 2.0) + pow(Y[i,j], 2.0)) + 1e-10
            Z[i,j] = sin(R) / R
        }
    }
    mesh(X, Y, Z)
    save("./tests/imgs/mesh/mesh_1.svg", "svg")
    clear()
}

public func testMesh2() {
    let x = linspace(-8.0, 8.0, num: 40)
    let (X, Y) = meshgrid(x, x)
    let size = x.size()
    let Z = empty<Float64>(size, size)
    for (i in 0..size) {
        for (j in 0..size) {
            let R = sqrt(pow(X[i,j], 2.0) + pow(Y[i,j], 2.0)) + 1e-10
            Z[i,j] = sin(R) / R
        }
    }
    let C = apply2(X, Y, {x: Float64, y: Float64 => x * y})
    mesh(X, Y, Z, C)
    save("./tests/imgs/mesh/mesh_2.svg", "svg")
    clear()
}

public func testMesh3() {
    let x = linspace(-5.0, 5.0, num: 30)
    let (X, Y, Z) = meshgrid(x, x, {x: Float64, y: Float64 => y * sin(x) - x * cos(y)})
    mesh(X, Y, Z).palette_map_at_surface(true).face_alpha(0.5)
    save("./tests/imgs/mesh/mesh_3.svg", "svg")
    clear()
}

public func testMesh4() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    mesh(X, Y, Z).hidden_3d(false)
    save("./tests/imgs/mesh/mesh_4.svg", "svg")
    clear()
}

public func testMeshContour1() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    meshc(X, Y, Z)
    save("./tests/imgs/mesh_contour/mesh_contour_1.svg", "svg")
    clear()
}

public func testMeshContour2() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    let C = apply2(X, Y, {x: Float64, y: Float64 => x * y})
    meshc(X, Y, Z, C)
    colorbar()
    save("./tests/imgs/mesh_contour/mesh_contour_2.svg", "svg")
    clear()
}

public func testMeshContour3() {
    let x = linspace(-5.0, 5.0, num: 30)
    let (X, Y, Z) = meshgrid(x, x, {x: Float64, y: Float64 => y * sin(x) - x * cos(y)})
    let s: Surface = meshc(X, Y, Z)
    s.edge_color("r")
    s.contour_line_spec().color("b")
    save("./tests/imgs/mesh_contour/mesh_contour_3.svg", "svg")
    clear()
}

public func testMeshCurtain1() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    meshz(X, Y, Z)
    save("./tests/imgs/mesh_curtain/mesh_curtain_1.svg", "svg")
    clear()
}

public func testMeshCurtain2() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    let (FX, FY) = gradient(Z, spacing_x: 6.0 / 49.0, spacing_y: 6.0 / 49.0)
    meshz(X, Y, Z, FX)
    colorbar()
    save("./tests/imgs/mesh_curtain/mesh_curtain_2.svg", "svg")
    clear()
}

public func testMeshCurtain3() {
    let x = linspace(-5.0, 5.0, num: 30)
    let (X, Y, Z) = meshgrid(x, x, {x: Float64, y: Float64 => y * sin(x) - x * cos(y)})
    meshz(X, Y, Z).edge_color("b")
    save("./tests/imgs/mesh_curtain/mesh_curtain_3.svg", "svg")
    clear()
}

public func testFunctionSurface1() {
    fsurf({x, y => sin(x) + cos(y)})
    save("./tests/imgs/function_surface/function_surface_1.svg", "svg")
    clear()
}

public func testFunctionSurface2() {
    fsurf({x, y => erf(x) + cos(y)}, xmin:-5.0, xmax:0.0, ymin:-5.0, ymax:5.0)
    hold(true)
    fsurf({x, y => sin(x) + cos(y)}, xmin:0.0, xmax:5.0, ymin:-5.0, ymax:5.0)
    hold(false)
    save("./tests/imgs/function_surface/function_surface_2.svg", "svg")
    clear()
}

public func testFunctionSurface3() {
    let r = {u: Float64, v: Float64 => 2.0 + sin(7.0 * u + 5.0 * v)}
    let fx = {u: Float64, v: Float64 => r(u, v) * cos(u) * sin(v)}
    let fy = {u: Float64, v: Float64 => r(u, v) * sin(u) * sin(v)}
    let fz = {u: Float64, v: Float64 => r(u, v) * cos(v)}
    fsurf(fx, fy, fz, smin: 0.0, smax: 2.0 * Float64.getPI(), tmin: 0.0, tmax: Float64.getPI()).lighting(true)
    save("./tests/imgs/function_surface/function_surface_3.svg", "svg")
    clear()
}

public func testFunctionSurface4() {
    fsurf({x, y => y * sin(x) - x * cos(y)}, xmin:-2.0*Float64.getPI(), xmax:2.0*Float64.getPI(), ymin:-2.0*Float64.getPI(), ymax:2.0*Float64.getPI())
    title("ysin(x) - xcos(y) for x and y in [-2π,2π]")
    xlabel("x")
    ylabel("y")
    zlabel("z")
    box(true)

    xticks(linspace(-2.0 * Float64.getPI(), 2.0 * Float64.getPI(), num: 9))
    xticklabels(["-2π", "-3π/2", "-π", "-π/2", "0", "π/2", "π", "3π/2", "2π"])
    yticks(linspace(-2.0 * Float64.getPI(), 2.0 * Float64.getPI(), num: 9))
    yticklabels(["-2π", "-3π/2", "-π", "-π/2", "0", "π/2", "π", "3π/2", "2π"])
    save("./tests/imgs/function_surface/function_surface_4.svg", "svg")
    clear()
}

public func testFunctionSurface5() {
    let fx = {u: Float64, v: Float64 => u * sin(v)}
    let fy = {u: Float64, v: Float64 => -u * cos(v)}
    let fz = {u: Float64, v: Float64 => v}
    fsurf(fx, fy, fz, smin:-5.0, smax:5.0, tmin:-5.0, tmax:-2.0).edge_color("g")
    hold(true)
    fsurf(fx, fy, fz, smin:-5.0, smax:5.0, tmin:-2.0, tmax:2.0).edge_color("none")
    hold(false)
    save("./tests/imgs/function_surface/function_surface_5.svg", "svg")
    clear()
}

public func testFunctionSurface6() {
    let fx = {u: Float64, v: Float64 => exp(-abs(u) / 10.0) * sin(5.0 * abs(v))}
    let fy = {u: Float64, v: Float64 => exp(-abs(u) / 10.0) * cos(5.0 * abs(v))}
    let fz = {u: Float64, v: Float64 => u}
    fsurf(fx, fy, fz, smin:-30.0, smax:30.0, tmin:-30.0, tmax:30.0).face_alpha(0.5)
    save("./tests/imgs/function_surface/function_surface_6.svg", "svg")
    clear()
}

public func testFunctionSurface7() {
    fsurf(peaks, xmin:-3.0, xmax:3.0, ymin:-3.0, ymax:3.0).contour_base(true)
    save("./tests/imgs/function_surface/function_surface_7.svg", "svg")
    clear()
}

public func testFunctionSurface8() {
    let fx = {s: Float64, t: Float64 => sin(s)}
    let fy = {s: Float64, t: Float64 => cos(s)}
    let fz = {s: Float64, t: Float64 => t / 10.0 * sin(1.0 / s)}
    tiledlayout(2, 1)
    nexttile()
    fsurf(fx, fy, fz, smin:-5.0, smax:5.0, tmin:-5.0, tmax:5.0, mesh_density:20)
    view(-172.0, 25.0)
    title("Decreased Mesh Density")

    nexttile()
    fsurf(fx, fy, fz, smin:-5.0, smax:5.0, tmin:-5.0, tmax:5.0, mesh_density:50)
    view(-172.0, 25.0)
    title("Increased Mesh Density")
    save("./tests/imgs/function_surface/function_surface_8.svg", "svg")
    clear()
}

public func testFunctionMesh1() {
    fmesh({x, y => sin(x) + cos(y)})
    save("./tests/imgs/function_mesh/function_mesh_1.svg", "svg")
    clear()
}

public func testFunctionMesh2() {
    let r = {u: Float64, v: Float64 => 2.0 + sin(7.0 * u + 5.0 * v)}
    let fx = {u: Float64, v: Float64 => r(u, v) * cos(u) * sin(v)}
    let fy = {u: Float64, v: Float64 => r(u, v) * sin(u) * sin(v)}
    let fz = {u: Float64, v: Float64 => r(u, v) * cos(v)}
    fmesh(fx, fy, fz, smin: 0.0, smax: 2.0 * Float64.getPI(), tmin: 0.0, tmax: Float64.getPI())
    save("./tests/imgs/function_mesh/function_mesh_2.svg", "svg")
    clear()
}

public func testFunctionMesh3() {
    fmesh({x, y => erf(x) + cos(y)}, xmin:-5.0, xmax:0.0, ymin:-5.0, ymax:5.0)
    hold(true)
    fmesh({x, y => sin(x) + cos(y)}, xmin:0.0, xmax:5.0, ymin:-5.0, ymax:5.0)
    hold(false)
    save("./tests/imgs/function_mesh/function_mesh_3.svg", "svg")
    clear()
}

public func testFunctionMesh4() {
    fmesh({x, y => sin(x) + cos(y)}).edge_color("red")
    save("./tests/imgs/function_mesh/function_mesh_4.svg", "svg")
    clear()
}

public func testWaterfall1() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    waterfall(X, Y, Z)
    save("./tests/imgs/waterfall/waterfall_1.svg", "svg")
    clear()
}

public func testWaterfall2() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    let (FX, FY) = gradient(Z, spacing_x: 6.0 / 49.0, spacing_y: 6.0 / 49.0)
    waterfall(X, Y, Z, FX)
    colorbar()
    save("./tests/imgs/waterfall/waterfall_2.svg", "svg")
    clear()
}

public func testWaterfall3() {
    let x = linspace(-5.0, 5.0, num:30)
    let (X, Y, Z) = meshgrid(x, x, {x: Float64, y: Float64 => y * sin(x) - x * cos(y)})
    waterfall(X, Y, Z).edge_color("b")
    save("./tests/imgs/waterfall/waterfall_3.svg", "svg")
    clear()
}

public func testFence1() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    fence(X, Y, Z)
    save("./tests/imgs/fence/fence_1.svg", "svg")
    clear()
}

public func testFence2() {
    let x = linspace(-3.0, 3.0, num: 50)
    let (X, Y, Z) = meshgrid(x, x, peaks)
    let row = Z.getRows()
    let c = vector<Float64>(row, {i => mean(Z[i])})
    fence(X, Y, Z, c)
    save("./tests/imgs/fence/fence_2.svg", "svg")
    clear()
}

public func testFence3() {
    let x = linspace(-5.0, 5.0, num:30)
    let (X, Y, Z) = meshgrid(x, x, {x: Float64, y: Float64 => y * sin(x) - x * cos(y)})
    fence(X, Y, Z).edge_color("b").face_alpha(0.5)
    save("./tests/imgs/fence/fence_3.svg", "svg")
    clear()
}

public func testRibbon1() {
    let x = linspace(-3.0, 3.0, num:13)
    let y = linspace(-3.0, 3.0, num:61)
    let (X, Y, Z) = meshgrid(x, y, peaks)
    ribbon(X, Y, Z)
    save("./tests/imgs/ribbon/ribbon_1.svg", "svg")
    clear()
}

public func testRibbon2() {
    let x = linspace(-3.0, 3.0, num:13)
    let y = linspace(-3.0, 3.0, num:61)
    let (X, Y, Z) = meshgrid(x, y, peaks)
    let (FX, FY) = gradient(Z, spacing_x: 6.0 / 49.0, spacing_y: 6.0 / 49.0)
    ribbon(X, Y, Z, FX, width:1.0)
    colorbar()
    save("./tests/imgs/ribbon/ribbon_2.svg", "svg")
    clear()
}

public func testRibbon3() {
    let x = linspace(-5.0, 5.0, num:30)
    let (X, Y, Z) = meshgrid(x, x, {x: Float64, y: Float64 => y * sin(x) - x * cos(y)})
    ribbon(X, Y, Z).edge_color("green").face_alpha(0.5)
    save("./tests/imgs/ribbon/ribbon_3.svg", "svg")
    clear()
}

public func testRibbon4() {
    let rastrigin = {x: Float64, y: Float64 =>
        20.0 + pow(x, 2.0) - 10.0 * cos(2.0 * Float64.getPI() * x) +
               pow(y, 2.0) - 10.0 * cos(2.0 * Float64.getPI() * y)}
    let x = linspace(-5.0, 5.0, num:11)
    let y = linspace(-5.0, 5.0, num:40)
    let (X, Y, Z) = meshgrid(x, y, rastrigin)
    ribbon(X, Y, Z).face_alpha(0.8)
    save("./tests/imgs/ribbon/ribbon_4.svg", "svg")
    clear()
}

public func testSurface() {
    testSurface1()
    testSurface2()
    testSurface3()
    testSurface4()
    testSurface5()
    testSurface6()
    testSurface7()
}

public func testSurfaceContour() {
    testSurfaceContour1()
    testSurfaceContour2()
    testSurfaceContour3()
    testSurfaceContour4()
}

public func testMesh() {
    testMesh1()
    testMesh2()
    testMesh3()
    testMesh4()
}

public func testMeshContour() {
    testMeshContour1()
    testMeshContour2()
    testMeshContour3()
}

public func testMeshCurtain() {
    testMeshCurtain1()
    testMeshCurtain2()
    testMeshCurtain3()
}

public func testFunctionSurface() {
    testFunctionSurface1()
    testFunctionSurface2()
    testFunctionSurface3()
    testFunctionSurface4()
    testFunctionSurface5()
    testFunctionSurface6()
    testFunctionSurface7()
    testFunctionSurface8()
}

public func testFunctionMesh() {
    testFunctionMesh1()
    testFunctionMesh2()
    testFunctionMesh3()
    testFunctionMesh4()
}

public func testWaterfall() {
    testWaterfall1()
    testWaterfall2()
    testWaterfall3()
}

public func testFence() {
    testFence1()
    testFence2()
    testFence3()
}

public func testRibbon() {
    testRibbon1()
    testRibbon2()
    testRibbon3()
    testRibbon4()
}