# -*- coding: utf-8 -*-
"""
Created on Tue Dec 26 14:52:37 2017

@author: Bri
"""

import numpy as np
import random
import math
from PIL import Image, ImageDraw, ImageTk
from scipy.spatial import Voronoi
from src_tools import *

XDIM = 720
BORDERSCALE = 1/8

#  Best evaluated from 2.25 to 5.75
def sinRatio(x):
    return (math.sin(100/math.log10(x))+math.sin(1000/math.log10(6*x))-math.sin(1000/math.log10(x/10)))

#  Returns whether it crosses the x axis at or around here
def sinRound(x):
    if abs(sinRatio(x)) < 0.1:
        return True
    return False

def xRange(name):
    start = 2.25+(seedNum(name)*73 % 250)/100
    end = start+1
    return (start,end)

def yRange(name):
    start = 2.25+(seedNum(name)*113 % 250)/100
    end = start+1
    return (start,end)

def xStreets(x,name):
    xStart = xRange(name)[0]
    xReal = xStart+((x*1)/XDIM)
    if sinRound(xReal):
        return True
    return False

def yStreets(y,name):
    yStart = yRange(name)[0]
    yReal = yStart+((y*1)/XDIM)
    if sinRound(yReal):
        return True
    return False

def clamp(x,minimum,maximum):
    if x < minimum:
        return minimum
    elif x > maximum:
        return maximum
    else:
        return x

def exclus(g):
    if g < 0.01 and g > 0:
        return 0.01
    elif g > -0.01 and g < 0:
        return -0.01
    return g

def drawCircle(drawer,x,y,radius,color):
    x1 = x-radius
    x2 = x+radius
    y1 = y-radius
    y2 = y+radius
    drawer.ellipse([(x1,y1),(x2,y2)],color)

def enorm(a,b):
    j = abs(a**2) + abs(b**2)
    return math.sqrt(j)

def colAvg(c0,c1):
    cc = (math.floor((c0[0]+c1[0])/2),math.floor((c0[1]+c1[1])/2),math.floor((c0[2]+c1[2])/2))
    return cc

class StreetNode:
    def __init__(self,coords):
        self.x = coords[0]
        self.y = coords[1]
        self.neighbors = []
        self.type = None
        self.drawColor = (0,0,0)
    def dist(self,x,y):
        dx = abs(self.x-x)
        dy = abs(self.y-y)
        dist = math.sqrt((dx**2) + (dy**2))
        return dist
    def midpt(self,n):
        xx = (self.x + n.x)/2
        yy = (self.y + n.y)/2
        return (xx,yy)
    def edgept(self,nbr,seed):
        d = self.dist(nbr.x,nbr.y)
        if d == 0:
            d = 1
        dt = (seed*self.x*self.y*37.13529) % d
        t = dt/d
        xx = ((1-t)*self.x) + (t*nbr.x)
        yy = ((1-t)*self.y) + (t*nbr.y)
        n = StreetNode((xx,yy))
        return n

class Street:
    def __init__(self,n0,n1,w=2):
        self.nodes = [None,None]
        self.nodes[0] = n0
        self.nodes[1] = n1
        n0.neighbors.append(n1)
        n1.neighbors.append(n0)
        self.exists = 1
        self.width = w
    def streetDist(self,x,y):
        d = (self.nodes[0].dist(x,y)+self.nodes[1].dist(x,y))/2
        return abs(d)
    def length(self):
        return self.nodes[0].dist(self.nodes[1].x,self.nodes[1].y)
    def cull(self):
        self.nodes[1].x = self.nodes[0].x
        self.nodes[1].y = self.nodes[0].y
        self.exists = 0
    def drawSelf(self,drawer,col):
        drawCircle(drawer,self.nodes[0].x,self.nodes[0].y,self.width-1,col)
        drawer.line([(self.nodes[0].x,self.nodes[0].y),(self.nodes[1].x,self.nodes[1].y)],fill=col,width=self.width*2)
        drawCircle(drawer,self.nodes[1].x,self.nodes[1].y,self.width-1,col)

