import random as rd
from gdpc import Editor, Block, geometry
from utils.functions import *
from utils.Enums import BALCONY_BORDER_RADIUS,COLLUMN_STYLE
from buildings.geometry.Point import Point
from buildings.geometry.Vertice import Vertice
from buildings.elements.Window import Window

class Balcony:
    def __init__(self, 
                 rdata, 
                 max_width : int, 
                 windows : Window, 
                 collumn_style : COLLUMN_STYLE):
        self.rdata = rdata
        self.windows = windows
        self.max_width = max_width
        self.collumn_style = collumn_style
        self.length = self.get_len()
        self.has_multiple = self.has_multiple()
        self.has_details = self.has_details()
        self.border_radius = self.has_border_radius()
        self.follow_window = self.follow_window()
        self.structure = self.get_structures()
        self.editor, self.materials = None,None
        
    def build(self, editor : Editor, materials : list[str]):
        self.editor = editor
        self.materials = materials
        for s in self.structure:
            s.fill(editor, materials[0])
            self.build_rembard(s)
            self.build_details(s)
            self.build_border_radius(s)
            
    def build_rembard(self, s : Vertice):
        geometry.placeCuboid(self.editor,(s.point1.x,1,-1),(s.point1.x,1,-self.length),Block(self.materials[3]))
        geometry.placeCuboid(self.editor,(s.point2.x,1,-1),(s.point2.x,1,-self.length),Block(self.materials[3]))
        geometry.placeCuboid(self.editor,(s.point1.x,1,-self.length),(s.point2.x,1,-self.length),Block(self.materials[3]))
        
    def build_details(self, s : Vertice):
        if not self.has_details: return
        geometry.placeCuboid(self.editor,(s.point1.x,0,-1),(s.point1.x,0,-self.length),Block(self.materials[4], {"facing": "east", "half": "top"}))
        geometry.placeCuboid(self.editor,(s.point2.x,0,-1),(s.point2.x,0,-self.length),Block(self.materials[4], {"facing": "west", "half": "top"}))
        geometry.placeCuboid(self.editor,(s.point1.x,0,-self.length),(s.point2.x,0,-self.length),Block(self.materials[4], {"facing": "south", "half": "top"}))
      
    def build_border_radius(self, s : Vertice):
        if self.border_radius == BALCONY_BORDER_RADIUS.NONE: return
        
        geometry.placeCuboid(self.editor,(s.point1.x,0,-self.length),(s.point1.x,1,-self.length),Block("air"))
        geometry.placeCuboid(self.editor,(s.point2.x,0,-self.length),(s.point2.x,1,-self.length),Block("air"))
        self.editor.placeBlock((s.point1.x+1,1,-self.length+1), Block(self.materials[3]))
        self.editor.placeBlock((s.point2.x-1,1,-self.length+1), Block(self.materials[3]))
        
        if self.has_details:
            self.editor.placeBlock((s.point1.x,0,-self.length+1), Block(self.materials[4], {"facing": "south", "half": "top"}))
            self.editor.placeBlock((s.point1.x+1,0,-self.length), Block(self.materials[4], {"facing": "east", "half": "top"}))
            self.editor.placeBlock((s.point2.x,0,-self.length+1), Block(self.materials[4], {"facing": "south", "half": "top"}))
            self.editor.placeBlock((s.point2.x-1,0,-self.length), Block(self.materials[4], {"facing": "west", "half": "top"}))
            
            if self.border_radius == BALCONY_BORDER_RADIUS.FULL:
                self.editor.placeBlock((s.point1.x+1,0,-self.length+1), Block(self.materials[4], {"facing": "east", "half": "top"}))
                self.editor.placeBlock((s.point2.x-1,0,-self.length+1), Block(self.materials[4], {"facing": "west", "half": "top"}))
        
    def get_structures(self) -> list[Vertice]:
        # structures are the base shape of the balcony
        attach_points = self.get_attach_points()
        len_attach_points = len(attach_points)-1
        min_wid = self.rdata["size"]["min_width"]
        min_gap = self.rdata["multiple"]["min_gap"]
        growth_chance = self.rdata["growth"]
        midpoint = len_attach_points//2
        x1,x2 = midpoint, len_attach_points - midpoint
        
        structures = []
        centered = True
        while x1 > 0:
            x1 -= 1
            x2 += 1 if centered else 0
            leng = attach_points[x2] - attach_points[x1] - 1
            
            if x1 == 0: 
                if leng >= min_wid: self.append_structure(structures, x1, x2, attach_points, len_attach_points, centered)
                break
            if leng < min_wid: continue
            
            if growth_chance < rd.random(): 
                self.append_structure(structures, x1, x2, attach_points, len_attach_points, centered)
                    
                if not self.has_multiple: break
                else:
                    centered = False
                    if attach_points[x1]-min_wid < min_gap: break
                    gap  = rd.randint(min_gap, attach_points[x1]-min_wid)
                    x2 = x1-gap
                    x1 = x2-min_wid+1
                           
        return structures
                
    def get_attach_points(self) -> list[int]:
        # points where the structures can start/finish
        padding = 0 if self.collumn_style.value < 2 else 1 # collumn_style < 2 = no outer collumns
        points = [i + padding for i in range(self.max_width)]
        if self.follow_window:
            pad = self.windows.padding
            for w in self.windows.windows:
                for i in range(pad+w.x1, pad+w.x2+1):
                    points.remove(i)
                    
        return points
    
    def create_structure(self, x1 : int, x2 : int) -> Vertice:
        return Vertice(Point(x = x1), Point(x = x2,z = -self.length))
    
    def append_structure(self, structures : list[Vertice], x1 : int, x2 : int, attach_points : list[int], len_attach_points : int, centered : bool):
        structures.append(self.create_structure(attach_points[x1], attach_points[x2]))
        if not centered:
            structures.append(self.create_structure(attach_points[len_attach_points-x1], attach_points[len_attach_points-x2]))
    
    def follow_window(self) -> bool:
        return not self.windows.ypadding > 3
        
    def has_multiple(self) -> bool:
        if self.max_width < self.rdata["multiple"]["min_width"]: return False
        return self.rdata["multiple"]["proba"] >= rd.random()
    
    def has_details(self) -> bool:
        return self.rdata["details"] >= rd.random()
    
    def has_border_radius(self) -> bool:
        if self.length < 2: return BALCONY_BORDER_RADIUS.NONE
        return select_random(self.rdata["border_radius"], BALCONY_BORDER_RADIUS)
    
    def get_len(self) -> int:
        return rd.randint(self.rdata["size"]["min_len"], self.rdata["size"]["max_len"])