const std = @import("std");
const vk = @import("vulkan");
const resources = @import("resources");
const core = @import("core");
const VkConstants = @import("vk_constants.zig");
const graphics = @import("graphics.zig");
const meshes = @import("mesh.zig");
const NeonVkContext = @import("vk_renderer.zig").NeonVkContext;
const materials = @import("materials.zig");
const animationSystem = @import("animation/animationSystem.zig");
const Animator = animationSystem.Animator;

const Material = materials.Material;
const EulerAngles = core.EulerAngles;
const Mat = core.Mat;
const Vectorf = core.Vectorf;
const Quat = core.Quat;
const zm = core.zm;
const mul = zm.mul;

const Mesh = meshes.Mesh;
const mesh_pool = @import("vk_renderer/vk_mesh_pool.zig");
const IndexedMesh = mesh_pool.IndexedMesh;

// lol we need to rename this thing again, it should be called RenderMesh
pub const StaticMeshSet = core.SparseSet(StaticMesh);

pub const StaticMesh = struct {
    const Self = @This();

    mesh: ?IndexedMesh = null,
    // texture: ?vk.DescriptorSet = null,
    textureId: ?u32 = null,
    transform: core.Mat = core.zm.translation(0, 0, 0),
    visibility: bool = true,

    // new position and rotator based api
    position: Vectorf = .{},
    rotation: Quat = .{ 0, 0, 0, 1 },
    scale: Vectorf = .{ .x = 1, .y = 1, .z = 1 },

    textureName: core.Name = core.NameInvalid,
    meshName: core.Name = core.NameInvalid,

    animated: bool = false, // todo remove
    animator: ?*Animator = null,

    flags: Flags0 = .{},

    pub var BaseContainer: *StaticMeshSet = undefined;
    pub const ComponentName = "StaticMesh";

    pub const ScriptExports: []const []const u8 = &.{
        "applyRelativeRotationX",
        "applyRelativeRotationY",
        "applyRelativeRotationZ",
        "setMesh",
        "setTextureByName",
    };

    pub const Flags0 = packed struct(u32) {
        alwaysInFront: bool = false,
        useAltFov: bool = false,
        _pad: u30 = 0,
    };

    pub fn setMeshByName(self: *@This(), meshName: core.Name) void {
        self.meshName = meshName;
    }

    // script function
    pub fn setMesh(self: *@This(), meshName: []const u8) void {
        const name = core.MakeName(meshName);
        self.mesh = graphics.getIndexedMeshByName(core.MakeName(meshName));
        self.meshName = name;
    }

    pub fn fromTransform(transform: core.Mat) Self {
        var self = Self{
            .mesh = null,
            .transform = transform,
            .position = undefined,
            .rotation = undefined,
            .scale = undefined,
        };

        self.updateScalars();

        return self;
    }

    pub fn setTexture(self: *Self, textureName: []const u8) void {
        var name = core.MakeName(textureName);
        self.textureId = graphics.getContext().textureIds.get(name.handle());
        self.textureName = name;
    }

    pub fn setTextureByName(self: *Self, _name: core.Name) void {
        var name = _name;
        self.textureId = graphics.getContext().textureIds.get(name.handle());
        self.textureName = name;
    }

    pub fn updateTexture(self: *@This(), gc: *NeonVkContext) void {
        self.textureId = gc.textureIds.get(self.textureName.handle());
    }

    pub fn applyTransform(self: *StaticMesh, transform: core.Mat) void {
        self.transform = core.zm.mul(self.transform, transform);
        self.updateScalars();
    }

    pub fn applyRelativeRotationX(self: *StaticMesh, angle: f32) void {
        var imat = core.zm.identity();
        imat[0][3] = -self.transform[0][3];
        imat[1][3] = -self.transform[1][3];
        imat[2][3] = -self.transform[2][3];

        const rmat = core.zm.identity();
        imat[0][3] = self.transform[0][3];
        imat[1][3] = self.transform[1][3];
        imat[2][3] = self.transform[2][3];

        var newTransform = core.zm.mul(imat, self.transform);
        newTransform = core.zm.mul(core.zm.rotationX(angle), newTransform);
        newTransform = core.zm.mul(rmat, newTransform);
        self.transform = newTransform;
    }

    pub fn applyRelativeRotationZ(self: *StaticMesh, angle: f32) void {
        var imat = core.zm.identity();
        imat[0][3] = -self.transform[0][3];
        imat[1][3] = -self.transform[1][3];
        imat[2][3] = -self.transform[2][3];

        const rmat = core.zm.identity();
        imat[0][3] = self.transform[0][3];
        imat[1][3] = self.transform[1][3];
        imat[2][3] = self.transform[2][3];

        var newTransform = core.zm.mul(imat, self.transform);
        newTransform = core.zm.mul(core.zm.rotationZ(angle), newTransform);
        newTransform = core.zm.mul(rmat, newTransform);
        self.transform = newTransform;
    }

    pub fn applyRelativeRotationY(self: *StaticMesh, angle: f32) void {
        var imat = core.zm.identity();
        imat[0][3] = -self.transform[0][3];
        imat[1][3] = -self.transform[1][3];
        imat[2][3] = -self.transform[2][3];

        const rmat = core.zm.identity();
        imat[0][3] = self.transform[0][3];
        imat[1][3] = self.transform[1][3];
        imat[2][3] = self.transform[2][3];

        var newTransform = core.zm.mul(imat, self.transform);
        newTransform = core.zm.mul(core.zm.rotationY(angle), newTransform);
        newTransform = core.zm.mul(rmat, newTransform);
        self.transform = newTransform;
    }

    pub fn updateScalars(self: *StaticMesh) void {
        self.position = Vectorf.fromZm(mul(self.transform, Vectorf.new(0.0, 0.0, 0.0).toZm()));
        self.rotation = zm.matToQuat(self.transform);
        self.scale = core.matToScalef(self.transform);
    }

    pub fn applyScalars(self: *StaticMesh) void {
        var newTransform = core.zm.mul(
            core.zm.scalingV(self.scale.toZm()),
            core.zm.matFromQuat(self.rotation),
        );
        newTransform = core.zm.mul(
            newTransform,
            core.zm.translationV(self.position.toZm()),
        );
        self.transform = newTransform;
    }

    pub fn initECS(self: *@This(), handle: core.ObjectHandle) void {
        const entity = core.Entity.fromHandle(handle);
        if (entity.get(core.Scene)) |scene| {
            self.position = scene.getPosition();
            self.rotation = scene.getRotation().quat;
            self.scale = scene.getScaleV();
        } else {
            _ = entity.addComponent(core.Scene);
        }
    }
};

