"""Tests for the `ArraySystemDesign` class."""

__author__ = "Rob Hammond"
__copyright__ = "Copyright 2020, National Renewable Energy Laboratory"
__maintainer__ = "Rob Hammond"
__email__ = "robert.hammond@nrel.gov"


from copy import deepcopy

import numpy as np
import pytest

from wisdem.orbit.core.library import extract_library_specs
from wisdem.orbit.phases.design import ArraySystemDesign, CustomArraySystemDesign
from wisdem.orbit.core.exceptions import LibraryItemNotFoundError

config_full_ring = extract_library_specs("config", "array_design_full_ring")

config_partial_ring = deepcopy(config_full_ring)
config_partial_ring["plant"]["num_turbines"] = 49

config_full_grid = deepcopy(config_full_ring)
config_full_grid["plant"]["layout"] = "grid"

config_partial_grid = deepcopy(config_full_grid)
config_partial_grid["plant"]["num_turbines"] = 49

config_cables_from_file_fail = deepcopy(config_full_grid)
config_cables_from_file_fail["array_system_design"]["cables"] = "Cable1"

config_custom_base = deepcopy(config_full_grid)
config_custom_base["plant"]["num_turbines"] = 8
config_custom_base["plant"]["layout"] = "custom"

config_incorrect_turbines = deepcopy(config_custom_base)
config_incorrect_turbines["plant"]["num_turbines"] = 11
config_incorrect_turbines["array_system_design"]["location_data"] = "passes"

config_missing_col = deepcopy(config_custom_base)
config_missing_col["array_system_design"]["location_data"] = "missing_columns"

config_incomplete_optional = deepcopy(config_custom_base)
config_incomplete_optional["array_system_design"]["location_data"] = "incomplete_optional"

config_incomplete_required = deepcopy(config_custom_base)
config_incomplete_required["array_system_design"]["location_data"] = "incomplete_required"

config_duplicate_coordinates = deepcopy(config_custom_base)
config_duplicate_coordinates["array_system_design"]["location_data"] = "duplicate_coordinates"


def test_array_system_creation():
    array = ArraySystemDesign(config_full_grid)
    array.run()
    assert array


def test_cable_not_found():
    array = ArraySystemDesign(config_cables_from_file_fail)
    with pytest.raises(LibraryItemNotFoundError):
        array.run()


@pytest.mark.parametrize(
    "config,num_full_strings,num_partial_strings,num_turbines_full_string,num_turbines_partial_string",
    (
        (config_full_ring, 10, 0, 4, 0),
        (config_partial_ring, 12, 1, 4, 1),
        (config_full_grid, 10, 0, 4, 0),
        (config_partial_grid, 12, 1, 4, 1),
    ),
    ids=["full_ring", "partial_ring", "full_grid", "partial_grid"],
)
def test_string_creation(
    config,
    num_full_strings,
    num_partial_strings,
    num_turbines_full_string,
    num_turbines_partial_string,
):
    array = ArraySystemDesign(config)
    array.run()

    assert array

    string_properties = (
        (num_full_strings, "num_full_strings"),
        (num_partial_strings, "num_partial_strings"),
        (num_turbines_full_string, "num_turbines_full_string"),
        (num_turbines_partial_string, "num_turbines_partial_string"),
    )
    for val, name in string_properties:
        assert val == array.__getattribute__(name)


@pytest.mark.parametrize(
    "turbine_rating,expected",
    ((1, 40), (2, 20), (4.5, 8), (6, 6), (8.5, 4), (12, 3)),
)
def test_max_turbines_per_cable(turbine_rating, expected):
    config = deepcopy(config_full_grid)
    config["array_system_design"]["cables"] = "XLPE_630mm_33kV"
    config["turbine"]["turbine_rating"] = turbine_rating
    array = ArraySystemDesign(config)
    array.run()
    assert array.cables["XLPE_630mm_33kV"].max_turbines == expected


@pytest.mark.parametrize(
    "config,shape,num_null",
    (
        (config_full_ring, (10, 4), 0),
        (config_partial_ring, (13, 4), 3),
        (config_full_grid, (10, 4), 0),
        (config_partial_grid, (13, 4), 3),
    ),
    ids=["full_ring", "partial_ring", "full_grid", "partial_grid"],
)
def test_grid_creation(config, shape, num_null):
    array = ArraySystemDesign(config)
    array.run()

    assert array.turbines_x.shape == shape
    assert array.turbines_y.shape == shape
    assert np.isnan(array.turbines_x).sum() == num_null
    assert np.isnan(array.turbines_y).sum() == num_null

    assert isinstance(array.oss_x, float)
    assert isinstance(array.oss_y, float)

    shape = (shape[0], shape[1] + 1, 2)
    assert array.coordinates.shape == shape
    assert np.isnan(array.coordinates).sum() == 2 * num_null


@pytest.mark.parametrize(
    "config,total_length",
    (
        (config_full_ring, 34.7),
        (config_partial_ring, 42.68),
        (config_full_grid, 53.29),
        (config_partial_grid, 77.0),
    ),
    ids=["full_ring", "partial_ring", "full_grid", "partial_grid"],
)
def test_total_cable_length(config, total_length):
    array = ArraySystemDesign(config)
    array.run()

    val = round(sum(val.sum() for val in array.cable_lengths_by_type.values()), 2)
    assert total_length == val

    val = round(sum(array.total_cable_length_by_type.values()), 2)
    assert total_length == val

    val = round(
        sum(
            length * n
            for val in array.design_result["array_system"]["cables"].values()
            for length, n in val["cable_sections"]
        ),
        2,
    )
    assert total_length == val


def test_missing_columns():
    array = CustomArraySystemDesign(config_missing_col)

    with pytest.raises(ValueError):
        array.run()


def test_duplicate_turbine_coordinates():
    array = CustomArraySystemDesign(config_duplicate_coordinates)

    with pytest.raises(ValueError):
        array.run()


def test_incomplete_required_columns():
    array = CustomArraySystemDesign(config_incomplete_required)

    with pytest.raises(ValueError):
        array.run()


def test_incomplete_optional_columns():
    array = CustomArraySystemDesign(config_incomplete_optional)

    with pytest.warns(UserWarning):
        array.run()


def test_correct_turbines():
    array = CustomArraySystemDesign(config_incorrect_turbines)

    with pytest.raises(ValueError):
        array.run()


def test_floating_calculations():
    base = deepcopy(config_full_ring)
    base["site"]["depth"] = 50
    number = base["plant"]["num_turbines"]

    sim = ArraySystemDesign(base)
    sim.run()

    base_length = sim.total_length

    floating_no_cat = deepcopy(base)
    floating_no_cat["site"]["depth"] = 250
    floating_no_cat["array_system_design"]["touchdown_distance"] = 0

    sim2 = ArraySystemDesign(floating_no_cat)
    sim2.run()

    no_cat_length = sim2.total_length
    assert no_cat_length == pytest.approx(base_length + 2 * (200 / 1000) * number)

    floating_cat = deepcopy(base)
    floating_cat["site"]["depth"] = 250

    sim3 = ArraySystemDesign(floating_cat)
    sim3.run()

    with_cat_length = sim3.total_length
    assert with_cat_length < no_cat_length
