"""
Generate Dactyl case
"""

from math import radians, sin
from lib import *
from shapes import *
import lib
import mat

SWITCH_THICKNESS = 1.3
SWITCH_TOP_THICKNESS = 2.8
WEB_THICKNESS = 3
SWITCH_RIM_THICKNESS = 2

POST_WIDTH = 0.5
POST_RAD = POST_WIDTH / 2

KEYHOLE_SIZE = 13.8
KEYSWITCH_HEIGHT = 15
KEYSWITCH_WIDTH = 15

PLATE_OUTER_WIDTH = KEYHOLE_SIZE + SWITCH_RIM_THICKNESS * 2

MAX_NUM_ROWS = 4
NUM_COLS = 6
NUM_PINKY_COLUMNS = 2

cols_with_max_rows = [2, 3]

SA_PROFILE_KEY_HEIGHT = 2
CAP_TOP_HEIGHT = SWITCH_TOP_THICKNESS + SA_PROFILE_KEY_HEIGHT

# extra space between the base of keys
EXTRA_HEIGHT = 3
EXTRA_WIDTH = 3
MOUNT_HEIGHT = KEYSWITCH_HEIGHT
MOUNT_WIDTH = KEYSWITCH_WIDTH
# use 10 for faster prototyping, 15 for real
TENTING_ANGLE = 11.0
# This affects the height of the keyboard
Z_OFFSET = 11.0

SHOULD_INCLUDE_RISERS = False


def is_pinky(col):
    return col >= NUM_COLS - NUM_PINKY_COLUMNS


# aka: alpha
def row_curve_deg(col):
    # I tried to have a different curve for pinkys, but it didn't work well
    return 17


# aka: beta
col_curve_deg = 4.0

column_radius = CAP_TOP_HEIGHT + ((MOUNT_WIDTH + EXTRA_WIDTH) / 2) / sin(
    radians(col_curve_deg) / 2
)


def row_radius(col):
    return CAP_TOP_HEIGHT + ((MOUNT_HEIGHT + EXTRA_HEIGHT) / 2) / sin(
        radians(row_curve_deg(col)) / 2
    )


# default 3
# controls left-right tilt / tenting (higher number is more tenting)
center_col = 3
center_row = 1


def column_extra_transform(col):
    if is_pinky(col):
        # TODO: translate first?
        return compose(rotate_x(6), rotate_y(-3))
    else:
        return identity()


def num_rows_for_col(col):
    if col in cols_with_max_rows:
        return MAX_NUM_ROWS
    else:
        return MAX_NUM_ROWS - 1


def does_coord_exist(row, col):
    return col >= 0 and col < NUM_COLS and row >= 0 and row < num_rows_for_col(col)


def negative(vect):
    return [-x for x in vect]


def bottom_transform(height):
    return extrude_linear(height)(project())


def bottom_hull(shape):
    return hull(shape, bottom_transform(0.1)(shape))


def single_switch_fn():
    outer_width = KEYHOLE_SIZE + SWITCH_RIM_THICKNESS * 2

    bottom_wall = cube(outer_width, SWITCH_RIM_THICKNESS, SWITCH_THICKNESS)
    top_wall = translate(0, KEYHOLE_SIZE + SWITCH_RIM_THICKNESS, 0)(bottom_wall)

    left_wall = cube(SWITCH_RIM_THICKNESS, outer_width, SWITCH_THICKNESS)
    right_wall = translate(KEYHOLE_SIZE + SWITCH_RIM_THICKNESS, 0, 0)(left_wall)

    nub_len = 2.75
    nub_cyl = translate(0, 0, -1)(rotate_x(90)(cylinder(1, nub_len, 30, center=True)))
    # nub_cube = translate(-SWITCH_RIM_THICKNESS / 2, 0, 0.)(cube(SWITCH_RIM_THICKNESS, nub_len, 4, center=True))
    # left_nub = translate(SWITCH_RIM_THICKNESS, (outer_width) / 2, 0)(hull(nub_cyl, nub_cube))

    # right_nub_cube = translate(SWITCH_RIM_THICKNESS / 2, 0, 0)(cube(SWITCH_RIM_THICKNESS, nub_len, 4, center=True))
    # right_nub = translate(-SWITCH_RIM_THICKNESS + outer_width, (outer_width) / 2, 0)(hull(nub_cyl, right_nub_cube))

    return translate(-outer_width / 2, -outer_width / 2, 0)(
        union(
            bottom_wall,
            top_wall,
            left_wall,
            right_wall,
            # left_nub,
            # right_nub,
        )
    )


single_switch = single_switch_fn()

filled_switch = translate(0, 0, SWITCH_THICKNESS / 2.0)(
    cube(PLATE_OUTER_WIDTH, PLATE_OUTER_WIDTH, SWITCH_THICKNESS, center=True)
)

sa_length = 16.5
sa_width = 17
sa_height = 16.5
sa_base_height = 1.5
sa_extra_height = 2
sa_double_length = 37.5


def sa_cap_fn():
    # bl2 = sa_length / 2
    m = 20.0

    bot = square(sa_width, sa_height, center=True)
    bot = extrude_linear(0.1)(bot)

    mid = square(sa_width, sa_height, center=True)
    mid = extrude_linear(0.1)(mid)
    mid = translate(0, 0, sa_base_height)(mid)

    top = square(14, 14, center=True)
    top = extrude_linear(0.1)(top)
    top = translate(0, 0, sa_extra_height)(top)

    return colour(220, 163, 163, 1)(
        translate(0, 0, SWITCH_TOP_THICKNESS + 1.5)(hull(bot, mid, top))
    )


