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.*

/* Type for latitude, longitude, sizes and colors: Float64 */
foreign func c_geoplot_empty(): CPointer<Unit>
foreign func c_geoplot(latitude: CPointer<Unit>, x_len: Int64, longitude: CPointer<Unit>, y_len: Int64, line_spec: CString): CPointer<Unit>
foreign func c_geobubble(latitude: CPointer<Unit>, longitude: CPointer<Unit>, sizes: CPointer<Unit>, len: Int64): CPointer<Unit>
foreign func c_geobubble_colors(latitude: CPointer<Unit>, longitude: CPointer<Unit>, sizes: CPointer<Unit>, colors: CPointer<Unit>, len: Int64): CPointer<Unit>
foreign func c_geodensityplot(latitude: CPointer<Unit>, longitude: CPointer<Unit>, len: Int64): CPointer<Unit>
foreign func c_geoscatter(latitude: CPointer<Unit>, longitude: CPointer<Unit>, sizes: CPointer<Unit>, colors: CPointer<Unit>, len: Int64): CPointer<Unit>
foreign func c_geolimits(latitude_min: Float64, latitude_max: Float64, longitude_min: Float64, longitude_max: Float64): Unit

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

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

public func geoplot(): Line {
    let handle = unsafe { c_geoplot_empty() }
    return Line(handle)
}

public func geoplot(latitude: Vector<Float64>, longitude: Vector<Float64>, line_spec!: String = ""): Line {
    let size = latitude.size()
    var cstr_line_spec = unsafe { LibC.mallocCString(line_spec) }
    let handle = unsafe { c_geoplot(latitude.ptr, size, longitude.ptr, size, cstr_line_spec) }
    unsafe { LibC.free(cstr_line_spec) }
    return Line(handle)
}

public func geobubble(latitude: Vector<Float64>, longitude: Vector<Float64>,
                      sizes: Vector<Float64>): Circles {
    let len = latitude.size()
    let handle = unsafe { c_geobubble(latitude.ptr, longitude.ptr, sizes.ptr, len) }
    return Circles(handle)
}

public func geobubble(latitude: Vector<Float64>, longitude: Vector<Float64>,
                      sizes: Vector<Float64>, colors: Vector<Float64>): Circles {
    let len = latitude.size()
    let handle = unsafe { c_geobubble_colors(latitude.ptr, longitude.ptr, sizes.ptr, colors.ptr, len) }
    return Circles(handle)
}

public func geodensityplot(latitude: Vector<Float64>, longitude: Vector<Float64>): Line {
    let len = latitude.size()
    let handle = unsafe { c_geodensityplot(latitude.ptr, longitude.ptr, len) }
    return Line(handle)
}

public func geoscatter(latitude: Vector<Float64>, longitude: Vector<Float64>,
                       sizes: Vector<Float64>, colors: Vector<Float64>): Line {
    let len = latitude.size()
    let handle = unsafe { c_geoscatter(latitude.ptr, longitude.ptr, sizes.ptr, colors.ptr, len )}
    return Line(handle)
}

public func geolimits(latitude_min: Float64, latitude_max: Float64, longitude_min: Float64,
                      longitude_max: Float64): Unit {
    unsafe { c_geolimits(latitude_min, latitude_max, longitude_min, longitude_max); }
}

func testGeoPlot1(): Unit {
    let lat_seattle = 47.62
    let lon_seattle = -122.33
    let lat_anchorage = 61.20
    let lon_anchorage = -149.9
    let lat = vector<Float64>([lat_seattle, lat_anchorage])
    let lon = vector<Float64>([lon_seattle, lon_anchorage])
    geoplot(lat, lon, line_spec: "g-*")
    geolimits(45.0, 62.0, -155.0, -120.0)
    save("./tests/imgs/geography/geo_plot1.svg", "svg")
    clear()
}

func testGeoPlot2(): Unit {
    let lat_seattle = 47.62
    let lon_seattle = -122.33
    let lat_anchorage = 61.20
    let lon_anchorage = -149.9
    let lat = vector<Float64>([lat_seattle, lat_anchorage])
    let lon = vector<Float64>([lon_seattle, lon_anchorage])
    geoplot(lat, lon, line_spec: "g-*")
    geolimits(45.0, 62.0, -155.0, -120.0)

    text(lon_anchorage, lat_anchorage, "Anchorage")
    text(lon_seattle, lat_seattle, "Seattle")
    save("./tests/imgs/geography/geo_plot2.svg", "svg")
    clear()
}

