import pygame
import neat
import time
import os
import random
pygame.font.init()
WIN_WIDTH = 600
WIN_HEIGHT = 800

#List of different images of the bird
BIRD_IMGS = [pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird1.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird2.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird3.png")))]

PIPE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "pipe.png")))
BASE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "base.png")))
BG_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bg.png")))

GEN = 0

STAT_FONT = pygame.font.SysFont("comicsans", 50)

class Bird:
	IMGS = BIRD_IMGS
	MAX_ROTATION = 25 #When the bird moves up it rotates 25 degrees
	ROT_VEL = 20 #
	ANIMATION_TIME = 5 #how long we show the diffrent imgages of the bird

	def __init__(self,x, y): #its a function
		self.x = x
		self.y = y
		self.tilt = 0
		self.tick_count = 0
		self.vel = 0
		self.height = self.y
		self.img_count = 0 #which imgages to show
		self.img = self.IMGS[0]

	def jump(self):
		self.vel = -10.5
		self.tick_count = 0 #keep track it when we last jumped
		self.height = self.y

	def move(self):
		self.tick_count += 1 #keep track on how much we moved since last jump so it makes sense to cound each time 'move' is being called

		#a function that simulate gravity
		#if d> 0 moving down, if d<0 moving up
		d = self.vel * self.tick_count + 1.5*self.tick_count**2

		if d>= 16:
			d = 16

		if d<0:
			d-= 2

		self.y = self.y + d

		if d<0 or self.y < self.height + 50:
			if self.tilt < self.MAX_ROTATION:
				self.tilt = self.MAX_ROTATION
		else:
			if self.tilt > - 90:
				self.tilt -= self.ROT_VEL		

	def draw(self, win):
		self.img_count += 1 #how many frames we showed the same img


		#making the flapping animation
		if self.img_count < self.ANIMATION_TIME:
			self.img = self.IMGS[0]
		elif self.img_count < self.ANIMATION_TIME*2:
			self.img = self.IMGS[1]
		elif self.img_count < self.ANIMATION_TIME*3:
			self.img = self.IMGS[2]
		elif self.img_count < self.ANIMATION_TIME*4:
			self.img = self.IMGS[1]
		elif self.img_count == self.ANIMATION_TIME*4 +1:
			self.img = self.IMGS[0]
			self.img_count = 0

		if self.tilt <= -80:
			self.img  = self.IMGS[1]
			self.img_count = self.ANIMATION_TIME*2

		rotated_image = pygame.transform.rotate(self.img, self.tilt)
		new_rect = rotated_image.get_rect(center=self.img.get_rect(topleft = (self.x, self.y)).center)
		win.blit(rotated_image, new_rect.topleft)

	# collistion
	def get_mask(self):
		return pygame.mask.from_surface(self.img)



class Pipe:
	GAP = 200
	VEL = 5

	def __init__(self, x):
		self.x = x
		self.height = 0
		self.gap = 100

		self.top = 0
		self.bottom = 0
		self.PIPE_TOP = pygame.transform.flip(PIPE_IMG, False, True)
		self.PIPE_BOTTOM = PIPE_IMG

		self.passed = False
		self.set_height()

	def set_height(self):
		self.height = random.randrange(50,450)
		self.top = self.height - self.PIPE_TOP.get_height()
		"top is the top left side of the img so if we want to start"
		"in 0 (the pipe wont be shown) we will draw the pipe in negative"
		"value of its own height and the bottom of the img going to be 0"

		self.bottom = self.height + self.GAP

	def move(self):
		self.x -= self.VEL

	def draw(self, win):
		win.blit(self.PIPE_TOP, (self.x, self.top))
		win.blit(self.PIPE_BOTTOM, (self.x, self.bottom))

	def collide(self, bird):
		bird_mask = bird.get_mask()
		top_mask = pygame.mask.from_surface(self.PIPE_TOP)
		bottom_mask = pygame.mask.from_surface(self.PIPE_BOTTOM)

		top_offset = (self.x - bird.x, self.top - round(bird.y))
		bottom_offset = (self.x- bird.x, self.bottom - round(bird.y))

		b_point = bird_mask.overlap(bottom_mask, bottom_offset)
		t_point = bird_mask.overlap(top_mask, top_offset)

		if t_point or b_point:
			return True #not null

		return False