class Block:
    def __init__(self,m):
        self.myTown = m
        self.verts = []
        self.neighbors = []
        self.subblocks = []
        self.substreets = []
        self.type = None
        self.node = None
        self.col = (0,0,0)
    def centroid(self):
        if len(self.verts) != 0:
            xx = sum([n.x for n in self.verts])/len(self.verts)
            yy = sum([n.y for n in self.verts])/len(self.verts)
        else:
            xx = 0
            yy = 0
        self.x = xx
        self.y = yy
        return [self.x,self.y]
    def ccw(self,p0,p1,c):
        # Return 1 if p1 is counterclockwise from p0 with c as center; otherwise, return 0
        cx = c[0]
        cy = c[1]
        v0 = (p0.x-cx,p0.y-cy)
        v1 = (p1.x-cx,p1.y-cy)
        d0 = enorm(v0[0],v0[1])
        d1 = enorm(v1[0],v1[1])
        dp = (v0[0]*v1[0]) + (v0[1]*v1[1])
        if d0*d1 == 0:
            return 0
        q = clamp(dp/exclus(d0*d1),-1,1)
        ang = math.acos(q)
        if ang >= 0:
            return 1
        else:
            return 0
    def blockDist(self,x,y):
        self.centroid()
        dx = abs(self.x-x)
        dy = abs(self.y-y)
        dist = math.sqrt((dx**2) + (dy**2))
        return dist
    def neighborize(self,j):
        if j not in self.neighbors:
            self.neighbors.append(j)
        if self not in j.neighbors:
            j.neighbors.append(j)
    def sharedNeighbors(self,f):
        s = 0
        for p in self.verts:
            if p in f.verts:
                s += 1
        return s
    def orderccw(self):
        n = len(self.verts)
        for i in range(n):
            for j in range(n):
                aa = j
                bb = (j+1) % n
                if self.ccw(self.verts[aa],self.verts[bb],self.centroid()) == 0:
                    # bb not ccw from aa
                    t = self.verts[aa]
                    self.verts[aa] = self.verts[bb]
                    self.verts[bb] = t
        self.verts = list(reversed(self.verts))
    def polyArea(self):
        if len(self.verts) < 1:
            self.area = 0
            return 0
        self.orderccw()
        area = 0
        q = self.verts[-1]
        for p in self.verts:
            area += (p.x * q.y) - (p.y * q.x)
            q = p
        area = abs(area/2)
        self.area = area
        return area
    def drawSelf(self,drawer):
        dCol = self.col
        if len(self.verts) > 1:
            vts = [(p.x,p.y) for p in self.verts]
            drawer.polygon(vts,dCol,dCol)

