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.continuous.tCDF

/* x, y, z have type Float64 */
foreign func c_fimplicit(
    x: CPointer<Unit>, y: CPointer<Unit>, z: CPointer<Unit>,
    row: Int64, col: Int64, line_spec: CString): CPointer<Unit>

public func fplot(f: (Float64)->Float64, start: Float64, end: Float64,
                  mesh_density!:Int64 = 500, line_spec!:String = ""): Line {
    let x = linspace<Float64>(start, end, num: mesh_density)
    let y = x.apply(f)
    let handle = plot(x, y, line_spec: line_spec)
    return handle
}

public func fplot(f: (Float64)->Float64, line_spec!:String = ""): Line {
    let x = linspace<Float64>(-5.0, 5.0, num: 250)
    let y = x.apply(f)
    let handle = plot(x, y, line_spec: line_spec)
    return handle
}

public func fplot(fx: (Float64)->Float64, fy: (Float64)->Float64, start: Float64,
                  end: Float64, mesh_density!:Int64 = 500, line_spec!:String = ""): Line {
    let t = linspace<Float64>(start, end, num: mesh_density)
    let x = t.apply(fx)
    let y = t.apply(fy)

    let handle = plot(x, y, line_spec: line_spec)
    return handle
}

public func fimplicit(f: (Float64, Float64)->Float64, startx!:Float64 = -5.0, endx!:Float64 = 5.0,
                      starty!:Float64 = -5.0, endy!:Float64 = 5.0, mesh_density!:Int64 = 100,
                      line_spec!:String = ""): Line {
    let x = linspace<Float64>(startx, endx, num: mesh_density)
    let y = linspace<Float64>(starty, endy, num: mesh_density)
    let (X, Y, Z) = meshgrid(x, y, f)
    let row = X.getRows()
    let col = X.getCols()
    var cstr_line_spec = unsafe { LibC.mallocCString(line_spec) }
    let handle = unsafe { c_fimplicit(X.ptr, Y.ptr, Z.ptr, row, col, cstr_line_spec) }
    unsafe { LibC.free(cstr_line_spec) }
    return Line(handle)
}

public func testFuncPlot1() {
    fplot({t => cos(t)}, -10.0, 10.0, line_spec: "o-r").line_width(2.0)
    hold(true)
    fplot({t => sin(t)}, -10.0, 10.0, line_spec: "x--b").line_width(2.0)
    hold(false)
    save("./tests/imgs/func_plot/func_plot1.svg", "svg")
    clear()
}

public func testFuncPlot2() {
    fplot({t => cos(3.0 * t)}, {t => sin(2.0 * t)}, -10.0, 10.0)
    save("./tests/imgs/func_plot/func_plot2.svg", "svg")
    clear()
}

public func testFuncPlot3() {
    fplot({t => exp(t)}, -3.0, 0.0, line_spec: "b")
    hold(true)
    fplot(cos, 0.0, 3.0, line_spec: "b")
    hold(false)
    grid(true)
    save("./tests/imgs/func_plot/func_plot3.svg", "svg")
    clear()
}

public func testFuncPlot4() {
    let line1 = fplot({x => sin(x + Float64.getPI() / 5.0)})
    line1.line_width(2.0)
    hold(true)
    fplot({x => sin(x - Float64.getPI() / 5.0)}, line_spec: "--or")
    fplot({x => sin(x)}, line_spec: "-.*c")
    save("./tests/imgs/func_plot/func_plot4.svg", "svg")
    hold(false)
    clear()
}

public func testFuncPlot5() {
    let line = fplot({x => sin(x)})
    line.line_style(":")
    line.color("r")
    line.marker("x")
    line.marker_color("b")
    save("./tests/imgs/func_plot/func_plot5.svg", "svg")
    clear()
}

public func testFuncPlot6() {
    fplot({x => sin(x)}, -2.0 * Float64.getPI(), 2.0 * Float64.getPI())
    grid(true)
    title("sin(x) from -2π to 2π")
    xlabel("x")
    ylabel("y")
    xticks(linspace(-2.0 * Float64.getPI(), 2.0 * Float64.getPI(), num: 9))
    xticklabels(["-2π", "-3π/2", "-π", "-π/2", "0", "π/2", "π", "3π/2", "2π"])
    save("./tests/imgs/func_plot/func_plot6.svg", "svg")
    clear()
}

public func testFuntionPlot(){
    testFuncPlot1()
    testFuncPlot2()
    testFuncPlot3()
    testFuncPlot4()
    testFuncPlot5()
    testFuncPlot6()
}

public func testFImplicitPlot1() {
    fimplicit({x, y => pow(x, 2.0) - pow(y, 2.0) - 1.0})
    save("./tests/imgs/fimplicit_plot/fimplicit_plot1.svg", "svg")
    clear()
}

public func testFImplicitPlot2() {
    fimplicit({x, y => pow(x, 2.0) + pow(y, 2.0) - 3.0},
        startx: -3.0, endx: 0.0, starty: -2.0, endy: 2.0)
    save("./tests/imgs/fimplicit_plot/fimplicit_plot2.svg", "svg")
    clear()    
}

public func testFImplicitPlot3() {
    fimplicit({x, y => pow(x, 2.0) + pow(y, 2.0) - 1.0})
    hold(true)
    fimplicit({x, y => pow(x, 2.0) + pow(y, 2.0) - 2.0}, line_spec: "--g")
    .line_width(2.0)
    hold(false)
    save("./tests/imgs/fimplicit_plot/fimplicit_plot3.svg", "svg")
    clear()    
}

public func testFImplicitPlot4() {
    fimplicit({x, y => y * sin(x) + x * cos(y)})
    .color("r").line_style("--").line_width(2.0)
    save("./tests/imgs/fimplicit_plot/fimplicit_plot4.svg", "svg")
    clear()    
}

public func testFImplicitPlot() {
    testFImplicitPlot1()
    testFImplicitPlot2()
    testFImplicitPlot3()
    testFImplicitPlot4()
}

public func testTCDF() {
    fplot({x => tCDF(x, 5)})
    save("./tests/imgs/func_plot/tcdf.svg", "svg")
    clear()
}