from OpenGL.GL import *
from .key import Key
from .mesh import Mesh
import sdl2
import numpy as np
import ctypes

def normalize(v):
    norm = np.linalg.norm(v)
    if norm == 0:
        return v
    return v / norm

def look_at(eye, center, up):
    f = normalize(center - eye)
    s = normalize(np.cross(f, up))
    u = np.cross(s, f)

    mat = np.identity(4, dtype=np.float32)
    mat[0, :3] = s
    mat[1, :3] = u
    mat[2, :3] = -f
    mat[:3, 3] = -np.dot(mat[:3, :3], eye)
    return mat

def perspective(fov, aspect, near, far):
    f = 1.0 / np.tan(np.radians(fov) / 2)
    mat = np.zeros((4, 4), dtype=np.float32)
    mat[0, 0] = f / aspect
    mat[1, 1] = f
    mat[2, 2] = (far + near) / (near - far)
    mat[2, 3] = (2 * far * near) / (near - far)
    mat[3, 2] = -1
    return mat

class Camera:
    def __init__(self, position, target, up, fov, aspect, near=0.1, far=100.0):
        self.position = np.array(position, dtype=np.float32)
        self.target = np.array(target, dtype=np.float32)
        self.up = np.array(up, dtype=np.float32)
        self.fov = fov
        self.aspect = aspect
        self.near = near
        self.far = far

        self.move_speed = 5.0
        self.mouse_sensitivity = 0.003

    @property
    def view_matrix(self):
        return look_at(self.position, self.target, self.up)

    @property
    def projection_matrix(self):
        return perspective(self.fov, self.aspect, self.near, self.far)

    @property
    def view_proj_matrix(self):
        return self.projection_matrix @ self.view_matrix

    def move(self, delta):
        self.position += delta
        self.target += delta

    def handle_input(self, dt, window):
        speed = self.move_speed * dt

        forward = normalize(self.target - self.position)
        right = normalize(np.cross(forward, self.up))

        if window.is_key_pressed(Key.W):
            self.move(forward * speed)
        if window.is_key_pressed(Key.S):
            self.move(-forward * speed)
        if window.is_key_pressed(Key.A):
            self.move(-right * speed)
        if window.is_key_pressed(Key.D):
            self.move(right * speed)

        # Dodane ruchy w górę/dół
        if window.is_key_pressed(Key.SPACE):  # Spacja - do góry
            self.move(self.up * speed)
        if window.is_key_pressed(Key.LCTRL):  # Lewy Ctrl - w dół
            self.move(-self.up * speed)

        if getattr(window, "mouse_locked", True):
            xrel = ctypes.c_int()
            yrel = ctypes.c_int()
            sdl2.SDL_GetRelativeMouseState(ctypes.byref(xrel), ctypes.byref(yrel))

            yaw = -xrel.value * self.mouse_sensitivity
            pitch = -yrel.value * self.mouse_sensitivity

            def rotate(v, axis, angle):
                axis = normalize(axis)
                cos_a = np.cos(angle)
                sin_a = np.sin(angle)
                return v * cos_a + np.cross(axis, v) * sin_a + axis * np.dot(axis, v) * (1 - cos_a)

            offset = self.target - self.position
            offset = rotate(offset, self.up, yaw)
            right = normalize(np.cross(offset, self.up))
            offset = rotate(offset, right, pitch)

            self.target = self.position + offset

    def set_uniforms(self, shader_program):
        program_id = shader_program.program  # ID programu
        loc_v = glGetUniformLocation(program_id, "uView")
        loc_p = glGetUniformLocation(program_id, "uProjection")

        glUniformMatrix4fv(loc_v, 1, GL_FALSE, self.view_matrix.T)
        glUniformMatrix4fv(loc_p, 1, GL_FALSE, self.projection_matrix.T)
