from math import ceil, floor

from helix.calculators.bom_helper import add_parts_to_list, apply_fudge_factors, \
    get_panel_type_counts
from helix.calculators.subarray_helper import extract_subarray
from helix.constants.module_type import ModuleType
from helix.constants.panel_type import PanelType
from helix.constants.parts import link_tray, cross_tray, ballast, cross_tray_1_1
from helix.constants.system_type import SystemType


class MechanicalBomCalculator(object):
    def __init__(self, values, panels, subarrays):
        self.values = values
        self.panels = panels
        self.subarrays = subarrays

    def mechanical_bom(self):
        module_type = self.values.module_type()
        system_type = self.values.system_type()
        system_parts = system_type.parts(module_type)
        combined_parts_list = {}

        for subarray in self.subarrays:
            panels = extract_subarray(self.panels, subarray.subarray_number)

            ballast_count = sum(panel.ballast for panel in panels)
            cross_count = sum(panel.cross_tray for panel in panels)
            assigned_seismic_anchors_count = sum(panel.seismic_anchors for panel in panels)
            required_seismic_anchors_count = subarray.required_seismic_anchors
            seismic_anchors_count = max(assigned_seismic_anchors_count, required_seismic_anchors_count)
            required_wind_anchors_count = sum(panel.wind_anchors for panel in panels)
            anchor_count = required_wind_anchors_count + seismic_anchors_count
            panel_type_counts = get_panel_type_counts(panels)

            subarray_parts_list = {}

            for index, panel_type_parts in enumerate(system_parts.parts_per_panel_type()):
                add_parts_to_list(subarray_parts_list, panel_type_parts, panel_type_counts[PanelType.from_index(index)])

            add_parts_to_list(subarray_parts_list, self.values.anchor_type().parts().parts, anchor_count)

            link_count = self.link_count(panel_type_counts, panels, subarray)

            add_parts_to_list(subarray_parts_list, {link_tray: 1}, link_count)
            cross_tray_parts = cross_tray if self.values.module_type() == ModuleType.Cell96 else cross_tray_1_1
            add_parts_to_list(subarray_parts_list, {cross_tray_parts: 1}, cross_count)
            add_parts_to_list(subarray_parts_list, {ballast: 1}, ballast_count)

            add_parts_to_list(subarray_parts_list, system_parts.row_parts(module_type), subarray.row_count)
            add_parts_to_list(subarray_parts_list, system_parts.column_parts(module_type), subarray.column_count)

            add_parts_to_list(subarray_parts_list, system_parts.sub_array_parts, 1)

            apply_fudge_factors(subarray_parts_list, system_parts.fudge_factors(not subarray.row_counted_geometrically))

            dependent_parts_list = {}
            for part, quantity in subarray_parts_list.items():
                dependent_parts = system_parts.dependent_parts(module_type).get(part)
                if dependent_parts:
                    add_parts_to_list(dependent_parts_list, dependent_parts, quantity)

            subarray_parts_list.update(dependent_parts_list)
            add_parts_to_list(combined_parts_list, subarray_parts_list)

        return combined_parts_list

    def link_count(self, panel_type_counts, panels, subarray):
        if self.values.system_type() == SystemType.dualTilt:

            # check if info about position of panels is available
            coordinates_available = all(p.coordinate for p in panels) \
                                    and not all(p.coordinate.x == 0 and p.coordinate.y == 0 for p in panels)

            if coordinates_available:
                # initially every C, NS, EW panels has 2 link trays attached
                panel_types = [PanelType.Corner, PanelType.NorthSouth, PanelType.EastWest]
                layout = dict([((p.coordinate.x, p.coordinate.y), 2) for p in panels if p.panel_type in panel_types])

                row_count = ceil(subarray.row_count)
                column_count = ceil(subarray.column_count)

                # reduce number of link trays between every two vertically adjoining panels
                for y in range(row_count):
                    for x in range(column_count):

                        if (x, y) not in layout:
                            continue

                        if (x, y + 1) in layout:
                            layout[(x, y + 1)] = 1

                # count link trays located on perimeter
                link_count = sum([layout[p] for p in layout])

                # subtract places reserved for leading trays
                link_count -= subarray.row_count + 1

                # add link trays for panels of type Middle
                link_count += sum([1 for panel in panels if panel.link_tray != 0 and panel.panel_type == PanelType.Middle])

                return max(link_count, 0)
            else:
                total_possible_link_trays = len(panels) + subarray.column_count
                link_count = total_possible_link_trays

                for panel in panels:
                    if panel.link_tray == 0 and panel.panel_type == PanelType.Middle:
                        link_count -= 1

                link_count -= floor(subarray.row_count)

                return link_count
        else:
            return sum([self.compute_link_count_single_tilt(panel_type, panel_type_counts, panels) for panel_type in PanelType.all()])

    def compute_link_count_single_tilt(self, panel_type, panel_type_counts, panels):
        if panel_type == PanelType.Corner:
            return 0
        elif panel_type == PanelType.NorthSouth:
            return 0
        elif panel_type == PanelType.EastWest:
            return panel_type_counts[panel_type] * 2
        elif panel_type == PanelType.Middle:
            return self.get_panel_type_middle_link_trays_single_tilt(panels)
        else:
            return 0

    def get_panel_type_middle_link_trays_single_tilt(self, panels):
        wind_zones = self.values.system_type().system_constants().wind_zones

        middle_panels_per_wind_zone = [0 for _ in wind_zones]
        middle_link_trays_per_wind_zone = [0 for _ in wind_zones]

        for panel in panels:
            if panel.panel_type != PanelType.Middle:
                continue

            wind_zone = panel.wind_zone
            middle_panels_per_wind_zone[wind_zone] += 1
            middle_link_trays_per_wind_zone[wind_zone] += panel.link_tray  # use calculated number of link trays to see if it is non-zero

        total_link_trays_required = 0
        for wind_zone, panel_count in enumerate(middle_panels_per_wind_zone):
            if middle_link_trays_per_wind_zone[wind_zone] > 0:  # if any middle panels in this wind zone need link trays
                total_link_trays_required += ceil(panel_count * 1.05)
        return total_link_trays_required

