from numpy import array, math, dot, vectorize

from helix.calculators.subarray_helper import extract_subarray
from helix.models.coordinate import Coordinate


class CoordinatesCalculator(object):
    def __init__(self, values):
        self.values = values


    def transform_coordinates(self, panels, subarrays, buildings):
        """
        Scales, rotates, and translates the coordinates so that they're all
        in inches, and in positive unit space.
        Coordinates are rounded to whole values (used in drawing on the
        array_summary page

        Parameters:
            panels (obj): List of panels
            subarrays (obj): List of subarrays
            buildings (obj): List of lists of building polygons

        Returns:
            tupple

        """

        rotate_all = vectorize(self.rotate)
        scale_all = vectorize(self.scale)
        round_all = vectorize(round)
        neg_translate_all = vectorize(self.neg_translate)
        origins = []


        first_subarray_rotation = None

        for subarray in subarrays:
            begin, size = (subarray.start_row, subarray.size)
            extracted_panels = extract_subarray(panels, subarray.subarray_number)

            raw_coordinates = [panel.coordinate for panel in extracted_panels]

            if first_subarray_rotation is None:
                first_subarray_rotation = raw_coordinates[0].rotation

            rotated_coordinates = rotate_all(raw_coordinates)
            scaled_coordinates = scale_all(rotated_coordinates)

            origin = self.find_origin(scaled_coordinates)
            rounded_coordinates = round_all(scaled_coordinates - origin)

            for idx, val in enumerate(rounded_coordinates):
                panels[begin + idx].coordinate = val
            origins.append(origin)


        prepared_buildings = self.prepare_buildings(buildings, first_subarray_rotation)
        rotated_buildings = list(map(lambda building: rotate_all(building), prepared_buildings ))
        scaled_buildings = list(map(lambda building: scale_all(building), rotated_buildings))

        all_building_coordinates = [point for sublist in scaled_buildings for point in sublist]
        global_origin = self.find_origin(all_building_coordinates + origins)
        origins = array(origins) - global_origin

        for idx, origin in enumerate(origins):
            subarrays[idx].origin = origin

        translated_buildings = list(map(lambda building: neg_translate_all(building, global_origin), scaled_buildings))
        # rounded_buildings = list(map(lambda building: round_all(building), translated_buildings))

        return panels, subarrays, translated_buildings

    def rotate(self, coordinate):
        rotation = math.radians(coordinate.rotation)
        rotation_matrix = array([[math.cos(rotation), -math.sin(rotation)],
                                 [math.sin(rotation), math.cos(rotation)]])
        vector = (coordinate.x, coordinate.y)
        rotated_vector = dot(vector, rotation_matrix)
        return Coordinate(rotated_vector[0], rotated_vector[1])

    def scale(self, coordinate):
        constants = self.values.module_system_constants()
        panel_x, panel_y = constants.panel_spacing
        return coordinate.scale(1. / panel_x, 1. / panel_y)

    def neg_translate(self, coordinate, other):
        return coordinate.neg_translate(other)

    def find_origin(self, coordinates):
        if coordinates == []:
            return Coordinate(0,0)
        min_x = min(list(map(lambda x: x.x, coordinates)))
        min_y = min(list(map(lambda x: x.y, coordinates)))
        return Coordinate(min_x, min_y)

    def prepare_buildings(self, buildings, rotation):
        return list(map(lambda building: self.prepare_single_building(building, rotation), buildings))

    def prepare_single_building(self, building_array, rotation):
        return list(map(lambda point: Coordinate(point[0],point[1],rotation), building_array))



