import math
import os
import time

class Point2D:
    x = 0
    y = 0
    
    def __init__(self, x, y, value = 0):
        self.x = x
        self.y = y
        
    def round(self):
        self.x = round(self.x)
        self.y = round(self.y)
        return self
    
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

def print_grid_tight(grid):
    width, height = len(grid), len(grid[0])
    for y in range(height):
        for x in range(width):
            if(grid[x][y] == 1):
                blockchar = u"\u2588"
                print((f"\033[38;5;{250}m{blockchar*2}"), end = "")
            else:
                print(("  "), end = "")
        print()


#def print_grid(grid):
#    width, height = len(grid[0]), len(grid)
#    for x in range(height):
#        print(("+" + "----") * width + "+")
#        print("|", end = "")
#        for y in range(width):
#            if(grid[x][y] == 1):
#                print((" " + u"\u2588" + u"\u2588" + " ")+ "|", end = "")
#            else:
#                print(("    ") + "|", end = "")
#            if(y ==  width-1):
#                print()
#    print(("+" + "----") * width + "+")


grid = [[0 for y in range(32)] for x in range(64)]



def shift_right(grid):
    width, height = len(grid[0]), len(grid)
    for x in range(height):
        for y in range(width-1, 0, -1):
            grid[x][y] = grid[x][y-1]
            

def shift_left(grid):
    width, height = len(grid[0]), len(grid)
    for x in range(height):
        for y in range(1, width):
            grid[x][y-1] = grid[x][y]
            

def shift_down(grid):
    width, height = len(grid[0]), len(grid)
    for x in range(height-1, 0, -1):
        for y in range(width):
            grid[x][y] = grid[x-1][y]

def shift_up(grid):
    width, height = len(grid[0]), len(grid)
    for x in range(1, height):
        for y in range(width):
            grid[x-1][y] = grid[x][y]
         
            
# line drawing funtion
def draw_line(grid, start: Point2D, end: Point2D, color):
    dx = abs(end.x - start.x)
    dy = abs(end.y - start.y)
    length = dx if dx > dy else dy # diagonal_distance
    if length > 0:
        for i in range(length+1):
            t = i / length
            x = start.x + int(t * (end.x - start.x)) # linear interpolation
            y = start.y + int(t * (end.y - start.y)) # linear interpolation
            grid[x][y] = color
    else:
        grid[start.x][start.y] = color
        


# triangle drawing helper functions
def fillBottomFlatTriangle(grid, point1, point2, point3, color):
    invslope1 = (point2.x - point1.x) / (point2.y - point1.y)
    invslope2 = (point3.x - point1.x) / (point3.y - point1.y)
    
    curx1 = point1.x
    curx2 = point1.x
    
    for scanlineY in range(point1.y, point2.y+1):
        draw_line(grid, Point2D(int(curx1), scanlineY), Point2D(int(curx2), scanlineY), color)
        curx1 += invslope1
        curx2 += invslope2

def fillTopFlatTriangle(grid, point1, point2, point3, color):
    invslope1 = (point3.x - point1.x) / (point3.y - point1.y)
    invslope2 = (point3.x - point2.x) / (point3.y - point2.y)

    curx1 = point3.x
    curx2 = point3.x

    for scanlineY in range(point3.y, point1.y-1, -1):
        draw_line(grid, Point2D(int(curx1), scanlineY), Point2D(int(curx2), scanlineY), color)
        curx1 -= invslope1
        curx2 -= invslope2

def sortPoints2DAscendingByY(points: tuple[Point2D]) -> tuple[Point2D]:
    return tuple(sorted(points, key=lambda point: point.y))


# triangle drawing function
def draw_triangle(grid, point1: Point2D, point2: Point2D, point3: Point2D, color):
    # at first sort the three vertices by y-coordinate ascending so point1 is the topmost vertex
    (point1, point2, point3) = sortPoints2DAscendingByY((point1, point2, point3))

    # here we know that point1.y <= point2.y <= point3.y
    # check for trivial case of bottom-flat triangle
    if point2.y == point3.y:
        fillBottomFlatTriangle(grid, point1, point2, point3, color)
    # check for trivial case of top-flat triangle
    elif point1.y == point2.y:
        fillTopFlatTriangle(grid, point1, point2, point3, color)
    else:
        # general case - split the triangle in a topflat and bottom-flat one
        point4 = Point2D(
            int(point1.x + ((float)(point2.y - point1.y) / (float)(point3.y - point1.y)) * (point3.x - point1.x)), point2.y)
        fillBottomFlatTriangle(grid, point1, point2, point4, color)
        fillTopFlatTriangle(grid, point2, point4, point3, color)



def pos_on_circle(center: Point2D, radius: float, angle: float) -> Point2D:
    angle = angle * 3.14159 / 180
    x = center.x + radius * math.cos(angle)
    y = center.y + radius * math.sin(angle)
    return Point2D(x, y)


# function for getting all the points on a circle
def get_circle_points(grid, center: Point2D, radius) -> list[Point2D]:
    points = list()
    for r in range(360):
        newPoint = pos_on_circle(center, radius, r).round()
        if not newPoint in points:
            points.append(newPoint)
            
    return points

points: list[Point2D] = get_circle_points(grid, Point2D(16,16), 6)


#flag is next step will be shift left
direction = 0
hand = Point2D
t = 0

# top left corner
grid[0][0] = 1
grid[0][1] = 1
grid[1][0] = 1

# top right corner
grid[62][0] = 1
grid[63][0] = 1
grid[63][1] = 1

# lower right corner
grid[62][31] = 1
grid[63][31] = 1
grid[63][30] = 1

# lower left corner
grid[1][31] = 1
grid[0][31] = 1
grid[0][30] = 1


draw_triangle(grid, Point2D(5,5), Point2D(5,10), Point2D(10,10), 1)
#draw_triangle(grid, Point2D(13,4), Point2D(18,12), Point2D(20,4), 1)
#draw_triangle(grid, Point2D(20,18), Point2D(29,14), Point2D(25,25), 1)

print_grid_tight(grid)

def next_tick_circle():
    # clear screen
    os.system('cls||clear')
    
    #update state in a square movement
    #global direction
    #match direction:
    #    case 3:
    #        shift_up(grid)
    #        direction = 0
    #    case 2:
    #        shift_left(grid)
    #        direction += 1
    #    case 1:
    #        shift_down(grid)
    #        direction += 1
    #    case 0:
    #        shift_right(grid)
    #        direction += 1
    global hand
    global t
    global points
    
    hand = points[t]
    color = 0
    
    if grid[hand.x][hand.y] is 0:
        color = 1
    
    draw_line(grid, Point2D(16,16), hand, color)
    
    t = t+1
    if t is len(points):
        t = 0
    
    
    # print current state
    print_grid_tight(grid)
    
    
    


while True:
    time.sleep(0.1)
    next_tick_circle()
    


#grayscale from 232(black) to 255(white)
print(f"\033[38;5;{250}m {num2} \033[0;0m\n")

# printing colors
# colorspep8.py
def colors_16(color_):
    return("\033[2;{num}m {num} \033[0;0m".format(num=str(color_)))


def colors_256(color_):
    num1 = str(color_)
    num2 = str(color_).ljust(3, ' ')
    if color_ % 16 == 0:
        return(f"\033[38;5;{num1}m {num2} \033[0;0m\n")
    else:
        return(f"\033[38;5;{num1}m {num2} \033[0;0m")

print("The 16 colors scheme is:")
print(' '.join([colors_16(x) for x in range(30, 38)]))
print("\nThe 256 colors scheme is:")
print(' '.join([colors_256(x) for x in range(256)]))