func testGeoPlot4(): Unit {
    let lat_seattle = 47.62
    let lon_seattle = -122.33
    let lat_anchorage = 61.20
    let lon_anchorage = -149.9
    let lat_pt_barrow = 71.38
    let lon_pt_barrow = -156.47
    geoplot(vector([lat_seattle, lat_anchorage]),
            vector([lon_seattle, lon_anchorage]), line_spec: "y-")
    hold(true)
    geoplot(vector([lat_seattle, lat_pt_barrow]),
            vector([lon_seattle, lon_pt_barrow]), line_spec: "b:")
    geolimits(44.0, 75.0, -170.0, -100.0)
    text(lon_anchorage, lat_anchorage, "Anchorage")
    text(lon_seattle, lat_seattle, "Seattle")
    text(lon_pt_barrow, lat_pt_barrow, "Point Barrow")
    save("./tests/imgs/geography/geo_plot4.png", "png")
    clear()
}

func testGeoPlot5(): Unit {
    let lat_seattle = 47.62
    let lon_seattle = -122.33
    let lat_anchorage = 61.20
    let lon_anchorage = -149.9
    let g = geoplot(vector([lat_seattle, lat_anchorage]),
                    vector([lon_seattle, lon_anchorage]))
    g.line_width(2.0)
    hold(true)
    geolimits(44.0, 75.0, -170.0, -100.0)
    text(lon_anchorage, lat_anchorage, "Anchorage")
    text(lon_seattle, lat_seattle, "Seattle")
    geoplot().color(0.0, 0.71, 0.65, 0.59)
    gca().color(0.0, 0.4, 0.61, 0.76)
    save("./tests/imgs/geography/geo_plot5.png", "png")
    clear()
}

let tsunami_lon = vector<Float64>([
    128.3,    -156.0,   157.95,  143.85,   -155.0,   -82.4,    159.5,
    156.5,    147.4,    178.2,   141.7,    -86.883,  -32.283,  -72.0,
    23.0,     -175.629, 137.0,   -99.0,    -79.5,    104.0,    -136.52,
    148.54,   148.8,    159.8,   156.0,    -111.215, -72.75,   143.4,
    143.7,    -74.5,    -80.7,   147.9,    168.5,    160.5,    142.8,
    22.05,    149.54,   150.1,   -176.6,   -147.5,   139.2,    178.55,
    22.4,     167.2,    167.6,   160.8,    -78.8,    -70.6,    166.5,
    164.8,    166.8,    97.3,    -79.8,    24.9,     132.5,    143.2,
    143.1,    119.8,    -92.6,   118.9,    147.9,    163.6,    121.7,
    -78.8,    145.5,    -71.2,   153.9,    153.2,    141.2,    146.6,
    147.0,    -103.0,   156.6,   145.8,    155.9,    155.6,    -77.79,
    -128.633, -17.649,  147.734, 155.054,  125.993,  -155.024, 124.023,
    118.464,  -78.52,   142.03,  -96.591,  -101.276, 136.04,   7.25,
    -79.358,  -122.18,  142.361, 127.924,  139.102,  139.099,  72.11,
    -71.871,  114.185,  152.828, -102.533, -101.647, -174.776, 147.689,
    14.97,    121.013,  -83.073, -124.316, -87.34,   121.896,  139.197,
    144.801,  127.733,  112.835, 112.892,  133.366,  152.214,  147.321,
    127.98,   -135.3,   121.067, 143.419,  125.127,  -70.294,  -104.205,
    130.175,  130.148,  149.3,   119.931,  22.083,   159.318,  136.952,
    -79.587,  131.468,  166.676, 162.035,  162.035,  -62.18,   141.926,
    124.891,  -62.18,   29.864,  168.214,  120.15,   123.573,  -120.65,
    152.169,  -73.641,  167.856, 124.249,  142.945,  134.297,  3.634,
    -62.18,   143.91,   95.982,  97.108,   127.214,  -62.18,   107.411,
    153.266
])