sa_cap = sa_cap_fn()


def row_cols():
    for row in range(MAX_NUM_ROWS):
        for col in range(NUM_COLS):
            if row < num_rows_for_col(col):
                yield (row, col)


def all_of_shape(shape):
    return [grid_position(row, col, shape) for (row, col) in row_cols()]


web_post = translate(-POST_RAD, -POST_RAD, SWITCH_THICKNESS - WEB_THICKNESS)(
    cube(POST_WIDTH, POST_WIDTH, WEB_THICKNESS)
)
short_web_post = translate(-POST_RAD, -POST_RAD, 0)(
    cube(POST_WIDTH, POST_WIDTH, POST_WIDTH)
)

SQUARE_OFFSET_IDXS = [
    [[-1, -1], [1, -1]],
    [[-1, 1], [1, 1]],
]


def square_apply(square, fn):
    return [[fn(x) for x in y] for y in square]


def square_translater_at_offset(offset):
    def fn(idx):
        return translate(idx[0] * offset, idx[1] * offset, 0)

    return square_apply(SQUARE_OFFSET_IDXS, fn)


def apply_translate_square(square, shape):
    def fn(translator):
        return translator(shape)

    return square_apply(square, fn)


outer_post_delta = KEYHOLE_SIZE / 2 + SWITCH_RIM_THICKNESS  # - POST_RAD/2

outer_post_translate_square = square_translater_at_offset(outer_post_delta)

web_post_tr = translate(outer_post_delta, outer_post_delta, 0)(web_post)
web_post_tl = translate(-outer_post_delta, outer_post_delta, 0)(web_post)
web_post_br = translate(outer_post_delta, -outer_post_delta, 0)(web_post)
web_post_bl = translate(-outer_post_delta, -outer_post_delta, 0)(web_post)

web_posts = apply_translate_square(outer_post_translate_square, web_post)
short_web_posts = apply_translate_square(outer_post_translate_square, short_web_post)

inner_post_delta = KEYHOLE_SIZE / 2 + POST_RAD
inner_post_translate_square = square_translater_at_offset(inner_post_delta)

inner_web_posts = apply_translate_square(inner_post_translate_square, web_post)
short_inner_web_posts = apply_translate_square(
    inner_post_translate_square, short_web_post
)

post_left_edge_indexes = [[0, 0], [1, 0]]
post_top_edge_indexes = [[1, 0], [1, 1]]
post_right_edge_indexes = [[1, 1], [0, 1]]
post_bot_edge_indexes = [[0, 1], [0, 0]]

square_idx_tl = [1, 0]
square_idx_tr = [1, 1]
square_idx_bl = [0, 0]
square_idx_br = [0, 1]


def get_web_post(idx):
    return web_posts[idx[0]][idx[1]]


post_left_edge = [get_web_post(e) for e in post_left_edge_indexes]
post_top_edge = [get_web_post(e) for e in post_top_edge_indexes]
post_right_edge = [get_web_post(e) for e in post_right_edge_indexes]
post_bot_edge = [get_web_post(e) for e in post_bot_edge_indexes]


def get_in_square(square, idx):
    return square[idx[0]][idx[1]]


def get_inner_web_post(idx):
    return get_in_square(inner_web_posts, idx)


def get_short_web_post(idx):
    return get_in_square(short_web_posts, idx)


def get_short_inner_web_post(idx):
    return get_in_square(short_inner_web_posts, idx)


def column_offset(col):
    if col == 2:
        return [1, 5, -3]
    elif col == 3:
        return [3, 0, -0.5]
    elif is_pinky(col):
        return [7.0, -14.5, 5.0]
    else:
        return [0, 0, 0]


def col_z_rotate(col):
    if is_pinky(col):
        return -3.0
    else:
        return 0


def place_on_grid_base(row, column, domain):
    column_angle = col_curve_deg * (center_col - column)
    row_angle = row_curve_deg(column) * (center_row - row)

    return domain.compose(
        # Row Sphere
        domain.translate(0, 0, -row_radius(column)),
        domain.rotate_x(row_angle),
        domain.translate(0, 0, row_radius(column)),
        # Row Offset
        # column_extra_transform(column),
        # Col sphere
        domain.translate(0, 0, -column_radius),
        domain.rotate_y(column_angle),
        domain.translate(0, 0, column_radius),
        # Z Fix
        domain.rotate_z(col_z_rotate(column)),
        # Column offset
        domain.translate(*column_offset(column)),
        # Misc
        domain.rotate_y(TENTING_ANGLE),
        domain.translate(0, 0, Z_OFFSET),
    )


def place_on_grid(row, column):
    return place_on_grid_base(row, column, lib)


def point_on_grid(row, column, x, y, z):
    point = mat.point3(x, y, z)
    tx_point = place_on_grid_base(row, column, mat) @ point

    return tx_point[:3]


def grid_position(row, col, shape):
    return place_on_grid(row, col)(shape)


