#! /usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
from math import pi, sin, cos, tan, asin, acos, atan, sqrt

import inkex

from path import Path
from pattern import Pattern

# Select name of class, inherits from Pattern
# TODO:
# 1) Implement __init__ method to get all custom options and then call Pattern's __init__
# 2) Implement generate_path_tree to define all of the desired strokes
MIN = 0.0001


class Bendy_Straw(Pattern):

    def __init__(self):
        """ Constructor
        """
        Pattern.__init__(self)  # Must be called in order to parse common options

        # save all custom parameters defined on .inx file
        self.add_argument('--pattern', type=str, default='bendy_straw')
        self.add_argument('--pattern_type', type=str, default='origami')
        self.add_argument('--parameter_type', type=str, default='angles')
        self.add_argument('--n', type=int, default=6)
        self.add_argument('--lines', type=int, default=3)
        self.add_argument('--radius', type=float, default=25.0)
        # self.add_argument('--attachment_length', type=float, default=3.0)
        # self.add_argument('--attachment_length', type=int, default=20)
        self.add_argument('--radial_ratio', type=float, default=0.75)
        # self.add_argument('--alpha1', type=float, default=45.0)
        # self.add_argument('--alpha2', type=float, default=45.0)
        self.add_argument('--alpha1', type=int, default=45)
        self.add_argument('--alpha2', type=int, default=35)
        self.add_argument('--h1', type=float, default=1)
        self.add_argument('--h2', type=float, default=2)

        self.add_argument('--vertex_base_outer_bool', type=inkex.Boolean, default=False)
        self.add_argument('--vertex_base_inner_bool', type=inkex.Boolean, default=False)
        self.add_argument('--vertex_radius_outer_bool', type=inkex.Boolean, default=False)
        self.add_argument('--vertex_radius_inner_bool', type=inkex.Boolean, default=False)

        self.add_argument('--add_attachment', type=inkex.Boolean, default=False)

        # slot options for support ring
        self.add_argument('--base_height', type=float, default=5.0)
        self.add_argument('--add_base_slot', type=inkex.Boolean, default=False)
        self.add_argument('--center_base_slot', type=inkex.Boolean, default=False)
        self.add_argument('--base_slot_height', type=float, default=3.0)
        self.add_argument('--base_slot_width', type=float, default=3.0)
        self.add_argument('--distance', type=float, default=3.0)
        self.add_argument('--add_distance_slot', type=inkex.Boolean, default=False)
        self.add_argument('--distance_slot_height', type=float, default=3.0)
        self.add_argument('--distance_slot_width', type=float, default=3.0)

    def generate_path_tree(self):
        """ Specialized path generation for your origami pattern
        """
        # retrieve conversion factor for selected unit
        unit_factor = self.calc_unit_factor()
        vertex_radius = self.options.vertex_radius * unit_factor

        # retrieve saved parameters, and apply unit factor where needed
        pattern_type = self.options.pattern_type
        n = self.options.n
        lines = self.options.lines
        radial_ratio = self.options.radial_ratio
        R = self.options.radius * unit_factor
        distance = self.options.distance * unit_factor
        base_height = self.options.base_height * unit_factor
        # add_attachment = self.options.add_attachment
        # attachment_length = self.options.attachment_length * unit_factor
        r = R * radial_ratio

        if (self.options.parameter_type == 'angles'):
            alpha1 = self.options.alpha1 * pi / 180
            alpha2 = self.options.alpha2 * pi / 180
        elif (self.options.parameter_type == 'heights'):
            alpha1 = atan(self.options.h1 * unit_factor / (R - r))
            alpha2 = atan(self.options.h2 * unit_factor / (R - r))

        # calculating pattern parameters
        l1 = (R - r) / cos(alpha1)
        l2 = (R - r) / cos(alpha2)
        A = 2 * R * sin(pi / n)
        # attachment_length = 0.01 * self.options.attachment_length * A
        a = A * radial_ratio
        dx = (A - a) / 2
        beta1 = acos(cos(alpha1) * sin(pi / n))
        beta2 = acos(cos(alpha2) * sin(pi / n))
        b1 = l1 * sin(beta1)
        b2 = l2 * sin(beta2)
        height = (b1 + b2) * lines + distance * (lines - 1)

        if self.options.add_attachment: n = n+1

        #
        # big horizontal mountains grid
        #
        mountain_horizontal_stroke = Path([(0, base_height), (A * n, base_height)], 'm')
        horizontal_grid_mountain = []
        for i in range(1, lines):
            horizontal_grid_mountain.append(
                mountain_horizontal_stroke + (0, distance * (i - 1) + b1 * (i + 0) + b2 * (i + 0)))
            horizontal_grid_mountain.append(
                mountain_horizontal_stroke + (0, distance * (i + 0) + b1 * (i + 0) + b2 * (i + 0)))
        if distance < MIN:
            horizontal_grid_mountain = horizontal_grid_mountain[::2]
        if base_height > MIN:
            horizontal_grid_mountain.insert(0, mountain_horizontal_stroke)
            horizontal_grid_mountain.append(
                mountain_horizontal_stroke + (0, distance * (lines - 1) + b1 * lines + b2 * lines))

        # reverse every other horizontal stroke for faster laser-cutting
        for i in range(len(horizontal_grid_mountain)):
            if (i % 2 == 0):
                horizontal_grid_mountain[i].points.reverse()

        #
        # diamond shapes
        #

        # full diamond patterns styles, depending on pattern type
        style_diag_left = 'm'
        style_diag_right = 'm'
        style_diag = 'v'
        style_vert = 'm'
        style_hori_left = 'm'
        style_hori_right = 'm'
        if pattern_type == 'origami' or pattern_type == 'origami_bent':
            style_hori_left = 'v'
            style_diag_left = 'n'
            style_diag_right = 'v'
        elif pattern_type == 'origami2':
            style_hori_right = 'v'
            style_diag_left = 'v'
            style_diag_right = 'n'
        elif pattern_type == 'kirigami1':
            style_vert = 'v'
            style_hori_left = 'c'
            style_hori_right = 'c'
        elif pattern_type == 'kirigami2':
            style_diag_left = 'c'
            style_diag_right = 'c'
            style_hori_left = 'n'
            style_hori_right = 'n'
            style_vert = 'n'

        # diamond pattern with strokes of different styles
        stroke_base = Path([(0, 0), (0, base_height)], 'm')
        diamond_diagonals_left = Path([(0, base_height), (-dx, base_height + b1), (0, base_height + b1 + b2)],
                                      style_diag_left)
        diamond_diagonals_right = Path([(0, base_height + b1 + b2), (dx, base_height + b1), (0, base_height)],
                                       style_diag_right)
        diamond_vertical = Path([(0, base_height), (0, base_height + b1 + b2)], style_vert)
        stroke_distance = Path([(0, base_height + b1 + b2), (0, distance + base_height + b1 + b2)], 'm')
        diamond_horizontal_left = Path([(-dx, 0), (0, 0)], style_hori_left)
        diamond_horizontal_right = Path([(0, 0), (dx, 0)], style_hori_right)

        diamond = [diamond_diagonals_left, diamond_diagonals_right, diamond_vertical]

        if pattern_type == 'origami_bent':
            bent_diagonal = Path([(0, base_height + b1 + b2), (dx, base_height + b1), (0, base_height)], 'm')
            bent_horizontal = Path([(0, 0), (dx, 0)], 'v')
            line_bent = []


        # drawing lines with the diamond shapes
        line_left = []
        line_middle = []
        line_right = []
        if base_height > MIN:
            line_middle.append(stroke_base)
            if pattern_type == 'origami_bent':
                line_bent.append(stroke_base)
        for i in range(lines):
            delta = (0, (distance + b1 + b2) * i)
            if pattern_type != 'kirigami2':
                line_left.append(diamond_diagonals_right + delta)
            line_middle = line_middle + Path.list_add(diamond, delta)
            if pattern_type != 'kirigami2':
                line_right.append(diamond_diagonals_left + delta)
            if distance > MIN and i < lines - 1:
                line_middle.append(stroke_distance + delta)

            if pattern_type == 'origami_bent':
                line_bent = line_bent + [bent_diagonal + delta]
                if distance > MIN and i < lines - 1:
                    line_bent.append(stroke_distance + delta)

        if base_height > MIN:
            line_middle.append(stroke_base + (0, base_height + height))
            if pattern_type == 'origami_bent':
                line_bent.append(stroke_base + (0, base_height + height))

        # creating full diamond patterns
        line_left = line_left[::-1]
        diamond_patterns_full = [line_left]
        for i in range(n - 1):
            delta = (A * (i + 1), 0)
            if pattern_type == 'origami_bent' and i == 2:
                diamond_patterns_full.append(Path.list_add(line_bent, delta))
            else:
                diamond_patterns_full.append(Path.list_add(line_middle, delta))
        diamond_patterns_full.append(Path.list_add(line_right, (A * n, 0)))

        #
        # small horizontal alternate style grid
        #
        valley_points = [(dx, 0),
                         (dx + a, 0)]
        valley_stroke = Path(valley_points, 'v')
        if pattern_type == 'kirigami2':
            horizontal_line = []
        else:
            horizontal_line = [diamond_horizontal_right + (0, 0)]
        for i in range(n):
            if not (pattern_type == 'origami_bent' and i == 3):
                horizontal_line.append(valley_stroke + ((a + 2 * dx) * i, 0))
            if (pattern_type != 'kirigami2'):
                if not (pattern_type == 'origami_bent' and i == 3):
                    horizontal_line.append(diamond_horizontal_left + (A + (a + 2 * dx) * i, 0))
                if pattern_type == 'origami_bent' and i==2:
                    horizontal_line.append(bent_horizontal + (A + (a + 2 * dx) * i, 0))
                elif i < n - 1:
                    horizontal_line.append(diamond_horizontal_right + (A + (a + 2 * dx) * i, 0))
        horizontal_grid_alternate = []
        for i in range(lines):
            horizontal_grid_alternate.append(
                Path.list_add(horizontal_line, (0, base_height + distance * (i + 0) + b1 * (i + 1) + b2 * (i + 0))))

        # reverse every other horizontal stroke for faster laser-cutting
        for i in range(len(horizontal_grid_alternate)):
            if (i % 2 == 0):
                horizontal_grid_alternate[i] = Path.list_invert(horizontal_grid_alternate[i])

        # for i in range(len(horizontal_grid_alternate)):
        #     inkex.utils.debug(i)
        #     Path.debug_points(horizontal_grid_alternate[i])
        #     inkex.utils.debug('\n')

        #
        # edge drawing
        #
        self.edge_points = [(0, 0)]
        self.edge_points.append((A * n, 0))

        # rectangles for attachments at base and between cells

        # add upper base attachment
        self.edge_points.append((A * n, base_height))

        # draw attachment between cells and inside cells
        for i in range(lines):
            self.edge_points.append((A * n + 0, base_height + (b1 + b2) * (i + 0) + distance * i))
            if pattern_type == 'kirigami2':
                self.edge_points.append((A * n - dx, base_height + b1 * (i + 1) + (b2 + distance) * i))
            self.edge_points.append((A * n + 0, base_height + (b1 + b2) * (i + 1) + distance * i))

        self.edge_points.append((A * n, height + 2 * base_height))
        self.edge_points.append((0, height + 2 * base_height))

        # if full kirigami selected, cut left side next to cells
        if pattern_type == 'kirigami2':
            for i in range(lines):
                self.edge_points.append((0, height + base_height - (b1 + b2) * (i + 0) - distance * i))
                self.edge_points.append((dx, height + base_height - b2 * (i + 1) - (b1 + distance) * i))
                self.edge_points.append((0, height + base_height - (b1 + b2) * (i + 1) - distance * i))

        #
        # slots drawing
        #
        center_slot = self.options.center_base_slot
        base_slots = []
        if self.options.add_base_slot:
            base_slot_height = self.options.base_slot_height * unit_factor
            base_slot_width = self.options.base_slot_width * unit_factor
            if base_slot_height > base_height or base_slot_width > A:
                inkex.utils.debug('Base slot dimensions are too big')
                base_slot_height = min(base_height, base_slot_height)
                base_slot_width = min(A, base_slot_width)
            if base_slot_height > 0 and base_slot_width > 0:
                points = [(0,     0),
                          (0,     base_slot_height),
                          (base_slot_width, base_slot_height),
                          (base_slot_width, 0,)]
                base_slot = Path(points, 'c', closed=True) + ((A - base_slot_width)/2, (base_height - base_slot_height)/(1+center_slot))
                base_slots_line = []
                for i in range(n):
                    base_slots_line.append(base_slot + (A*i, 0))
                base_slots = [base_slots_line]
                base_slots.append(Path.list_add(base_slots_line, (0, height+base_slot_height + (base_height - base_slot_height)*center_slot)))

        dist_slots = []
        if self.options.add_distance_slot:
            dist_slot_height = self.options.distance_slot_height * unit_factor
            dist_slot_width = self.options.distance_slot_width * unit_factor
            if dist_slot_height > distance or dist_slot_width > A:
                inkex.utils.debug('Dimensions of slots between cells are too big')
                dist_slot_height = min(distance, dist_slot_height)
                dist_slot_width = min(A, dist_slot_width)

            if dist_slot_height > 0 and dist_slot_width > 0:
                points = [(0,     0),
                          (0,     dist_slot_height),
                          (dist_slot_width, dist_slot_height),
                          (dist_slot_width, 0,)]
                dist_slot = Path(points, 'c', closed=True) + ((A - dist_slot_width)/2, base_height+b1+b2 + (distance - dist_slot_height)/2)
                dist_slots_line = []
                for i in range(n):
                    dist_slots_line.append(dist_slot + (A*i, 0))

                for i in range(lines-1):
                    dist_slots.append(Path.list_add(dist_slots_line, (0, i*(b1+b2+distance))))



        # sending lines  to draw
        self.path_tree = [horizontal_grid_mountain, horizontal_grid_alternate, diamond_patterns_full, base_slots, dist_slots]

        #
        # vertices drawing
        #
        # outer base vertices?
        if self.options.vertex_base_outer_bool:
            self.vertex_points = self.vertex_points + [(A * i, height + base_height * 2) for i in range(n + 1)]
            self.vertex_points = self.vertex_points + [(A*i, 0) for i in range(n+1)]

        # inner base vertices?
        if self.options.vertex_base_inner_bool:
            self.vertex_points = self.vertex_points + [(A*i, base_height) for i in range(n+1)]
            self.vertex_points = self.vertex_points + [(A*i, height+base_height) for i in range(n+1)]
            for j in range(lines-1):
                    self.vertex_points = self.vertex_points + [(A*i, base_height+((b1+b2)*(j+1))+distance*j) for i in range(n+1)] + \
                                                              [(A*i, base_height+((b1+b2)*(j+1))+distance*(j+1)) for i in range(n+1)]

        # radius vertices?
        if self.options.vertex_radius_outer_bool and pattern_type != 'kirigami2':
            for j in range(lines):
                i_range = list(range(3)) + list(range(3+(pattern_type=='origami_bent'), n + 1))
                self.vertex_points = self.vertex_points + [(A * i, base_height + b1*(j+1) + (b2+distance)*j) for i in i_range]
        if self.options.vertex_radius_inner_bool:
            for j in range(lines):
                if pattern_type != 'origami2':
                    # pass
                    self.vertex_points = self.vertex_points + [(dx + A * i, base_height + b1*(j+1) + (b2+distance)*j) for i in range(n)]
                if pattern_type[:7] != 'origami':
                    # pass
                    self.vertex_points = self.vertex_points + [(-dx + A * (i + 1), base_height + b1 * (j + 1) + (b2 + distance) * j) for i in range(n)]
            # for j in range(lines):




        # self.vertex_points = self.vertex_points + vertices_base

        # self.vertex_points = self.vertex_points + self.edge_points
        # diamond_patterns_full_simple = Path.list_simplify(diamond_patterns_full)
        # for path in diamond_patterns_full:
        #     # path = Path.list_simplify(path)
        #     # path = Path.list_simplify(path[0])
        #     if path.style != 'n':
        #         self.vertex_points = self.vertex_points + path.points



#

# Main function, creates an instance of the Class and calls self.run() to draw the origami on inkscape
# self.run() is either a call to inkex.affect() or to svg.run(), depending on python version
if __name__ == '__main__':
    e = Bendy_Straw()  # remember to put the name of your Class here!
    e.run()
