from math import sqrt, log
import math

from helix.constants.global_constants import parapet_coefficients, parapet_factor_max
from numpy import array
from numpy.ma import maximum

from helix.constants.panel_type import PanelType


class PressureCoefficientCalculator(object):
    def __init__(self, user_values):
        self.values = user_values
        self.system_constants = self.values.system_type().system_constants()
        self.module_constants = self.values.module_system_constants()

    def c_p_matrix(self, L_B):
        parapet = self.parapet_factor()
        return self.compute_c_p_matrix(L_B, parapet)

    def L_B(self):
        """ Building scaling factor """
        height = max(15, self.values.building_height())

        length = self.values.building_length()
        width = self.values.building_width()

        longest_side = max(width, length)

        return min(height, 0.4 * sqrt(height * max(1, longest_side)))

    def minimum_array_size(self, L_B):
        panel_area = self.module_constants.panel_area
        module_count = self.system_constants.module_count
        minimum_array_size = []
        for minimum_A_n in self.minimum_A_n(L_B):
            if minimum_A_n is None:
                value = 6
            else:
                value = int(math.ceil((minimum_A_n * L_B ** 2) / (panel_area * 1000) / module_count))
            minimum_array_size.append(value)
        return minimum_array_size

    # Normalized area, scales the tributary area by the building scaling factor and panel area
    def A_n(self, L_B):
        return self.module_constants.tributary_area * (self.module_constants.panel_area * 1000. / L_B ** 2)

    def compute_c_p_matrix(self, L_B, parapet):
        A_n = self.A_n(L_B)
        wind_zones = self.system_constants.wind_zones
        return array([self.c_p_row(A_n, wind_zone, parapet) for wind_zone in wind_zones])

    def c_p_row(self, A_n_row, wind_zone, parapet_factor):
        c_p_lower_bound = self.module_constants.c_p_lower_bound()

        if wind_zone == self.system_constants.wind_zones[-1]:
            return c_p_lower_bound
        computed_row = []
        for index, A_n in enumerate(A_n_row):
            edge_factor = self.module_constants.edge_factor(wind_zone, PanelType.from_index(index))
            computed_row.append(self.c_p(A_n, wind_zone, parapet_factor, edge_factor))

        return maximum(array(computed_row), c_p_lower_bound)

    def c_p(self, A_n, wind_zone, parapet_factor, edge_factor):
        c0, c1 = self.module_constants.c_p_constants(A_n, wind_zone)
        return max(0., c0 * log(A_n) + c1) * parapet_factor * edge_factor

    def parapet_factor(self):
        height = max(15, self.values.building_height())
        parapet_height = max(0, self.values.building_parapet_height())

        factor = parapet_height / height
        c0, c1 = parapet_coefficients
        return min(parapet_factor_max, c0 + c1 * factor)

    def ideal_subarray_average_uplift_c_p(self, L_B):
        c_p_matrix = self.compute_c_p_matrix(L_B, 1)
        return [self.module_constants.weighted_average_c_p(c, n, e, m) for c, n, e, m in c_p_matrix]

    def minimum_A_n(self, L_B):
        uplift_c_p = self.ideal_subarray_average_uplift_c_p(L_B)
        minimum_A_n = []
        for idx, wind_zone in enumerate(self.system_constants.wind_zones):
            wind_zone_uplift = uplift_c_p[idx]
            coefficients = self.module_constants.minimum_a_n_coefficients(wind_zone_uplift, wind_zone)
            if not coefficients:
                minimum_A_n.append(None)
                continue

            value = math.exp((coefficients[0] - wind_zone_uplift) / coefficients[1])
            minimum_A_n.append(value)
        return minimum_A_n
