import random

class TerrainMaker(object):
    def __init__(self, width=100, height=100):
        self.Width = width
        self.Height = height
        self.StoredTerrainsDict = {}
    
    def storeTerrain(self, Terrain):
        self.StoredTerrainsDict[Terrain.Name] = Terrain
    
    def writeToCSV(self, Terrain, filename=""):
        if filename == "":
            filename = f"terrain_gen[{self.Width}, {self.Height}]" + ".csv"
        elif filename[-4:] != ".csv":
            filename += ".csv"
        
        with open(filename, "w+") as file:
            for row in range(Terrain.Height):
                for col in range(Terrain.Width):
                    file.write(str(Terrain.TerrainMatrix[row][col]) + ", ")
                file.write('\n')

    def makeRandomTerrain2D(self, floor=0, ceiling=100, terrainName=""):
        """
        Generates a 2D array of random 3-D topographic-style values
        """
        if terrainName == "":
            terrainName = f"random [{self.Width}, {self.Height}]"
        m = []

        for _ in range(self.Height):
            row = []
            for _ in range(self.Width):
                alt = random.random() * (ceiling - floor)
                row.append(alt)
            m.append(row)
        
        return Terrain(terrainName, m)
    
    def makePeakTerrain(self, floor=0, ceiling=100, terrainName=""):
        """
        Generates a 2-D array of 3-D topographic-style values with a center peak
        """
        if terrainName == "":
            terrainName = f"peak [{self.Width}, {self.Height}]"
        m = []
        halfH = (self.Height / 2)
        halfW = (self.Width / 2)
        for i in range(self.Height):
            row = []
            rowFactor = 1 - abs((halfH - i)) / halfH
            for j in range(self.Width):
                colFactor = 1 - abs((halfW - j)) / halfW
                peakFactor = colFactor * rowFactor
                alt = (ceiling - floor) * peakFactor
                row.append(alt)
            m.append(row)
        
        return Terrain(terrainName, m)


class Terrain(object):
    def __init__(self, name, terrainMatrix):
        self.Name = name
        self.TerrainMatrix = terrainMatrix
        self.Width = len(terrainMatrix[0])
        self.Height = len(terrainMatrix)
    
    def getPointForm(self):
        cols = []

        for i in range(self.Height):
            for j in range(self.Width):
                row = []
                row.append(j)
                row.append(i)
                row.append(self.TerrainMatrix[i][j])
                cols.append(row)
        
        return cols

    def preview(self, numRows=10, numCols=10):
        r = numRows if numRows < self.Width else self.Width
        c = numCols if numCols < self.Height else self.Height
        for i in range(r):
            print("[", end="")
            for j in range(c-1):
                print(round(self.TerrainMatrix[i][j]), end=", ")
            if i != r-1:
                print(f"{round(self.TerrainMatrix[i][c])}],\n", end="")
            else:
                print(f"{round(self.TerrainMatrix[i][c])}]")


def makeTerrainPlotHtml():
    import numpy as np
    import plotly.offline as go_offline
    import plotly.graph_objects as go

    file=open('H:\\dev\\pile\\src\\data_gen\\survey_data.csv','r')
    lines=file.readlines()
    n_line=len(lines)
    x=[]
    y=[]
    z=[]
    for i in range(1,n_line):
        split_line=lines[i].split(",")
        x.append(float(split_line[0].rstrip()))
        y.append(float(split_line[1].rstrip()))
        z.append(float(split_line[2].rstrip()))
    
    def distance(x1,y1,x2,y2):
        d = ((x1-x2)**2 + (y1-y2)**2)**(1/2)
        return d

    #CREATING IDW FUNCTION
    def idw_npoint(xz, yz, n_point, p):
        r=10 #block radius iteration distance
        nf=0
        while nf<=n_point: #will stop when np reaching at least n_point
            x_block=[]
            y_block=[]
            z_block=[]
            r +=10 # add 10 unit each iteration
            xr_min=xz-r
            xr_max=xz+r
            yr_min=yz-r
            yr_max=yz+r
            for i in range(len(x)):
                # condition to test if a point is within the block
                if ((x[i]>=xr_min and x[i]<=xr_max) and (y[i]>=yr_min and y[i]<=yr_max)):
                    x_block.append(x[i])
                    y_block.append(y[i])
                    z_block.append(z[i])
            nf=len(x_block) #calculate number of point in the block
        
        #calculate weight based on distance and p value
        w_list=[]
        for j in range(len(x_block)):
            d=distance(xz,yz,x_block[j],y_block[j])
            if d>0:
                w=1/(d**p)
                w_list.append(w)
            else:
                w_list.append(0) #if meet this condition, it means d<=0, weight is set to 0
        
        #check if there is 0 in weight list
        w_check=0 in w_list
        if w_check==True:
            idx=w_list.index(0) # find index for weight=0
            z_idw=z_block[idx] # set the value to the current sample value
        else:
            wt=np.transpose(w_list)
            z_idw=np.dot(z_block,wt)/sum(w_list) # idw calculation using dot product
        return z_idw

    # POPULATE INTERPOLATION POINTS
    n=100 #number of interpolation point for x and y axis
    x_min=min(x)
    x_max=max(x)
    y_min=min(y)
    y_max=max(y)
    w=x_max-x_min #width
    h=y_max-y_min #length
    wn=w/n #x interval
    hn=h/n #y interval

    #list to store interpolation point and elevation
    y_init=y_min
    x_init=x_min
    x_idw_list=[]
    y_idw_list=[]
    z_head=[]
    for i in range(n):
        xz=x_init+wn*i
        yz=y_init+hn*i
        y_idw_list.append(yz)
        x_idw_list.append(xz)
        z_idw_list=[]
        for j in range(n):
            xz=x_init+wn*j
            z_idw=idw_npoint(xz,yz,5,1.5) #min. point=5, p=1.5
            z_idw_list.append(z_idw)
        z_head.append(z_idw_list)

    # CREATING 3D TERRAIN
    fig = go.Figure()
    fig.add_trace(go.Surface(z=z_head,x=x_idw_list,y=y_idw_list))
    fig.update_layout(scene=dict(aspectratio=dict(x=2, y=2, z=0.5), xaxis=dict(range=[x_min,x_max],), yaxis=dict(range=[y_min, y_max])))
    go_offline.plot(fig, filename='H:\\dev\\pile\\src\\data_gen\\3d_terrain.html', validate=True, auto_open=False)


makeTerrainPlotHtml()