def connectors():
    def make_edge_connection(r1, c1, e1, r2, c2, e2):
        posts1 = [grid_position(r1, c1, e) for e in e1]
        posts2 = [grid_position(r2, c2, e) for e in e2]
        return hull(*posts1, *posts2)

    all_connectors = []
    for col in range(NUM_COLS - 1):
        for row in range(MAX_NUM_ROWS):
            if does_coord_exist(row, col) and does_coord_exist(row, col + 1):
                if (row, col) == (0, 3):
                    right_edge = [
                        translate(1, 1, 0)(web_post_tr),
                        translate(0, 1, 0)(web_post_tr),
                        web_post_br,
                    ]
                else:
                    right_edge = post_right_edge
                all_connectors.append(
                    make_edge_connection(
                        row, col, right_edge, row, col + 1, post_left_edge
                    )
                )

    for col in range(NUM_COLS):
        for row in range(MAX_NUM_ROWS - 1):
            if does_coord_exist(row, col) and does_coord_exist(row + 1, col):
                all_connectors.append(
                    make_edge_connection(
                        row, col, post_bot_edge, row + 1, col, post_top_edge
                    )
                )

    for col in range(NUM_COLS - 1):
        row = num_rows_for_col(col) - 1
        next_col = col + 1
        next_row = num_rows_for_col(next_col) - 1
        if next_row == row + 1:
            this_t = place_on_grid(row, col)
            next_t = place_on_grid(next_row, next_col)
            this_level_t = place_on_grid(row, next_col)
            all_connectors.append(
                hull(
                    this_t(web_post_br), this_level_t(web_post_bl), next_t(web_post_tl),
                )
            )
            all_connectors.append(
                hull(this_t(web_post_br), next_t(web_post_tl), next_t(web_post_bl),)
            )
        if next_row == row - 1:
            this_t = place_on_grid(row, col)
            next_t = place_on_grid(next_row, next_col)
            this_level_t = place_on_grid(next_row, col)

            all_connectors.append(
                hull(
                    this_t(web_post_tr), this_level_t(web_post_br), next_t(web_post_bl),
                )
            )

            all_connectors.append(
                hull(this_t(web_post_tr), this_t(web_post_br), next_t(web_post_bl),)
            )

    def does_diag_exist(row, col):
        for dr in [0, 1]:
            for dc in [0, 1]:
                if not does_coord_exist(row + dr, col + dc):
                    return False
        return True

    for col in range(NUM_COLS - 1):
        for row in range(MAX_NUM_ROWS - 1):
            if does_diag_exist(row, col):
                p1 = grid_position(row, col, web_post_br)
                p2 = grid_position(row + 1, col, web_post_tr)
                p3 = grid_position(row + 1, col + 1, web_post_tl)
                p4 = grid_position(row, col + 1, web_post_bl)
                all_connectors.append(hull(p1, p2, p3, p4))

    return union(*all_connectors)


SWITCH_RISER_OFFSET = 9.8 + SWITCH_RISER_RADIUS
switch_riser_offset_square = square_translater_at_offset(SWITCH_RISER_OFFSET)


def get_riser_is_connector(rc1, idx1, rc2, idx2):
    if rc1 == [0, 3] and rc2 == [0, 4]:
        return False
    return True


def get_riser_offset_delta(row_col, idx):
    if row_col == [0, 1] and idx == square_idx_tr:
        return [-2.5, 0]
    if row_col == [0, 3] and idx == square_idx_tl:
        return [2.5, 0]
    if row_col == [0, 3] and idx == square_idx_tr:
        return [-0.5, 0]
    if row_col == [0, 4] and idx == square_idx_tl:
        return [2.5, 0]
    if row_col == [2, 4] and idx == square_idx_bl:
        return [2.5, 0]
    if row_col == [3, 2] and idx == square_idx_br:
        return [-3.5, 1]
    if row_col == [2, 1] and idx == square_idx_br:
        return [-3, 0]
    else:
        return None


def wall_connect(row1, col1, idx1, row2, col2, idx2, **kwargs):
    place_fn1 = place_on_grid(row1, col1)
    place_fn2 = place_on_grid(row2, col2)

    delta1 = get_riser_offset_delta([row1, col1], idx1)
    delta2 = get_riser_offset_delta([row2, col2], idx2)

    connectors = get_riser_is_connector([row1, col1], idx1, [row2, col2], idx2)

    return wall_connect_from_placer(
        place_fn1,
        idx1,
        place_fn2,
        idx2,
        delta1=delta1,
        delta2=delta2,
        connectors=connectors,
        **kwargs,
    )


def make_offsetter(idx, delta):
    offsetter = get_in_square(switch_riser_offset_square, idx)

    if delta:
        z = 0
        if len(delta) == 3:
            z = delta[2]
        offsetter = translate(delta[0], delta[1], z)(offsetter)
    return offsetter


def wall_connect_from_placer(
    place_fn1,
    idx1,
    place_fn2,
    idx2,
    *,
    delta1=None,
    delta2=None,
    connectors=True,
    walls=True
):
    offsetter1 = make_offsetter(idx1, delta1)
    offsetter2 = make_offsetter(idx2, delta2)

    post1 = offsetter1(switch_riser_post)
    post2 = offsetter2(switch_riser_post)

    shapes = []

    if SHOULD_INCLUDE_RISERS:
        shapes.append(hull(place_fn1(post1), place_fn2(post2)))

    if connectors:
        shapes.append(
            hull(
                place_fn1(union(offsetter1(web_post), get_in_square(web_posts, idx1))),
                place_fn2(union(offsetter2(web_post), get_in_square(web_posts, idx2))),
            )
        )

    if walls:
        shapes.append(
            bottom_hull(
                hull(
                    place_fn1(offsetter1(switch_riser_raw_dot)),
                    place_fn2(offsetter2(switch_riser_raw_dot)),
                )
            )
        )

    return union(*shapes)