let tsunami_lat = vector<Float64>([
    -3.8,   19.5,   -9.02,   42.15,   19.1,    43.1,    52.75,  50.0,
    -2.4,   -18.3,  34.0,    41.7,    67.8,    -30.0,   39.5,   51.292,
    -1.5,   16.5,   1.2,     -4.5,    58.34,   44.53,   44.2,   53.4,
    -7.5,   44.712, -15.75,  39.8,    39.4,    -39.5,   -6.8,   43.2,
    -18.5,  -9.9,   38.0,    38.42,   44.81,   44.1,    -24.8,  61.1,
    38.65,  51.29,  38.4,    -15.8,   -15.9,   -10.3,   -10.7,  -25.5,
    -11.8,  -11.3,  -11.8,   5.5,     -10.6,   39.4,    32.3,   40.8,
    39.4,   0.2,    15.6,    -3.1,    43.6,    57.7,    15.8,   -9.2,
    -4.9,   -32.5,  -5.5,    -4.9,    46.5,    -6.5,    -6.7,   18.48,
    50.5,   43.2,   -7.5,    -7.4,    -12.27,  54.083,  35.997, 43.024,
    -6.59,  12.54,  19.334,  6.262,   -11.085, -10.233, 38.19,  16.01,
    17.813, -1.679, 43.7,    1.598,   46.2,    42.158,  -4.056, 40.462,
    41.346, -6.852, -33.135, -9.245,  -4.439,  18.19,   17.802, 51.52,
    -6.088, 38.41,  18.606,  9.685,   40.368,  11.742,  -8.48,  42.851,
    12.982, 1.015,  -10.477, -10.362, -10.777, -4.238,  43.773, -1.258,
    59.5,   13.525, 40.525,  -8.378,  -23.34,  19.055,  27.929, 28.094,
    44.663, 0.729,  38.367,  54.45,   -0.891,  -9.593,  31.885, -12.584,
    54.841, 54.841, 16.72,   -2.961,  -2.071,  16.722,  40.748, -16.423,
    5.1,    -1.105, 34.5,    -3.98,   -16.265, -17.6,   6.033,  -3.302,
    -1.757, 36.964, 16.722,  41.815,  3.295,   2.085,   -3.595, 16.722,
    -9.254, 46.592
])

let tsunami_height = vector<Float64>([
    2.8,   3.6, 6.0,  6.5,   1.0, 1.52, 18.0,1.5, 1.4,    3.0, 3.0,  3.0,
    18.28, 1.0, 1.2,  15.24, 1.8, 2.5,  1.0, 1.0, 524.26, 5.0, 1.0,  2.0,
    2.4,   1.0, 5.7,  1.0,   1.5, 25.0, 9.0, 1.0, 1.5,    1.0, 1.0,  3.0,
    4.5,   15.0,1.0,  67.0,  5.8, 10.7, 3.0, 7.0, 2.0,    1.4, 3.0,  1.0,
    2.0,   1.5, 2.0,  2.0,   2.0, 1.2,  2.4, 6.0, 3.0,    10.0,2.0,  4.0,
    5.0,   15.0,2.0,  1.8,   3.0, 1.2,  3.0, 3.0, 2.0,    1.8, 1.5,  1.16,
    1.5,   4.5, 1.5,  4.5,   1.8, 8.2,  2.1, 5.5, 2.0,    4.0, 14.3, 4.48,
    15.0,  1.2, 1.0,  1.5,   1.3, 2.0,  10.0,6.0, 250.0,  1.3, 3.0,  14.5,
    1.0,   1.5, 3.0,  2.0,   1.3, 3.0,  2.5, 1.4, 1.5,    5.5, 1.03, 3.0,
    1.8,   10.0,26.2, 31.7,  2.1, 2.0,  13.0,3.7, 3.0,    1.2, 11.0, 3.0,
    7.62,  7.3, 1.1,  4.0,   3.0, 5.1,  2.6, 1.5, 1.1,    3.4, 2.0,  30.0,
    7.7,   5.1, 1.1,  3.0,   1.5, 8.0,  3.0, 15.0,2.75,   2.0, 2.52, 6.0,
    20.0,  6.0, 7.0,  1.0,   7.0, 3.0,  3.0, 4.0, 5.0,    2.0, 4.0,  4.0,
    50.0,  3.0, 3.5,  1.0,   10.0,1.8
])

