# Copyright (C) 2022 RozK
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import time
from math import pi, tau, dist
from ctypes import Structure

from engine import *

from game import math
from game import generator
from game import shader
from game import resources
from game import batch
from game import triangles
from game import sea
from game.camera import Camera
from game.environment import Environment

proj_hfov = pi * 0.25
proj_ratio = 16.0 / 9.0
proj_near_z = 8.0
proj_far_z = 3000.0

camera_origin = (0.0, -1200.0,  500.0)
camera_lookat = (0.0,   500.0, -500.0)

sun_direction = math.vec3_normalize((1.0, 0.0, 1.0))
sun_power = 1.0

def main():
    print("Generating terrain...")
    gen_begin = time.process_time()
    generated = generator.Generator(256)
    gen_end = time.process_time()
    print("Done: ", round(gen_end - gen_begin, 2), "seconds")

    print("Initializing...")
    window = initialize(b'RK Island', 1600, 900)
    terrain_shader = shader.load('terrain', 'common')
    tests_shader = shader.load('tests', 'common')
    sky_shader = shader.load('sky')

    heightmap = create_texture(
        TEXTURE_FORMAT_32F, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
        generated.packed_heights)
    terrain_heightmap_sampler = resolve_input(terrain_shader, b'u_height_sampler')
    tests_heightmap_sampler = resolve_input(tests_shader, b'u_height_sampler')

    normalmap = create_texture(
        TEXTURE_FORMAT_RGB10_A2, 256, 256, 0, TEXTURE_FLAG_MIN_LINEAR | TEXTURE_FLAG_MAG_LINEAR,
        generated.packed_normals)
    terrain_normalmap_sampler = resolve_input(terrain_shader, b'u_normal_sampler')
    tests_normalmap_sampler = resolve_input(tests_shader, b'u_normal_sampler')

    print("Loading resources...")
    archive = resources.RuntimeArchive.load('data/rk_island.rkar')

    print("Building tiles...")
    tiles_texture = archive.get_texture('tiles')
    tiles_sampler = resolve_input(terrain_shader, b'u_texture_sampler')
    tiles_vertices = archive.get_vertices('tiles')
    water_model = archive.get_model('water')
    sand_model = archive.get_model('sand')
    grass_model = archive.get_model('grass')
    forest_model = archive.get_model('forest')
    rock_model = archive.get_model('rock')
    mud_model = archive.get_model('mud')
    lava_model = archive.get_model('lava')
    terrain_batch = batch.Batch(tiles_vertices, generated.size ** 2, params_format(PARAM_FORMAT_VEC3_SHORT), vec3)

    #TODO: generator & for real
    vc = generated.volcano_c
    vr = generated.volcano_r
    for my, mx in generated.map_coords:
        vd = dist((mx + 0.5, my + 0.5), vc)
        nx, ny, nz, h = generated.unpack(my, mx)
        r = generated.rivers[my * generated.size + mx]
        if h == 0.0:
            continue
        if r > 0.0:
            model = water_model
        elif h < 2.0:
            model = sand_model
        elif h < 180:
            if nz > 0.9:
                if ny < -0.01 and nz > 0.93:
                    model = forest_model
                else:
                    model = grass_model
            else:
                model = rock_model
        elif vd < vr - 3.0 and nz > 0.999:
            model = lava_model
        elif vd < vr + 2.0:
            model = mud_model
        elif vd < vr + 6.0 and nz < 0.67:
            model = mud_model
        else:
            model = rock_model
        model.spawn(terrain_batch, vec3(float(((mx - 128) * 8) + 4), float(((127 - my) * 8) + 4), 0.0))

    class testsparams(Structure):
        _fields_ = ('translation', vec3), ('orientation', vec3)

    tests_texture = archive.get_texture('tests')
    tests_sampler = resolve_input(tests_shader, b'u_texture_sampler')
    tests_vertices = archive.get_vertices('tests')
    blob_model = archive.get_model('blob')
    cube_model = archive.get_model('cube')
    clouds_model = archive.get_model('clouds')
    tests_batch = batch.Batch(tests_vertices, 3,
        params_format(PARAM_FORMAT_VEC3_FLOAT, PARAM_FORMAT_VEC3_INT10 | PARAM_FORMAT_NORMALIZE), testsparams)
    blob_spawn_translation = vec3(-100.0, -500.0, 0.0)
    cube_spawn_translation = vec3(100.0, -500.0, 0.0)
    blob_id = blob_model.spawn(tests_batch,
        testsparams(blob_spawn_translation, vec3(*math.vec3_normalize((sun_direction[0], sun_direction[1], 0.0)))))
    cube_id = cube_model.spawn(tests_batch, testsparams(cube_spawn_translation, vec3_forward))
    clouds_id = clouds_model.spawn(tests_batch, testsparams(vec3(0.0, 0.0, 32.0), vec3_forward))

    sea_phase = resolve_input(sky_shader, b'u_sea_phase')
    sea_polar_textures = sea.load_polar_textures(('data/sea_bump1.png', 'data/sea_bump2.png'))
    sea_polar_sampler = resolve_input(sky_shader, b'u_sea_polar_sampler')
    sea_detail_texture = sea.load_detail_texture('data/sea_bump.png')
    sea_detail_sampler = resolve_input(sky_shader, b'u_sea_detail_sampler')
    sky_triangles = create_triangles(triangles.sky_triangles(64, proj_far_z - 0.1, proj_ratio))

    camera = Camera()
    camera.set_projection(proj_hfov, proj_ratio, proj_near_z, proj_far_z)
    terrain_camera_inputs = camera.resolve_inputs(terrain_shader)
    tests_camera_inputs = camera.resolve_inputs(tests_shader)
    sky_camera_inputs = camera.resolve_inputs(sky_shader)

    environment = Environment()
    terrain_environment_inputs = environment.resolve_inputs(terrain_shader)
    tests_environment_inputs = environment.resolve_inputs(tests_shader)
    sky_environment_inputs = environment.resolve_inputs(sky_shader)

    blob_translation = tests_batch.params[blob_id].translation
    cube_translation = tests_batch.params[cube_id].translation
    cube_orientation = tests_batch.params[cube_id].orientation
    clouds_orientation = tests_batch.params[clouds_id].orientation

    print("Running...")
    start_time = time.monotonic()
    current_time = 0.0
    frame_min = 10000.0
    frame_max = 0.0
    frame_avg = 0.0
    draw_min = 10000.0
    draw_max = 0.0
    draw_avg = 0.0
    perf_count = 0
    try:
        for frame in range(10000):
            current_time = time.monotonic() - start_time

            begin_frame()
            frame_begin = time.thread_time()

            rotation = mat3()
            mat3_rotation(rotation, vec3_up, (current_time * 0.21) % tau)
            mat3_mul_vec3(blob_translation, rotation, blob_spawn_translation)
            mat3_mul_vec3(cube_translation, rotation, cube_spawn_translation)
            vec3_rotate(cube_orientation, vec3_forward, vec3_up, (current_time * 0.43) % tau)
            vec3_rotate(clouds_orientation, vec3_forward, vec3_up, (current_time * -0.037) % tau)

            origin = vec3()
            lookat = vec3()
            mat3_rotation(rotation, vec3_up, (current_time * 0.05) % tau)
            mat3_mul_vec3(origin, rotation, vec3(*camera_origin))
            mat3_mul_vec3(lookat, rotation, vec3(*camera_lookat))
            camera.set_view(origin, lookat)
            environment.from_sun(camera.view, sun_direction, sun_power)

            select_shader(terrain_shader)
            camera.update_inputs(terrain_camera_inputs)
            environment.update_inputs(terrain_environment_inputs)
            select_texture(0, tiles_texture, tiles_sampler)
            select_texture(1, heightmap, terrain_heightmap_sampler)
            select_texture(2, normalmap, terrain_normalmap_sampler)
            select_vertices(tiles_vertices)
            draw_begin = time.thread_time()
            terrain_batch.draw()
            draw_end = time.thread_time()
            unselect_vertices(tiles_vertices)
            unselect_texture(0, tiles_texture)
            unselect_texture(1, heightmap)
            unselect_texture(2, normalmap)
            unselect_shader(terrain_shader)

            select_shader(tests_shader)
            camera.update_inputs(tests_camera_inputs)
            environment.update_inputs(tests_environment_inputs)
            select_texture(0, tests_texture, tests_sampler)
            select_texture(1, heightmap, tests_heightmap_sampler)
            select_texture(2, normalmap, tests_normalmap_sampler)
            select_vertices(tests_vertices)
            tests_batch.draw()
            unselect_vertices(tests_vertices)
            unselect_texture(0, tests_texture)
            unselect_texture(1, heightmap)
            unselect_texture(2, normalmap)
            unselect_shader(tests_shader)

            m2km = vec3(0.001, 0.001, 0.001)
            origin_km = vec3()
            lookat_km = vec3()
            vec3_mul_vec3(origin_km, origin, m2km)
            vec3_mul_vec3(lookat_km, lookat, m2km)
            camera.set_view(origin_km, lookat_km)

            select_shader(sky_shader)
            camera.update_inputs(sky_camera_inputs)
            environment.update_inputs(sky_environment_inputs)
            set_input_float(sea_phase, (current_time * 0.023) % 1.0)
            select_texture(0, sea_polar_textures, sea_polar_sampler)
            select_texture(1, sea_detail_texture, sea_detail_sampler)
            draw_triangles(sky_triangles)
            unselect_texture(0, sea_polar_textures)
            unselect_texture(1, sea_detail_texture)
            unselect_shader(sky_shader)

            frame_end = time.thread_time()
            end_frame()

            if frame > 0:
                draw_ms = draw_end - draw_begin
                draw_min = min(draw_min, draw_ms)
                draw_max = max(draw_max, draw_ms)
                draw_avg += draw_ms
                frame_ms = frame_end - frame_begin
                frame_min = min(frame_min, frame_ms)
                frame_max = max(frame_max, frame_ms)
                frame_avg += frame_ms
                perf_count += 1
    except KeyboardInterrupt:
        pass

    print("\rDraw  *", perf_count,
        ": min =", round(draw_min * 1000.0, 2),
        ", max =", round(draw_max * 1000.0, 2),
        ", avg =", round((draw_avg / perf_count) * 1000.0, 2), "ms")

    print("\rFrame *", perf_count,
        ": min =", round(frame_min * 1000.0, 2),
        ", max =", round(frame_max * 1000.0, 2),
        ", avg =", round((frame_avg / perf_count) * 1000.0, 2), "ms")

    # seed 666
    # camera_origin = vec3((0.0, -1200.0, 500.0))
    # camera_lookat = vec3((0.0,  500.0, -500.0))
    # for x in range(10000)
    # current_time = 0

    # Draw  * 9999 : min = 0.2 , max = 2.31 , avg = 0.27 ms
    # Draw  * 9999 : min = 0.2 , max = 2.41 , avg = 0.27 ms
    # Draw  * 9999 : min = 0.2 , max = 2.73 , avg = 0.27 ms

    # Frame * 9999 : min = 0.26 , max = 2.44 , avg = 0.38 ms
    # Frame * 9999 : min = 0.26 , max = 2.54 , avg = 0.37 ms
    # Frame * 9999 : min = 0.26 , max = 2.90 , avg = 0.38 ms

    print("Quitting...")
    del tests_batch
    del terrain_batch
    destroy_texture(sea_polar_textures)
    destroy_texture(sea_detail_texture)
    destroy_texture(heightmap)
    destroy_triangles(sky_triangles)
    archive.destroy()
    destroy_shader(terrain_shader)
    destroy_shader(tests_shader)
    destroy_shader(sky_shader)
    terminate()