def case_walls():
    all_shapes = []

    # Top wall
    for col in range(0, NUM_COLS):
        all_shapes.append(wall_connect(0, col, square_idx_tl, 0, col, square_idx_tr))
    for col in range(0, NUM_COLS - 1):
        all_shapes.append(
            wall_connect(0, col, square_idx_tr, 0, col + 1, square_idx_tl)
        )

    # Right wall
    max_col = NUM_COLS - 1
    for row in range(0, num_rows_for_col(max_col)):
        all_shapes.append(
            wall_connect(row, max_col, square_idx_tr, row, max_col, square_idx_br)
        )
    for row in range(0, num_rows_for_col(max_col) - 1):
        all_shapes.append(
            wall_connect(row, max_col, square_idx_br, row + 1, max_col, square_idx_tr)
        )

    # Left wall
    for row in range(0, num_rows_for_col(0)):
        all_shapes.append(wall_connect(row, 0, square_idx_tl, row, 0, square_idx_bl))
    for row in range(0, num_rows_for_col(0) - 1):
        all_shapes.append(
            wall_connect(row, 0, square_idx_bl, row + 1, 0, square_idx_tl)
        )

    # Bottom wall
    def include_wall(col):
        return col >= 2

    for col in range(0, NUM_COLS):
        all_shapes.append(
            wall_connect(
                num_rows_for_col(col) - 1,
                col,
                square_idx_bl,
                num_rows_for_col(col) - 1,
                col,
                square_idx_br,
                walls=include_wall(col),
            )
        )
    for col in range(0, NUM_COLS - 1):
        all_shapes.append(
            wall_connect(
                num_rows_for_col(col) - 1,
                col,
                square_idx_br,
                num_rows_for_col(col + 1) - 1,
                col + 1,
                square_idx_bl,
                walls=include_wall(col),
            )
        )

    return union(*all_shapes)


def all_switches():
    return union(*all_of_shape(single_switch))


def filled_switches():
    return union(*all_of_shape(filled_switch))


def all_caps():
    return union(*all_of_shape(sa_cap))


def post_test():
    return union(*[y for x in short_web_posts for y in x],)


thumb_basic_postition = point_on_grid(num_rows_for_col(1), 1, 0, 0, 0)
thumb_offsets = [16, 4.8, -2.5]
# thumb_offsets = [16, 7, -5]
thumb_position = [sum(x) for x in zip(thumb_basic_postition, thumb_offsets)]


def thumb_placer(rot, move):
    return compose(
        rotate_x(rot[0]),
        rotate_y(rot[1]),
        rotate_z(rot[2]),
        translate(*move),
        rotate_z(-10),
        translate(*thumb_position),
    )


thumb_r_placer = thumb_placer([14, -26, 12], [-17.4, -11.8, -4.2])
thumb_m_placer = thumb_placer([8, -17, 22], [-36, -17.6, -12])
thumb_l_placer = thumb_placer([6, -5, 30], [-54, -26.5, -16])

thumb_bl_placer = thumb_placer([4, -10, 27], [-35.3, -38.2, -18])
thumb_br_placer = thumb_placer([4, -26, 18], [-15.8, -30.7, -11.6])

thumb_placement_fns = [
    thumb_r_placer,
    thumb_m_placer,
    thumb_l_placer,
    thumb_br_placer,
    thumb_bl_placer,
]


def thumbs_post_offsets(placer, post):
    if placer == thumb_br_placer:
        if post == square_idx_bl:
            return [3, 0, 0]
        if post == square_idx_tr:
            return [0, -3.8, 0]

    if placer == thumb_r_placer:
        if post == square_idx_bl:
            return [20, 0, 0]
        if post == square_idx_tl:
            return [4, 0, 0]

    if placer == thumb_bl_placer:
        if post == square_idx_br:
            return [-3, 0, 0]
        if post == square_idx_tl:
            return [0, -3, 0]

    if placer == thumb_l_placer:
        if post == square_idx_br:
            return [-10.8, 0, -0.5]

    if placer == thumb_m_placer:
        if post == square_idx_tr:
            return [-2, 0, 0]

    return None


def thumb_wall(place_fn1, idx1, place_fn2, idx2, **kwargs):
    d1 = thumbs_post_offsets(place_fn1, idx1)
    d2 = thumbs_post_offsets(place_fn2, idx2)

    return wall_connect_from_placer(
        place_fn1, idx1, place_fn2, idx2, delta1=d1, delta2=d2, **kwargs
    )


def get_offset_thumb_placer(placer, idx, shape):
    delta = thumbs_post_offsets(placer, idx)
    return placer(make_offsetter(idx, delta)(shape))