class Town:
    def __init__(self,n,m,nom):
        self.myMap = m
        self.node = n
        self.name = nom
        self.mapName = "./generated/town_"+self.name+".gif"
        self.xDim = XDIM
        self.yDim = XDIM
        self.landColor = self.myMap.biomeColors[self.node.biome]
        self.streetColor = Tools.streetColor
        self.waterColor = Tools.waterColor
        cnt = 512
        sd = (self.node.x*73)+(self.node.y*37)
        random.seed(sd)
        verts = [[random.randint(0,self.xDim),random.randint(0,self.yDim)] for i in range(cnt)]
        verts.append([self.xDim/2,self.yDim/2])
        verts = np.asarray(verts)
        primVor = Voronoi(verts)
        self.streetNodes = [StreetNode(i) for i in primVor.vertices]
        self.blocks = [Block(self) for i in primVor.regions]
        for i in range(len(self.blocks)):
            b = self.blocks[i]
            b.col = self.landColor
            for j in range(len(primVor.regions[i])):
                index = primVor.regions[i][j]
                if index >= 0:
                    b.verts.append(self.streetNodes[index])
        self.streets = []
        for i in range(len(primVor.ridge_vertices)):
            in0 = primVor.ridge_vertices[i][0]
            in1 = primVor.ridge_vertices[i][1]
            newStreet = Street(self.streetNodes[in0],self.streetNodes[in1],3)
            self.streets.append(newStreet)
        if self.node.city != None:
            self.population = self.node.city.population
            self.radius = 32+(math.sqrt(self.population)*2)
        else:
            self.population = 0
            self.radius = -8
        self.x = self.xDim/2
        self.y = self.yDim/2
        self.neighborPts = []
        for n in self.node.neighbors:
            d = n.dist(self.node)
            scl = self.xDim/d
            dx = (n.x-self.node.x)*scl
            dy = (n.y-self.node.y)*scl
            xx = clamp(self.x + dx,0,self.xDim)
            yy = clamp(self.y + dy,0,self.yDim)
            s = StreetNode([xx,yy])
            if n.river != None:
                s.type = "river"
                s.drawCol = self.waterColor
            if n.bodyWater != None:
                s.type = "water"
                self.radius *= 1.15
                s.drawCol = self.waterColor
            else:
                s.drawCol = n.myMap.biomeColors[n.biome]
                #s.drawCol = colAvg(n.myMap.biomeColors[n.biome],self.landColor)
            self.neighborPts.append(s)
        for p in self.neighborPts:
            if p.type == "river":
                if self.node.river != None:
                    self.buildRiver(p,self.nearestBlock(self.x,self.y))
            else:
                for k in range(len(self.blocks)):
                    kk = self.blocks[k].centroid()
                    if (self.blocks[k].blockDist(p.x,p.y) < self.x):
                        self.blocks[k].type = p.type
                        self.blocks[k].col = p.drawCol
        for b in self.blocks:
            for f in self.blocks:
                if (b.sharedNeighbors(f) >= 2 and
                    b.type == "water"):
                    f.col = self.waterColor
        for f in self.blocks:
            if f.col == self.waterColor:
                f.type = "water"
        self.outskirts = []
        for k in range(len(self.blocks)):
            if (self.blocks[k].blockDist(self.x,self.y) > self.radius
                or self.blocks[k].type == "water"):
                self.outskirts.append(self.blocks[k])
        for i in self.blocks:
            for j in self.blocks:
                if i.sharedNeighbors(j) > 0:
                    i.neighborize(j)
        self.avgColors(6)
    def nearestBlock(self,xx,yy):
        d = self.xDim
        a = self.blocks[0]
        for k in self.blocks:
            dd = k.blockDist(xx,yy)
            if dd < d:
                d = dd
                a = k
        return a
    def buildRiver(self,n0,b1):
        curr = self.nearestBlock(n0.x,n0.y)
        bx = b1.centroid()[0]
        by = b1.centroid()[1]
        q = 0
        while curr != b1:
            q = q+1
            curr.type = "water"
            curr.col = self.waterColor
            t = curr
            d = self.xDim
            for f in self.blocks:
                if (curr.sharedNeighbors(f) >= 2 and f != curr
                and f.blockDist(bx,by) < d):
                    d = f.blockDist(bx,by)
                    t = f
            if t == self.nearestBlock(self.xDim/2,self.yDim/2) or q > 100:
                curr = b1
            else:
                curr = t
    def avgColors(self,count=1):
        for u in range(count):
            for i in self.blocks:
                if i.col not in [self.waterColor,self.streetColor,"black"]:
                    c0 = i.col[0]
                    c1 = i.col[1]
                    c2 = i.col[2]
                    pp = 1
                    for j in i.neighbors:
                        if j.col not in [self.waterColor,self.streetColor,"black"]:
                            c0 += j.col[0]
                            c1 += j.col[1]
                            c2 += j.col[2]
                            pp += 1
                    c0 = math.floor(c0/pp)
                    c1 = math.floor(c1/pp)
                    c2 = math.floor(c2/pp)
                    i.col = (c0,c1,c2)
    def drawRoads(self,image):
        drawer = ImageDraw.Draw(image)
        dCol = Tools.streetColor
        if self.node.city == None:
            return 0
        for xx in range(math.floor(XDIM-(XDIM*BORDERSCALE*2))):
            for yy in range(math.floor(XDIM-(XDIM*BORDERSCALE*2))):
                x = xx+(XDIM*BORDERSCALE)
                y = yy+(XDIM*BORDERSCALE)
                if (image.getpixel((x,y))) not in [(0,0,0),self.waterColor]:
                    if xStreets(x,self.name) or yStreets(y,self.name):
                        drawer.point((x,y),dCol)
    def drawSelf(self,drawer):
        drawer.rectangle([(0,0),(self.xDim,self.yDim)],self.landColor,self.landColor)
        for k in self.blocks:
            k.drawSelf(drawer)
        scl = 1/BORDERSCALE
        h = self.yDim/scl
        w = self.xDim/scl
        dCol = (0,0,0)
        drawer.rectangle([(0,0),(self.xDim,h)],dCol,dCol)
        drawer.rectangle([(0,0),(w,self.yDim)],dCol,dCol)
        drawer.rectangle([(self.xDim-w,0),(self.xDim,self.yDim)],dCol,dCol)
        drawer.rectangle([(0,self.yDim-h),(self.xDim,self.yDim)],dCol,dCol)