class GameController(Controller):
    def __init__(self, window):
        super(GameController, self).__init__(window)
        self.sector, self.highlighted_block, self.crack, self.last_key = (None,) * 4
        self.bg_red, self.bg_green, self.bg_blue = (0.0,) * 3
        self.mouse_pressed, self.sorted = (False,) * 2
        self.block_damage = 0
        self.time_of_day = 6.0

        self.back_to_main_menu = threading.Event()


    def update(self, dt):
        if self.back_to_main_menu.isSet():
            self.switch_controller_class(MainMenuController)
            return
        self.update_sector(dt)
        self.update_player(dt)
        self.update_mouse(dt)
        self.update_time(dt)
        self.camera.update(dt)

    def update_sector(self, dt):
        sector = sectorize(self.player.position)
        if sector != self.sector:
            self.world.change_sectors(sector)
            # When the world is loaded, show every visible sector.
            if self.sector is None:
                self.world.process_entire_queue()
            self.sector = sector

    def update_player(self, dt):
        m = 8
        df = min(dt, 0.2)
        for _ in range(m):
            self.player.update(df / m, self)
        for ply in self.player_ids.values():
            for _ in range(m):
                ply.update(df / m, self)
        momentum = self.player.get_motion_vector(15 if self.player.flying else 5*self.player.current_density)
        if momentum != self.player.momentum_previous:
            self.player.momentum_previous = momentum
            self.packetreceiver.send_movement(momentum, self.player.position)


    def update_mouse(self, dt):
        if self.mouse_pressed:
            vector = self.player.get_sight_vector()
            block, previous = self.world.hit_test(self.player.position, vector,
                                                  self.player.attack_range)
            self.set_highlighted_block(block)

            if self.highlighted_block:
                hit_block = self.world[self.highlighted_block]
                if hit_block.hardness >= 0:
                    self.update_block_damage(dt, hit_block)
                    self.update_block_remove(dt, hit_block)

    def update_block_damage(self, dt, hit_block):
        multiplier = 1
        current_item = self.item_list.get_current_block()
        if current_item is not None:
            if isinstance(current_item, Tool):  # tool
                if current_item.tool_type == hit_block.digging_tool:
                    multiplier = current_item.multiplier

        self.block_damage += self.player.attack_power * dt * multiplier

    def update_block_remove(self, dt, hit_block):
        if self.block_damage >= hit_block.hardness:
            self.world.remove_block(self.player,
                                    self.highlighted_block)
            self.set_highlighted_block(None)
            if getattr(self.item_list.get_current_block_item(), 'durability', -1) != -1:
                self.item_list.get_current_block_item().durability -= 1
                if self.item_list.get_current_block_item().durability <= 0:
                    self.item_list.remove_current_block()
                    self.item_list.update_items()
            if hit_block.drop_id is None:
                return
            if type(hit_block.drop_id) == list:
                for index, item in enumerate(hit_block.drop_id):
                    if not self.player.add_item(item, quantity=hit_block.drop_quantity[index]):
                        return
            elif not self.player.add_item(hit_block.drop_id, quantity=hit_block.drop_quantity):
                    return
            self.item_list.update_items()
            self.inventory_list.update_items()

    def init_gl(self):
        glEnable(GL_ALPHA_TEST)
        glAlphaFunc(GL_GREATER, 0.1)
        glEnable(GL_COLOR_MATERIAL)
        glEnable(GL_BLEND)

        glEnable(GL_LINE_SMOOTH)
        glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
        glEnable(GL_POLYGON_SMOOTH)
        glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST)

        # glClampColorARB(GL_CLAMP_VERTEX_COLOR_ARB, GL_FALSE)
        # glClampColorARB(GL_CLAMP_FRAGMENT_COLOR_ARB, GL_FALSE)
        # glClampColorARB(GL_CLAMP_READ_COLOR_ARB, GL_FALSE)

        # glClearColor(0, 0, 0, 0)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

    def setup(self):
        if G.SINGLEPLAYER:
            try:
                # TODO: create world menu
                G.SAVE_FILENAME = "world"
                start_server(internal=True)
                sock = socket.socket()
                time.sleep(2)
                sock.connect(("localhost", 1486))
            except socket.error as e:
                #Otherwise back to the main menu we go
                return False
            except Exception as e:
                import traceback
                traceback.print_exc()
                return False
        else:
            try:
                #Make sure the address they want to connect to works
                ipport = G.IP_ADDRESS.split(":")
                if len(ipport) == 1: ipport.append(1486)
                sock = socket.socket()
                sock.connect((tuple(ipport)))
            except socket.error as e:
                #Otherwise back to the main menu we go
                return False
        self.init_gl()

        self.debug_text = TextWidget(self.window, 'Loading information...',
                                     0, self.window.height - 300,
                                     500, 300,
                                     visible=True, multi_line=True, readonly=True,
                                     font_name='Arial', font_size=8,
                                     text_color=(255, 255, 255, 255),
                                     background_color=(0, 0, 0, 0))
        self.debug_text.write_escape("$$r")

        sky_rotation = -20.0  # -20.0

        # TERRAIN_CHOICE = self.biome_generator.get_biome_type(sector[0], sector[2])
        default_skybox = 'skydome.jpg'
        # if TERRAIN_CHOICE == G.NETHER:
        #     default_skybox = 'skydome_nether.jpg'
        # else:
        #     default_skybox = 'skybox.jpg'

        self.skydome = Skydome(
            'resources/' + default_skybox,
            #'resources/skydome.jpg',
            0.7,
            100.0,
            sky_rotation,
        )

        self.player_ids = {}  # Dict of all players this session, indexes are their ID's [0: first Player on server,]

        self.focus_block = Block(width=1.05, height=1.05)
        self.earth = vec(0.8, 0.8, 0.8, 1.0)
        self.white = vec(1.0, 1.0, 1.0, 1.0)
        self.polished = GLfloat(100.0)
        self.crack_batch = pyglet.graphics.Batch()

        # if G.DISABLE_SAVE and world_exists(G.game_dir, G.SAVE_FILENAME):
        #     open_world(self, G.game_dir, G.SAVE_FILENAME)

        self.world = World()
        self.packetreceiver = PacketReceiver(self.world, self, sock)
        self.world.packetreceiver = self.packetreceiver
        G.CLIENT = self.packetreceiver
        self.packetreceiver.start()

        #Get our position from the server
        self.packetreceiver.request_spawnpos()
        #Since we don't know it yet, lets disable self.update, or we'll load the wrong chunks and fall
        self.update_disabled = self.update
        self.update = lambda dt: None
        #We'll re-enable it when the server tells us where we should be

        self.player = Player(game_mode=G.GAME_MODE)
        self.item_list = ItemSelector(self, self.player, self.world)
        self.inventory_list = InventorySelector(self, self.player, self.world)
        self.item_list.on_resize(self.window.width, self.window.height)
        self.inventory_list.on_resize(self.window.width, self.window.height)
        self.text_input = TextWidget(self.window, '',
                                     0, 0,
                                     self.window.width,
                                     visible=False,
                                     font_name=G.CHAT_FONT)
        self.text_input.push_handlers(on_toggled=self.on_text_input_toggled, key_released=self.text_input_callback)
        self.chat_box = TextWidget(self.window, '',
                                   0, self.text_input.y + self.text_input.height + 50,
                                   self.window.width // 2, height=min(300, self.window.height // 3),
                                   visible=False, multi_line=True, readonly=True,
                                   font_name=G.CHAT_FONT,
                                   text_color=(255, 255, 255, 255),
                                   background_color=(0, 0, 0, 100),
                                   enable_escape=True)
        self.camera = Camera3D(target=self.player)

        if G.HUD_ENABLED:
            self.label = pyglet.text.Label(
                '', font_name='Arial', font_size=8, x=10, y=self.window.height - 10,
                anchor_x='left', anchor_y='top', color=(255, 255, 255, 255))

        self.debug_text.write_escape("$$D")

        pyglet.clock.schedule_interval_soft(self.world.process_queue,
                                            1.0 / G.MAX_FPS)
        pyglet.clock.schedule_interval_soft(self.world.hide_sectors, 10.0, self.player)
        return True

    def update_time(self, dt: float):
        self.time_of_day += dt * 24.0 / G.TIME_RATE
        if self.time_of_day > 24.0:
           self.time_of_day = 0.0

        # Calculate sky colour according to time of day.
        sin_t = sin(pi * (((self.time_of_day / 12.0) + 1) % 2 - 1))
        self.bg_red = 0.1 * (1.0 - sin_t)
        self.bg_green = 0.9 * sin_t
        self.bg_blue = min(sin_t + 0.4, 0.8)

        self.skydome.update_time_of_day(self.time_of_day)

    def set_highlighted_block(self, block):
        if self.highlighted_block == block:
            return
        self.highlighted_block = block
        self.block_damage = 0
        if self.crack:
            self.crack.delete()
        self.crack = None

    def on_mouse_press(self, x, y, button, modifiers):
        if self.window.exclusive:
            vector = self.player.get_sight_vector()
            block, previous = self.world.hit_test(self.player.position, vector, self.player.attack_range)
            if button == pyglet.window.mouse.LEFT:
                self.on_mouse_press_left(block, x, y, button, modifiers)
            else:
                self.on_mouse_press_right(block, previous, x, y, button, modifiers)
        else:
            self.window.set_exclusive_mouse(True)

    def on_mouse_press_left(self, block, x, y, button, modifiers):
        if block:
            self.mouse_pressed = True
            self.set_highlighted_block(None)

    def on_mouse_press_right(self, block, previous, x, y, button, modifiers):
        if previous:
            hit_block = self.world[block]
            if hit_block.id == craft_block.id:
                self.inventory_list.switch_mode(1)
                self.inventory_list.toggle(False)
            elif hit_block.id == furnace_block.id:
                self.inventory_list.switch_mode(2)
                self.inventory_list.set_furnace(hit_block)
                self.inventory_list.toggle(False)
            elif hit_block.density >= 1:
               self.place_block(previous)
        elif self.item_list.get_current_block() and getattr(self.item_list.get_current_block(), 'regenerated_health', 0) != 0 and self.player.health < self.player.max_health:
            self.eat_food()

    def place_block(self, previous):
        current_block = self.item_list.get_current_block()
        if current_block is not None:
            if current_block.id.is_item():
                if current_block.on_right_click(self.world, self.player):
                    self.item_list.get_current_block_item().change_amount(-1)
                    self.item_list.update_health()
                    self.item_list.update_items()
            else:
                localx, localy, localz = map(operator.sub,previous,normalize(self.player.position))
                if localx != 0 or localz != 0 or (localy != 0 and localy != -1):
                    self.world.add_block(previous, current_block)
                    self.item_list.remove_current_block()

    def eat_food(self):
        self.player.change_health(self.item_list.get_current_block().regenerated_health)
        self.item_list.get_current_block_item().change_amount(-1)
        self.item_list.update_health()
        self.item_list.update_items()

    def on_mouse_release(self, x, y, button, modifiers):
        if self.window.exclusive:
            self.set_highlighted_block(None)
            self.mouse_pressed = False

    def on_mouse_motion(self, x, y, dx, dy):
        if self.window.exclusive:
            m = 0.15
            x, y = self.player.rotation
            x, y = x + dx * m, y + dy * m
            y = max(-90, min(90, y))
            self.player.rotation = (x, y)
            self.camera.rotate(x, y)

    def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
        if button == pyglet.window.mouse.LEFT:
            self.on_mouse_motion(x, y, dx, dy)

    def on_key_press(self, symbol, modifiers):
        if symbol == G.TOGGLE_HUD_KEY:
            G.HUD_ENABLED = not G.HUD_ENABLED
        if symbol == G.TOGGLE_DEBUG_TEXT_KEY:
            self.debug_text.visible = not self.debug_text.visible
            self.debug_text.delete()
        elif symbol == G.INVENTORY_SORT_KEY:
            if self.last_key == symbol and not self.sorted:
                self.player.quick_slots.sort()
                self.player.inventory.sort()
                self.sorted = True
            else:
                self.player.quick_slots.change_sort_mode()
                self.player.inventory.change_sort_mode()
                self.item_list.update_items()
                self.inventory_list.update_items()
        elif symbol == G.INVENTORY_KEY:
            self.set_highlighted_block(None)
            self.mouse_pressed = False
            self.inventory_list.toggle()
        elif symbol == G.SOUND_UP_KEY:
            G.EFFECT_VOLUME = min(G.EFFECT_VOLUME + .1, 1)
        elif symbol == G.SOUND_DOWN_KEY:
            G.EFFECT_VOLUME = max(G.EFFECT_VOLUME - .1, 0)
        elif symbol == G.SCREENCAP_KEY:  # dedicated screencap key
            now = datetime.datetime.now()
            dt = datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, now.second)
            st = dt.strftime('%Y-%m-%d_%H.%M.%S')
            filename = str(st) + '.png'
            if not os.path.exists('screencaptures'):
                os.makedirs('screencaptures')
            path = 'screencaptures/' + filename
            pyglet.image.get_buffer_manager().get_color_buffer().save(path)
        elif symbol == G.SHOWMAP_KEY:
            self.show_map()
        elif symbol == G.QUIT_KEY:
            self.back_to_main_menu.set()
            savingsystem.save_quit_world(G.SERVER)
            self.window.set_exclusive_mouse(False)
        self.last_key = symbol

    def on_key_release(self, symbol, modifiers):
        if symbol == G.TALK_KEY:
            self.toggle_text_input()
            return pyglet.event.EVENT_HANDLED

    def on_resize(self, width, height):
        if G.HUD_ENABLED:
            self.label.y = height - 10
        self.text_input.resize(x=0, y=0, width=self.window.width)
        self.chat_box.resize(x=0, y=self.text_input.y + self.text_input.height + 50,
                             width=self.window.width // 2, height=min(300, self.window.height // 3))
        self.debug_text.resize(0, self.window.height - 300,
                                   500, 300)

    def set_3d(self):
        width, height = self.window.get_size()
        glEnable(GL_DEPTH_TEST)
        glViewport(0, 0, width, height)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(G.FOV, width / float(height),
                       G.NEAR_CLIP_DISTANCE,
                       G.FAR_CLIP_DISTANCE)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glPushMatrix()
        self.camera.look()
        self.skydome.draw()
        glPopMatrix()
        self.camera.transform()

    def clear(self):
        glClearColor(self.bg_red, self.bg_green, self.bg_blue, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    def on_draw(self):
        self.clear()
        #self.window.clear()
        self.set_3d()
        self.world.batch.draw()
        self.world.transparency_batch.draw()
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        self.crack_batch.draw()
        glDisable(GL_BLEND)
        self.draw_focused_block()
        for ply in self.player_ids.values():
            ply.model.draw()
        self.set_2d()
        if G.HUD_ENABLED:
            self.item_list.draw()
            self.inventory_list.draw()
        self.update_label()
        self.debug_text.draw()
        self.text_input.draw()
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        self.chat_box.draw()
        glDisable(GL_BLEND)

    def show_cracks(self, hit_block, vertex_data):
        if self.block_damage:  # also show the cracks
            crack_level = int(CRACK_LEVELS * self.block_damage
                              // hit_block.hardness)  # range: [0, CRACK_LEVELS]
            if crack_level >= CRACK_LEVELS:
                return
            texture_data = crack_textures.texture_data[crack_level]
            count = len(texture_data) // 2
            if self.crack:
                self.crack.delete()
            self.crack = self.crack_batch.add(count, GL_QUADS, crack_textures.group,
                                              ('v3f/static', vertex_data),
                                              ('t2f/static', texture_data))

    def draw_focused_block(self):
        glDisable(GL_LIGHTING)
        vector = self.player.get_sight_vector()
        position = self.world.hit_test(self.player.position, vector, self.player.attack_range)[0]
        if position:
            hit_block = self.world[position]
            if hit_block.density >= 1:
                self.focus_block.width = hit_block.width * 1.05
                self.focus_block.height = hit_block.height * 1.05
                vertex_data = self.focus_block.get_vertices(*position)

                if hit_block.hardness > 0.0:
                    self.show_cracks(hit_block, vertex_data)

                glEnable(GL_BLEND)
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
                glColor4f(0.0, 0.0, 0.0, 0.4)
                glLineWidth(2.0)
                glDisable(GL_TEXTURE_2D)
                glDepthMask(False)

                glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
                pyglet.graphics.draw(24, GL_QUADS, ('v3f/static', vertex_data))
                glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

                glDepthMask(True)
                glEnable(GL_TEXTURE_2D)
                glDisable(GL_BLEND)

    def update_label(self):
        x, y, z = self.player.position
        self.debug_text.clear()
        self.debug_text.write_line(' '.join((G.APP_NAME, str(G.APP_VERSION))))
        self.debug_text.write_line('FPS:%02d Blocks Shown: %d / %d sector_packets:%d' \
                                   % (pyglet.clock.get_fps(),len(self.world._shown),len(self.world),
                                      len(self.world.sector_packets)))
        self.debug_text.write_line('x: %.2f, chunk: %d' %(x, x // G.SECTOR_SIZE))
        self.debug_text.write_line('y: %.2f, chunk: %d' %(y, y // G.SECTOR_SIZE))
        self.debug_text.write_line('z: %.2f, chunk: %d' %(z, z // G.SECTOR_SIZE))
        dirs = ['East', 'South', 'West', 'North']
        vec, direction, angle = self.player.get_sight_direction()
        dx, dy, dz = vec
        self.debug_text.write_line('Direction: (%.2f, %.2f, %.2f) %d(%s) %.2f' % (dx, dy, dz, direction, dirs[direction], angle))

    def write_line(self, text, **kwargs):
        self.chat_box.write_line(text, **kwargs)

    def text_input_callback(self, symbol, modifier):
        if symbol == G.VALIDATE_KEY:
            txt = self.text_input.text.replace('\n', '')
            self.text_input.clear()
            if txt:
                self.world.packetreceiver.send_chat(txt)
            return pyglet.event.EVENT_HANDLED

    def hide_chat_box(self, dt):
        self.chat_box.toggle(False)

    def on_text_input_toggled(self):
        pyglet.clock.unschedule(self.hide_chat_box)  # Disable the fade timer
        self.chat_box.toggle(state=self.text_input.visible)  # Pass through the state incase chat_box was already visible
        if self.chat_box.visible:
            self.chat_box.focused = True # Allow scrolling
            self.window.push_handlers(self.chat_box)
        else:
            self.chat_box.focused = False
            self.window.remove_handlers(self.chat_box)

    def toggle_text_input(self):
        self.text_input.toggle()
        if self.text_input.visible:
            self.player.velocity = 0
            self.player.strafe = [0, 0]
            self.window.push_handlers(self.text_input)
            self.text_input.focus()
        else:
            self.window.remove_handlers(self.text_input)

    def push_handlers(self):
        if self.setup():
            self.window.push_handlers(self.camera)
            self.window.push_handlers(self.player)
            self.window.push_handlers(self)
            self.window.push_handlers(self.item_list)
            self.window.push_handlers(self.inventory_list)
        else:
            self.switch_controller_class(MainMenuController)

    def pop_handlers(self):
        while self.window._event_stack:
            self.window.pop_handlers()

    def on_close(self):
        G.save_config()
        self.world.packetreceiver.stop()  # Disconnect from the server so the process can close

    def show_map(self):
         # taken from Nebual's biome_explorer, this is ment to be a full screen map that uses mini tiles to make a full 2d map.
        with open(os.path.join(G.game_dir, "world", "seed"), "r") as f:
            SEED = f.read()
        b = BiomeGenerator(SEED)
        x, y, z = self.player.position
        curx =  x
        cury = y
        xsize = 79
        ysize = 28
        pbatch = pyglet.graphics.Batch()
        pgroup = pyglet.graphics.OrderedGroup(1)
        DESERT, PLAINS, MOUNTAINS, SNOW, FOREST = list(range(5))
        letters = ["D","P","M","S","F"]

        #  temp background pic...
        image = load_image('resources', 'textures', 'main_menu_background.png')

        #map_frame = image_sprite(image, pbatch, 0, y=G.WINDOW_WIDTH, height=G.WINDOW_HEIGHT)
        #sprite = pyglet.sprite.Sprite(image)
        #sprite.image(image)
        #sprite.visible = True
       # map_frame.draw()
       # map_frame.visible = True
        for y in range(int(cury),int(cury+ysize)):
         for x in range(int(curx),int(curx+xsize)):
             #string += letters[b.get_biome_type(x,y)]
            tmap = letters[b.get_biome_type(x,y)]
            tile_map = load_image('resources', 'textures', tmap +'.png')
            tile_map.anchor_x = x * 8
            tile_map.anchor_y = y * 8
            sprite = pyglet.sprite.Sprite(tile_map, x=x * 8, y=y * 8, batch=pbatch)
            game_map = image_sprite(tile_map, pbatch, pgroup, x * 8, y * 8, 8, 8)
            game_map = pyglet.sprite.Sprite(image,x=G.WINDOW_WIDTH, y=G.WINDOW_HEIGHT,batch=pbatch, group=pgroup)
            game_map = pyglet.sprite.Sprite(tile_map,x=x*8, y=y*8,batch=pbatch, group=pgroup)

            tile_map.blit(x *8, y * 8)

            #tile_map.draw()
            #map.append(tmap)
            game_map.draw()
            pbatch.draw()