def thumb_walls():
    return union(
        thumb_wall(thumb_bl_placer, square_idx_bl, thumb_bl_placer, square_idx_br),
        thumb_wall(thumb_bl_placer, square_idx_br, thumb_br_placer, square_idx_bl),
        thumb_wall(thumb_br_placer, square_idx_bl, thumb_br_placer, square_idx_br),
        thumb_wall(thumb_bl_placer, square_idx_bl, thumb_bl_placer, square_idx_tl),
        thumb_wall(
            thumb_bl_placer,
            square_idx_tl,
            thumb_l_placer,
            square_idx_br,
            connectors=False,
        ),
        thumb_wall(
            thumb_l_placer,
            square_idx_br,
            thumb_l_placer,
            square_idx_bl,
            connectors=False,
        ),
        thumb_wall(thumb_l_placer, square_idx_bl, thumb_l_placer, square_idx_tl),
        thumb_wall(thumb_l_placer, square_idx_tl, thumb_l_placer, square_idx_tr),
        thumb_wall(thumb_l_placer, square_idx_tr, thumb_m_placer, square_idx_tl),
        thumb_wall(
            thumb_m_placer, square_idx_tl, thumb_m_placer, square_idx_tr, walls=False
        ),
        thumb_wall(
            thumb_m_placer, square_idx_tr, thumb_r_placer, square_idx_tl, walls=False
        ),
        thumb_wall(
            thumb_r_placer, square_idx_tl, thumb_r_placer, square_idx_tr, walls=False
        ),
        thumb_wall(
            thumb_r_placer, square_idx_tr, thumb_r_placer, square_idx_br, walls=False
        ),
        thumb_wall(thumb_br_placer, square_idx_br, thumb_br_placer, square_idx_tr),
        thumb_wall(
            thumb_r_placer,
            square_idx_bl,
            thumb_r_placer,
            square_idx_br,
            walls=False,
            connectors=False,
        ),
        hull(
            get_offset_thumb_placer(thumb_br_placer, square_idx_tr, top_dot),
            get_offset_thumb_placer(thumb_r_placer, square_idx_bl, top_dot),
            get_offset_thumb_placer(
                thumb_r_placer, square_idx_bl, switch_riser_raw_dot
            ),
        ),
        hull(
            get_offset_thumb_placer(thumb_br_placer, square_idx_tr, top_dot),
            get_offset_thumb_placer(
                thumb_br_placer, square_idx_tr, switch_riser_raw_dot
            ),
            get_offset_thumb_placer(
                thumb_r_placer, square_idx_bl, switch_riser_raw_dot
            ),
        ),
        hull(
            get_offset_thumb_placer(
                thumb_r_placer, square_idx_br, switch_riser_raw_dot
            ),
            get_offset_thumb_placer(
                thumb_r_placer, square_idx_bl, switch_riser_raw_dot
            ),
            get_offset_thumb_placer(
                thumb_br_placer, square_idx_tr, switch_riser_raw_dot
            ),
        ),
        bottom_hull(
            hull(
                get_offset_thumb_placer(
                    thumb_r_placer, square_idx_br, switch_riser_raw_dot
                ),
                get_offset_thumb_placer(
                    thumb_br_placer, square_idx_tr, switch_riser_raw_dot
                ),
            )
        ),
    )


def thumb_connectors():
    right_thumb_cover_sphere = get_offset_thumb_placer(
        thumb_r_placer, square_idx_bl, translate(0, 1.7, 0)(web_post)
    )

    left_thumb_cover_lower_sphere = get_offset_thumb_placer(
        thumb_bl_placer, square_idx_tl, translate(0, 2, 0)(web_post)
    )
    left_thumb_cover_upper_sphere = thumb_l_placer(translate(0, 0, 0)(web_post_br))

    br_tr_with_offset = thumb_br_placer(translate(0.8, 0.2, 0)(web_post_tr))

    thumb_l_special_point = get_offset_thumb_placer(
        thumb_l_placer, square_idx_bl, translate(10, 1.1, 0)(web_post)
    )

    return union(
        hull(
            thumb_m_placer(web_post_tr),
            thumb_m_placer(web_post_br),
            thumb_r_placer(web_post_tl),
            thumb_r_placer(web_post_bl),
        ),
        hull(
            thumb_m_placer(web_post_tl),
            thumb_l_placer(web_post_tr),
            thumb_m_placer(web_post_bl),
            thumb_l_placer(web_post_br),
            thumb_m_placer(web_post_bl),
        ),
        hull(
            thumb_bl_placer(web_post_tr),
            thumb_bl_placer(web_post_br),
            thumb_br_placer(web_post_tl),
            thumb_br_placer(web_post_bl),
        ),
        hull(
            thumb_bl_placer(web_post_tl),
            thumb_m_placer(web_post_bl),
            thumb_l_placer(web_post_br),
        ),
        hull(
            thumb_bl_placer(web_post_tl),
            thumb_bl_placer(web_post_tr),
            thumb_m_placer(web_post_bl),
        ),
        hull(
            thumb_bl_placer(web_post_tr),
            thumb_m_placer(web_post_bl),
            thumb_m_placer(web_post_br),
        ),
        hull(
            thumb_br_placer(web_post_tl),
            thumb_bl_placer(web_post_tr),
            thumb_m_placer(web_post_br),
        ),
        hull(
            thumb_m_placer(web_post_br),
            thumb_r_placer(web_post_bl),
            thumb_br_placer(web_post_tl),
        ),
        hull(
            thumb_r_placer(web_post_bl),
            thumb_br_placer(web_post_tl),
            br_tr_with_offset,
        ),
        hull(
            thumb_r_placer(web_post_bl), thumb_r_placer(web_post_br), br_tr_with_offset,
        ),
        # Right special connectors
        hull(
            right_thumb_cover_sphere,
            get_offset_thumb_placer(thumb_r_placer, square_idx_bl, web_post),
            thumb_r_placer(web_post_br),
            get_offset_thumb_placer(thumb_r_placer, square_idx_br, web_post),
        ),
        hull(
            right_thumb_cover_sphere,
            get_offset_thumb_placer(thumb_r_placer, square_idx_bl, web_post),
            get_offset_thumb_placer(
                thumb_br_placer, square_idx_tr, switch_riser_raw_dot
            ),
            br_tr_with_offset,
        ),
        # Left special connectors
        hull(
            thumb_l_placer(web_post_bl),
            get_offset_thumb_placer(thumb_l_placer, square_idx_bl, web_post),
            thumb_l_special_point,
        ),
        hull(
            thumb_l_placer(web_post_bl),
            thumb_l_placer(web_post_br),
            thumb_l_special_point,
        ),
        hull(
            left_thumb_cover_lower_sphere,
            get_offset_thumb_placer(thumb_bl_placer, square_idx_tl, web_post),
            thumb_bl_placer(web_post_tl),
        ),
        hull(
            left_thumb_cover_lower_sphere,
            thumb_bl_placer(web_post_tl),
            left_thumb_cover_upper_sphere,
        ),
        hull(
            left_thumb_cover_lower_sphere,
            left_thumb_cover_upper_sphere,
            thumb_l_special_point,
        ),
        hull(
            left_thumb_cover_lower_sphere,
            get_offset_thumb_placer(thumb_l_placer, square_idx_br, web_post),
            thumb_l_special_point,
            get_offset_thumb_placer(thumb_bl_placer, square_idx_tl, web_post),
        ),
    )