class Base:
	VEL = 5
	WIDTH = BASE_IMG.get_width()
	IMG = BASE_IMG

	def __init__(self, y):
		self.y = y
		self.x1 = 0
		self.x2 = self.WIDTH

	def move(self):
		self.x1 -= self.VEL
		self.x2 -= self.VEL

		if self.x1 + self.WIDTH< 0:
			self.x1 = self.x2 + self.WIDTH

		if self.x2 + self.WIDTH<0:
			self.x2 = self.x1+self.WIDTH 
	def draw(self, win):
		win.blit(self.IMG, (self.x1, self.y))
		win.blit(self.IMG, (self.x2, self.y))


def draw_window(win, birds, pipes, base, score, gen):
	win.blit(BG_IMG, (0,0))
	win.blit(BG_IMG, (BG_IMG.get_width()-5, 0))

	for pipe in pipes:
		pipe.draw(win)

	text = STAT_FONT.render("Score: " + str(score) , 1, (255,255,255))
	win.blit(text, (WIN_WIDTH- 10 - text.get_width(), 10))

	text = STAT_FONT.render("Gen: " + str(gen) , 1, (255,255,255))
	win.blit(text, (10, 10))
	
	base.draw(win)
	
	for bird in birds:
		bird.draw(win)
	
	pygame.display.update()

#have to add genomes, config for the fitness function
def main(genomes, config):
	global GEN
	GEN += 1

	nets = []
	ge = []
	birds = []

	#'genomes' is list of genome object which are coupled like this (1, g) so underscore had to be added to run on the list
	for _, g in genomes:
		net = neat.nn.FeedForwardNetwork.create(g, config) #building the nn based on the genome
		nets.append(net)
		birds.append(Bird(230,230))
		g.fitness = 0 
		ge.append(g)

	base = Base(730)
	pipes = [Pipe(700)]
	win = pygame.display.set_mode((WIN_WIDTH , WIN_HEIGHT))
	clock = pygame.time.Clock()
	score = 0

	run = True
	while run and len(birds) > 0:
		clock.tick(30)

		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				run = False
				pygame.quit()
				quit()


		pipe_ind = 0 
		if len(birds) > 0:
			if len(pipes) > 1 and birds[0].x >pipes[0].x + pipes[0].PIPE_TOP.get_width(): #passed the first pipe
				pipe_ind= 1 #if it passed the first focus your look on the second one

		else:
			run = False
			break

		for x, bird in enumerate(birds):
			bird.move()
			ge[x].fitness += 0.1

			output = nets[x].activate((bird.y, abs(bird.y - pipes[pipe_ind].height), abs(bird.y - pipes[pipe_ind].bottom)))

			if output[0] > 0.5:
				bird.jump()

		add_pipe = False
		rem = []
		for pipe in pipes:
			for x, bird in enumerate(birds):
				if pipe.collide(bird):
					ge[x].fitness -= 1 #when hitting a pipe lowering that bird's fittness 
					birds.pop(x)
					nets.pop(x)
					ge.pop(x)


				if not pipe.passed and pipe.x < bird.x:
					pipe.passed = True
					add_pipe = True

			if pipe.x + pipe.PIPE_TOP.get_width() < 0:
				rem.append(pipe)

			pipe.move()

		if add_pipe:
			score += 1
			for g in ge:
				g.fitness += 5
			pipes.append(Pipe(600))

		for r in rem:
			pipes.remove(r)

		#check if bird hit the ground or touches the ceiling, if True remove the bird,genome and nn
		for x, bird in enumerate(birds):
			if bird.y + bird.img.get_height() >= 730 or bird.y < 0 :
				birds.pop(x)
				nets.pop(x)
				ge.pop(x)

		base.move()
		draw_window(win, birds, pipes, base, score, GEN)

	print("not birds left")

	



def run(config_path):
	config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction,
                         neat.DefaultSpeciesSet, neat.DefaultStagnation,
                         config_path)
	
	#population object which hold all genomes we created (pop_size)
	p = neat.Population(config)

	p.add_reporter(neat.StdOutReporter(True))
	stats = neat.StatisticsReporter()
	p.add_reporter(stats)

	#50 is max generations
	winner = p.run(main,500)

if __name__ == "__main__":
	local_dir = os.path.dirname(__file__)
	config_path = os.path.join(local_dir, "config-feedforward.txt")
	run(config_path)