# 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/>.

from math import radians, cos
from engine import vec3, mat4_mul_vec3, resolve_input, set_input_vec3
from game.math import vec3_up, vec3_add, vec3_sub, vec3_scale, vec3_mul, vec3_dot

def _angles(start, end):
    cmin = round(cos(radians(start)), 6)
    cmax = round(cos(radians(end)), 6)
    return (cmin, cmax, 1.0 / (cmax - cmin))

def _floats(a, b):
    return (a, b - a)

def _colors(a, b):
    return (a, vec3_sub(b, a))

_light_power = (
    (_angles(180.0, 0.0), _floats(1.0, 1.0)),
)

_light_color = (
    (_angles(180.0, 0.0), _colors((1.0, 1.0, 1.0), (1.0, 1.0, 1.0))),
)

_horizon_color = (
    (_angles(180.0, 0.0), _colors((0.75, 0.75, 1.0), (0.75, 0.75, 1.0))),
)

_sky_color = (
    (_angles(180.0, 0.0), _colors((0.0, 0.0, 0.5), (0.0, 0.0, 0.5))),
)

_sun_color = (
    (_angles(180.0, 0.0), _colors((8.0, 8.0, 4.0), (8.0, 8.0, 4.0))),
)

def _resolve(ranges, c):
    for (cmin, cmax, crng), ab in ranges:
        if c >= cmin and c <= cmax:
            return ((c - cmin) * crng, ab)
    return None

def _resolve_float(ranges, c):
    w, (a, b) = _resolve(ranges, c)
    return a + (b * w)

def _resolve_color(ranges, c):
    w, (a, b) = _resolve(ranges, c)
    return vec3_add(a, vec3_scale(b, w))

class _Inputs:
    __slots__ = 'light_direction', 'light_color', 'horizon_color', 'sky_color', 'sun_color'

    def __init__(self, shader):
        self.light_direction = resolve_input(shader, b'u_light_direction')
        self.light_color = resolve_input(shader, b'u_light_color')
        self.horizon_color = resolve_input(shader, b'u_horizon_color')
        self.sky_color = resolve_input(shader, b'u_sky_color')
        self.sun_color = resolve_input(shader, b'u_sun_color')

class Environment:
    __slots__ = 'light_direction', 'light_color', 'horizon_color', 'sky_color', 'sun_color'

    def __init__(self):
        self.light_direction = vec3()
        self.light_color = vec3()
        self.horizon_color = vec3()
        self.sky_color = vec3()
        self.sun_color = vec3()

    def from_sun(self, view, sun_direction, sun_power):
        c = vec3_dot(sun_direction, vec3_up)
        light_power = _resolve_float(_light_power, c) * sun_power
        mat4_mul_vec3(self.light_direction, view, vec3(*sun_direction), 0.0)
        self.light_color.set(*vec3_scale(_resolve_color(_light_color, c), light_power))
        self.horizon_color.set(*vec3_scale(_resolve_color(_horizon_color, c), light_power))
        self.sky_color.set(*vec3_scale(_resolve_color(_sky_color, c), light_power))
        self.sun_color.set(*_resolve_color(_sun_color, c))

    def resolve_inputs(self, shader):
        return _Inputs(shader)

    def update_inputs(self, inputs):
        set_input_vec3(inputs.light_direction, self.light_direction)
        set_input_vec3(inputs.light_color, self.light_color)
        set_input_vec3(inputs.horizon_color, self.horizon_color)
        set_input_vec3(inputs.sky_color, self.sky_color)
        set_input_vec3(inputs.sun_color, self.sun_color)