def thumb_to_body_connectors():
    return union(
        bottom_hull(
            hull(
                thumb_r_placer(
                    get_in_square(switch_riser_offset_square, square_idx_br)(
                        switch_riser_raw_dot
                    )
                ),
                place_on_grid(3, 2)(
                    get_in_square(switch_riser_offset_square, square_idx_bl)(
                        switch_riser_raw_dot
                    )
                ),
            )
        ),
        hull(
            thumb_r_placer(
                get_in_square(switch_riser_offset_square, square_idx_br)(
                    switch_riser_raw_dot
                )
            ),
            thumb_r_placer(
                get_in_square(switch_riser_offset_square, square_idx_tr)(
                    switch_riser_raw_dot
                )
            ),
            place_on_grid(3, 2)(
                get_in_square(switch_riser_offset_square, square_idx_bl)(
                    switch_riser_raw_dot
                )
            ),
        ),
        hull(
            thumb_r_placer(
                get_in_square(switch_riser_offset_square, square_idx_tr)(
                    switch_riser_raw_dot
                )
            ),
            place_on_grid(3, 2)(
                get_in_square(switch_riser_offset_square, square_idx_bl)(
                    switch_riser_raw_dot
                )
            ),
            place_on_grid(2, 1)(
                get_in_square(switch_riser_offset_square, square_idx_br)(
                    switch_riser_raw_dot
                )
            ),
        ),
        hull(
            thumb_r_placer(
                get_in_square(switch_riser_offset_square, square_idx_tr)(
                    switch_riser_raw_dot
                )
            ),
            thumb_r_placer(
                get_in_square(switch_riser_offset_square, square_idx_tl)(
                    switch_riser_raw_dot
                )
            ),
            place_on_grid(2, 1)(
                get_in_square(switch_riser_offset_square, square_idx_br)(
                    switch_riser_raw_dot
                )
            ),
        ),
        bottom_hull(
            hull(
                thumb_m_placer(
                    get_in_square(switch_riser_offset_square, square_idx_tl)(
                        switch_riser_raw_dot
                    )
                ),
                place_on_grid(2, 0)(
                    get_in_square(switch_riser_offset_square, square_idx_bl)(
                        switch_riser_raw_dot
                    )
                ),
            )
        ),
        hull(
            thumb_m_placer(
                get_in_square(switch_riser_offset_square, square_idx_tl)(
                    switch_riser_raw_dot
                )
            ),
            place_on_grid(2, 0)(
                get_in_square(switch_riser_offset_square, square_idx_bl)(
                    switch_riser_raw_dot
                )
            ),
            place_on_grid(2, 0)(
                get_in_square(switch_riser_offset_square, square_idx_br)(
                    switch_riser_raw_dot
                )
            ),
        ),
    )


def thumb_switches():
    return union(*[fn(single_switch) for fn in thumb_placement_fns])


def filled_thumb_switches():
    return union(*[fn(filled_switch) for fn in thumb_placement_fns])


def bottom_edge_at_position(row, col):
    yield row, col, square_idx_bl
    yield row, col, square_idx_br


def bottom_edge_iterator():
    for col in range(NUM_COLS):
        row = num_rows_for_col(col) - 1

        yield from bottom_edge_at_position(row, col)
    return


def thumb_spots():
    return [
        [thumb_l_placer, square_idx_tl],
        [thumb_l_placer, square_idx_tr],
        [thumb_m_placer, square_idx_tl],
        [thumb_m_placer, square_idx_tr],
        [thumb_r_placer, square_idx_tl],
        [thumb_r_placer, square_idx_tr],
        [thumb_r_placer, square_idx_br],
        [thumb_br_placer, square_idx_br],
    ]


def thumb_caps():
    return union(*[fn(sa_cap) for fn in thumb_placement_fns[-2:]])


def blocker():
    size = 500
    shape = cube(size, size, size)
    shape = translate(-size / 2, -size / 2, -size)(shape)
    return shape


screw_insert_height = 4.2
screw_insert_bottom_radius = 5.31 / 2.0
screw_insert_top_radius = 5.1 / 2

screw_insert_width = 2
bottom_height = 2