fn makePerspective(fov: f32, aspect: f32, near: f32, far: f32) Mat {
    const proj = core.zm.perspectiveFovRh(
        core.radians(fov),
        aspect,
        near,
        far,
    );
    // proj[1][1] *= -1;
    return proj;
}

// Camera coordinate system:
//
// from you as a user, staring at the screen:
//
// This is a right handed coordinate system
//
// forward = +Z (index finger)
// left = +X (middle finger)
// up = +Y (thumb)

const ecs = core.ecs;

pub const Camera = struct {
    fov: f32 = 70.0,
    altFov: f32 = 70.0,
    aspect: f32 = 16.0 / 9.0,
    near_clipping: f32 = 0.1,
    far_clipping: f32 = 10000.0,

    position: Vectorf = Vectorf{ .x = 0.0, .y = 0.0, .z = 0.0 },
    rotation: Quat, // todo, remove, we only work with euler tracks now for camera.

    // applied in that order,
    yaw: f32 = 0,
    pitch: f32 = 0,
    roll: f32 = 0,

    transform: core.Transform = zm.identity(),
    worldTransform: Mat = zm.identity(),
    projection: Mat = makePerspective(
        core.radians(70.0), // angle
        16.0 / 9.0,
        0.1,
        200000,
    ),
    projectionAlt: Mat = makePerspective(
        core.radians(70.0), // angle
        16.0 / 9.0,
        0.0001,
        1000,
    ),

    final: Mat = zm.identity(),
    finalAlt: Mat = zm.identity(),

    pub const EcsComponentDefinition = ecs.DefineComponent(@This(), .set); // set, map, multiset, maplist

    pub fn init() Camera {
        return .{
            .rotation = zm.quatFromRollPitchYaw(0.0, 0.0, 0.0),
        };
    }

    pub fn translate(self: *Camera, offset: core.Vectorf) void {
        var off: core.Vectorf = offset;
        off.y = offset.y;
        off.x = offset.x;
        off.z = offset.z;
        self.*.position = self.position.add(off);
    }

    pub fn getRotation(self: *Camera) Quat {
        return zm.quatFromMat(self.transform);
    }

    pub fn setRotationEuler(self: *@This(), x: f32, y: f32, z: f32) void {
        self.rotation = core.zm.quatFromRollPitchYaw(x, y + core.radians(180.0), z);
    }

    pub fn updateCamera(self: *Camera) void {
        self.projection = zm.perspectiveFovRh(core.radians(self.fov), 16.0 / 9.0, 0.1, 200000);
        self.projection[1][1] *= -1;

        self.projectionAlt = zm.perspectiveFovRh(core.radians(self.altFov), 16.0 / 9.0, 0.01, 200000);
        self.projectionAlt[1][1] *= -1;

        // self.projectionAlt = self.projection;
    }

    pub fn resolve(self: *Camera) void {
        {
            var base = core.zm.identity();
            base = mul(core.zm.rotationY(-self.yaw), base);
            base = mul(core.zm.rotationX(self.pitch), base);
            base = mul(core.zm.rotationZ(self.roll), base);

            const pr2 = core.scene.SceneObjectPosRot{
                .position = self.position,
            };

            self.worldTransform = mul(base, pr2.toTransform());
        }

        // calculate viewProjections
        {
            var base = core.zm.rotationY(self.yaw + core.radians(180.0));
            base = mul(base, core.zm.rotationX(self.pitch));
            base = mul(base, core.zm.rotationZ(self.roll));
            self.transform = base;
            // self.transform = mul(
            //     base,
            //     mul(zm.matFromQuat(self.rotation), zm.rotationY(core.radians(180.0))),
            // );
            var position = self.position;
            // position.x *= -1;
            // position.z *= -1;
            self.transform = mul(zm.translationV(position.fmul(-1).toZm()), self.transform);
            self.final = mul(self.transform, self.projection);
            self.finalAlt = mul(self.transform, self.projectionAlt);
        }
    }
};