let tsunami_cause_ints = vector<Int64>([
    1, 1, 6, 1, 1, 4, 1, 1, 6, 2, 1, 4, 5, 1, 1, 1, 1, 1, 1, 1, 2,
    1, 1, 1, 5, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 2, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 2, 1,
    1, 1, 1, 1, 1, 1, 3, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 3, 1, 1, 2, 1, 1,
    1, 2, 1, 1, 3, 6, 1, 1, 1, 1, 1, 3, 7, 2, 1, 6, 1, 1, 4, 1, 3,
    1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 6, 1, 1
])

func testGeoBubble1(): Unit {
    let log_tsunami_height = tsunami_height.apply({x => log(x + 2.0)})
    geobubble(tsunami_lat, tsunami_lon, log_tsunami_height)
    save("./tests/imgs/geography/geobubble1.png", "png")
    clear()
}

func testGeoBubble2(): Unit {
    let log_tsunami_height = tsunami_height.apply({x => log(x + 2.0)})
    let tsunami_cause = empty<Float64>(tsunami_cause_ints.size())
    for (i in 0..tsunami_cause_ints.size()) {
        if (tsunami_cause_ints[i] > 0) {
            tsunami_cause[i] = Float64(tsunami_cause_ints[i])
        } else {
            tsunami_cause[i] = Float64.NaN
        }
    }
    geobubble(tsunami_lat, tsunami_lon, log_tsunami_height, tsunami_cause)
    save("./tests/imgs/geography/geobubble2.png", "png")
    clear()
}

func testGeodensityplot1(): Unit {
    let r: Random = Random(0)
    let data = linspace(-170.0, 170.0, num:3000)
    let lon = empty<Float64>(data.size())
    for (i in 0..data.size()) {
        lon[i] = data[i] + 10.0 * r.nextFloat64()
    }
    let lat = empty<Float64>(data.size())
    for (i in 0..data.size()) {
        lat[i] = 50.0 * cos(3.0 * lon[i] / 180.0 * Float64.getPI()) + 10.0 * r.nextFloat64()
    }
    geodensityplot(lat, lon)
    save("./tests/imgs/geography/geodensity1.png", "png")
    clear()
}

func testGeoscatter1(): Unit {
    let lon = linspace(-170.0, 170.0, num:35)
    let lat = lon.apply({x: Float64 => 50.0 * cos(3.0 * x / 180.0 * Float64.getPI())})
    let A = lon.apply({x: Float64 => (101.0 + 100.0 * sin(2.0 * x / 180.0 * Float64.getPI())) / 7.0})
    let C = lon.apply({x: Float64 => cos(4.0 * x / 180.0 * Float64.getPI())})
    geoscatter(lat, lon, A, C).marker(MarkerStyle.UpwardPointingTriangle)
    save("./tests/imgs/geography/geoscatter1.png", "png")
    clear()
}

func testGeoscatter2(): Unit {
    let lon = linspace(-170.0, 170.0, num:35)
    let lat = lon.apply({x: Float64 => 50.0 * cos(3.0 * x / 180.0 * Float64.getPI())})
    let A = lon.apply({x: Float64 => (101.0 + 100.0 * sin(2.0 * x / 180.0 * Float64.getPI())) / 7.0})
    let C = lon.apply({x: Float64 => cos(4.0 * x / 180.0 * Float64.getPI())})
    geoscatter(lat, lon, A, C).marker(MarkerStyle.UpwardPointingTriangle)
    geoplot().color(0.0, 0.71, 0.65, 0.59)
    gca().color(0.0, 0.4, 0.61, 0.76)
    save("./tests/imgs/geography/geoscatter2.png", "png")
    clear()
}

public func testGeoPlot() {
    print("  + testGeoPlot\n")
    // testGeoPlot1()
    // testGeoPlot2()
    testGeoPlot4()
    testGeoPlot5()
    // testGeoBubble1()
    // testGeoBubble2()
    // testGeodensityplot1()
    // testGeoscatter1()
    // testGeoscatter2()
}