screw_insert_outer = translate(0, 0, bottom_height)(
    cylinderr1r2(
        screw_insert_bottom_radius + screw_insert_width,
        screw_insert_top_radius + screw_insert_width,
        screw_insert_height + screw_insert_width,
    )
)
screw_insert_inner = translate(0, 0, bottom_height)(
    cylinderr1r2(
        screw_insert_bottom_radius, screw_insert_top_radius, screw_insert_height
    )
)


def screw_insert(col, row, shape, ox, oy):
    postiion = point_on_grid(row, col, 0, 0, 0)
    postiion[2] = 0
    postiion[0] += ox
    postiion[1] += oy
    return translate(*postiion)(shape)


def screw_insert_all_shapes(shape):
    return union(
        screw_insert(2, 0, shape, -5.3, 5.9), # On the same side as the usb hole
        screw_insert(NUM_COLS - 1, 0, shape, 6.7, 5.5), # Upper pinky corner
        screw_insert(NUM_COLS - 1, num_rows_for_col(NUM_COLS - 1), shape, 6.8, 14.4), # Lower pinky corner
        screw_insert(0, 0, shape, -6.2, -8), # near the TRRS hole
        screw_insert(1, MAX_NUM_ROWS + 1, shape, -9.8, 3.4), # Thumb inwards
        screw_insert(0, MAX_NUM_ROWS - 1, shape, -17.4, -2), # Thumb outwards
    )


TRRS_HOLDER_SIZE = [6.0, 14.0, 7.0]
TRRS_HOLE_SIZE = [2.6, 10.0]
TRRS_HOLDER_THICKNESS = 2.5
TRRS_FRONT_THICKNESS = 1.8


def trrs_key_holder_position():
    base_place = point_on_grid(0, 0, 0, KEYSWITCH_WIDTH / 2, 0)
    return [base_place[0] - 5, base_place[1] + 1.43, 13.0]


def trrs_holder():
    x, y, z = TRRS_HOLDER_SIZE
    shape = cube(
        x + TRRS_HOLDER_THICKNESS,
        y + TRRS_FRONT_THICKNESS,
        z + TRRS_HOLDER_THICKNESS - 3 ,
    )

    placed_shape = translate(
        -x / 2 - 1,
        -y,
        -(z / 3 + TRRS_HOLDER_THICKNESS),
    )(shape)

    blocker = cube(x + TRRS_HOLDER_THICKNESS, 5, 5)
    blocker = translate(-x, -y - 2 * TRRS_HOLDER_THICKNESS, -z + 2)(blocker)

    return translate(*trrs_key_holder_position())(
        union(
            placed_shape,
            blocker,
        )
    )


def trrs_holder_hole():
    x, y, z = TRRS_HOLDER_SIZE
    rect_hole = cube(x, y + 2, z)
    rect_hole = translate(
        -x / 2, -y - 1.3 , -z / 2,
    )(rect_hole)

    rect2 = cube(x, y + 1, z)
    rect2 = translate(
        -x / 2, -y - 1.5, -z + 7,
    )(rect2)

    cylinder_hole = cylinder(*TRRS_HOLE_SIZE, segments=30)
    cylinder_hole = rotate_x(90)(cylinder_hole)
    cylinder_hole = translate(0, 5, 0)(cylinder_hole)

    return translate(
        *trrs_key_holder_position()
    )(
        union(
            rect_hole,
            # union(
            #     rect_hole,
            #     rect2,
            # ),
            cylinder_hole
        )
    )


usb_holder_hole_dims = [9, 8, 3.5]
usb_holder_thickness = 0.5


def usb_holder_position():
    base_place = point_on_grid(0, 0, 0, KEYSWITCH_WIDTH / 2, 0)
    return [base_place[0] + 8, base_place[1] + 2, 4]


def usb_holder_rim():
    base_shape = cube(
        usb_holder_hole_dims[0] + usb_holder_thickness * 2,
        usb_holder_thickness * 2,
        usb_holder_hole_dims[2] + usb_holder_thickness * 2,
    )

    placed_shape = translate(
        -usb_holder_hole_dims[0] / 2 - usb_holder_thickness,
        0,
        -usb_holder_hole_dims[2] / 2 - usb_holder_thickness,
    )(base_shape)

    return translate(*usb_holder_position())(placed_shape)


def usb_holder_hole():
    placed_shape = translate(
        -usb_holder_hole_dims[0] / 2,
        -usb_holder_hole_dims[1] / 2,
        -usb_holder_hole_dims[2] / 2,
    )(cube(*usb_holder_hole_dims))

    return translate(*usb_holder_position())(placed_shape)


reset_switch_hole_height = 4.2
reset_switch_width = 6.0
reset_switch_hole_depth = 6.5
reset_switch_hole_back = 5.0

reset_switch_hole_radius = 4.3 / 2

reset_switch_total_depth = reset_switch_hole_depth + reset_switch_hole_back
reset_switch_total_height = reset_switch_hole_height + 2 * bottom_height


def unplaced_reset_switch_body():
    shape = cube(
        reset_switch_width,
        reset_switch_total_depth,
        reset_switch_total_height,
        center=True,
    )
    return translate(0, 0, reset_switch_total_height / 2)(shape)


def unplaced_reset_switch_body_hole():
    rect = translate(
        0, -reset_switch_hole_back / 2.0, reset_switch_hole_height / 2.0 + bottom_height
    )(
        cube(
            reset_switch_width + 0.2,
            reset_switch_hole_depth,
            reset_switch_hole_height,
            center=True,
        )
    )
    cyl = translate(0, -reset_switch_hole_back / 2, bottom_height / 2)(
        cylinder(reset_switch_hole_radius, bottom_height, center=True)
    )

    return union(rect, cyl)


def place_reset_switch_shape(shape):
    base_point = point_on_grid(1, 1, 0, 0, 0)

    return translate(base_point[0], base_point[1], 0)(shape)


reset_switch_body = place_reset_switch_shape(unplaced_reset_switch_body())
reset_switch_body_hole = place_reset_switch_shape(unplaced_reset_switch_body_hole())


def right_shell():
    global SHOULD_INCLUDE_RISERS
    SHOULD_INCLUDE_RISERS = True

    # cover = translate(-60, 20, 0)(cube(15, 15, 20))

    full_proto = difference(
        union(
            all_switches(),
            connectors(),
            case_walls(),
            screw_insert_all_shapes(screw_insert_outer),
            # all_caps(),
            thumb_switches(),
            thumb_walls(),
            thumb_connectors(),
            # thumb_caps(),
            thumb_to_body_connectors(),

            trrs_holder(),
            usb_holder_rim(),
        ),
        union(
            blocker(),
            screw_insert_all_shapes(screw_insert_inner),
            trrs_holder_hole(),
            usb_holder_hole(),
        ),
    )

    # return intersection(cover, full_proto)
    return full_proto


def left_shell():
    return flip_lr()(right_shell())


screw_head_height = 1.0
screw_head_radius = 5.5 / 2
screw_hole_radius = 1.7


def wall_shape():
    walls_3d = union(case_walls(), thumb_walls(), thumb_to_body_connectors(),)

    walls_2d = offset(0.4)(project(cut=True))(translate(0, 0, -0.1)(walls_3d))

    return walls_2d


def model_outline():
    global SHOULD_INCLUDE_RISERS
    SHOULD_INCLUDE_RISERS = True

    solid_bottom = project()(
        union(
            filled_switches(),
            connectors(),
            case_walls(),
            filled_thumb_switches(),
            thumb_walls(),
            thumb_connectors(),
            thumb_to_body_connectors(),
        )
    )

    bottom_2d = difference(solid_bottom, wall_shape())
    return extrude_linear(bottom_height)(bottom_2d)


weight_width = 40
weight_height = 40

weight_z_offset = 0.5


def weight_shape():
    return translate(0, 0, 1.5 + weight_z_offset)(
        cube(weight_width, weight_height, 3, center=True)
    )


def weight_shape_vert():
    return rotate_z(90)(weight_shape())


def place_weight_hole(x, y):
    return translate(x, y, 0)(weight_shape())


def bottom_weight_cutouts():
    shapes = []

    base_point = point_on_grid(1, NUM_COLS - 1, 0, 0, 0)
    base_x = base_point[0] - 13.6
    base_y = base_point[1]

    space_between = 0
    offsets = space_between + weight_width

    weight_1 = place_weight_hole(base_x, base_y + 4)
    weight_2 = place_weight_hole(base_x - offsets, base_y + 8)

    weight_1 = rotate_z(-3)(weight_1)
    weight_2 = rotate_z(-3)(weight_2)

    shapes.append(weight_1)
    shapes.append(weight_2)

    return union(*shapes)


reset_switch_body = place_reset_switch_shape(unplaced_reset_switch_body())
reset_switch_body_hole = place_reset_switch_shape(unplaced_reset_switch_body_hole())

def liatris_stop_block():
    base_place = point_on_grid(0, 0, 0, KEYSWITCH_WIDTH / 2, 0)
    x = base_place[0] + 8
    y = base_place[1] - 30
    z = base_place[2] - 26

    outer = cube(23, 4, 5)
    outer = translate(-23/2, -3.5, -3/2)(outer)

    inner = cube(19, 4, 6)
    inner = translate(-25/2 + 3, -2, -3/2)(inner)

    inner2 = cube(13, 5, 6)
    inner2 = translate(-25/2 + 6, -7, -3/2)(inner2)

    return translate(x, y, z)(
        difference(
            difference(outer, inner),
            inner2
        )
    )


def bottom_plate():
    return difference(
        union(
            model_outline(),
            # reset_switch_body,
            # screw_insert_all_shapes(screw_insert_outer),
            liatris_stop_block(),
        ),
        union(
            screw_insert_all_shapes(
                cylinderr1r2(screw_head_radius, screw_head_radius, screw_head_height + 2)
            ),
            screw_insert_all_shapes(
                cylinderr1r2(screw_hole_radius, screw_hole_radius, bottom_height + 2)
            ),
            bottom_weight_cutouts(),
            # reset_switch_body_hole,
        ),
    )


def left_bottom_plate():
    return flip_lr()(bottom_plate())


def thumb_corner():
    global SHOULD_INCLUDE_RISERS
    SHOULD_INCLUDE_RISERS = False
    return difference(
        union(
            thumb_switches(),
            thumb_walls(),
            thumb_connectors(),
            # thumb_caps(),
            thumb_to_body_connectors(),
        ),
        union(blocker(),),
    )


def write_test():
    render_to_file(right_shell(), "things/right.scad")
    render_to_file(left_shell(), "things/left.scad")
    render_to_file(bottom_plate(), "things/right_bottom_plate.scad")
    render_to_file(left_bottom_plate(), "things/left_bottom_plate.scad")


def run():
    write_test()
    print("done")


if __name__ == "__main__":
    run()
