package com.cgvsu;

import com.cgvsu.math.Matrix4f;
import com.cgvsu.math.Vector3f;
import com.cgvsu.math.Vector4f;
import com.cgvsu.model.Model;
import com.cgvsu.objreader.ObjReader;
import com.cgvsu.render_engine.Camera;
import com.cgvsu.render_engine.GraphicConveyor;
import com.cgvsu.render_engine.RenderEngine;
import com.cgvsu.render_engine.TransformationParameters;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class GuiController {


    final private float TRANSLATION = 0.5F;
    @FXML
    public CheckBox checkBoxUseFillPolygon;
    @FXML
    public CheckBox checkBoxUseIntrLighting;
    @FXML
    public CheckBox checkBoxUseFlatLighting;
    @FXML
    AnchorPane anchorPane;
    RenderEngine renderEngine = new RenderEngine();
    @FXML
    private Canvas canvas;
    @FXML
    private VBox modelsManager;
    @FXML
    private TextField textFieldScaleX;
    @FXML
    private TextField textFieldScaleY;
    @FXML
    private TextField textFieldScaleZ;
    @FXML
    private TextField textFieldRotationX;
    @FXML
    private TextField textFieldRotationY;
    @FXML
    private TextField textFieldRotationZ;
    @FXML
    private TextField textFieldTranslationX;
    @FXML
    private TextField textFieldTranslationY;
    @FXML
    private TextField textFieldTranslationZ;
    @FXML
    private VBox camerasManager;
    @FXML
    private TextField textFieldCameraPositionX;
    @FXML
    private TextField textFieldCameraPositionY;
    @FXML
    private TextField textFieldCameraPositionZ;
    @FXML
    private TextField textFieldCameraPointOfDirectionX;
    @FXML
    private TextField textFieldCameraPointOfDirectionY;
    @FXML
    private TextField textFieldCameraPointOfDirectionZ;
    @FXML
    private VBox lightingModelsManager;
    @FXML
    private CheckBox checkBoxDrawPolygonalGrid;
    @FXML
    private CheckBox checkBoxUseTexture;
    @FXML
    private TextField textFieldLightingModelDirectionX;
    @FXML
    private TextField textFieldLightingModelDirectionY;
    @FXML
    private TextField textFieldLightingModelDirectionZ;
    private Model mesh = null;
    private int counterModels = 0;
    private int counterCameras = 0;
    private int counterLightingModels = 0;

    private Camera camera = new Camera(
            new Vector3f(0, 0, 100),
            new Vector3f(0, 0, 0),
            0.3F, 1, 25F, 1000);

    private Timeline timeline;

    private boolean isLeftButtonPressed = false;
    private double lastMouseX, lastMouseY;
    private boolean isCtrlPressed = false;
    private float speed = 0.01F;

    @FXML
    private void loadModelFromFile() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Model (*.obj)", "*.obj"));
        fileChooser.setTitle("Load Model");

        File initialDirectory = new File("3DModels");
        if (initialDirectory.exists()) {
            fileChooser.setInitialDirectory(initialDirectory);
        }

        File file = fileChooser.showOpenDialog((Stage) canvas.getScene().getWindow());
        if (file == null) {
            return;
        }

        Path fileName = Path.of(file.getAbsolutePath());

        try {
            String fileContent = Files.readString(fileName);
            mesh = ObjReader.read(fileContent);

            mesh.triangulate();
            // todo: обработка ошибок
        } catch (IOException exception) {

        }
    }

    @FXML
    private void saveModelFromFile() {
        // objWriter
    }

    @FXML
    public void initialize() {
        canvas.setOnMousePressed(this::handleMousePressed);
        canvas.setOnMouseReleased(this::handleMouseReleased);
        canvas.setOnMouseDragged(this::handleMouseDragged);
        canvas.setOnScroll(this::handleScroll);
    }

    private void handleMousePressed(MouseEvent event) {
        if (event.isPrimaryButtonDown()) {
            isLeftButtonPressed = true;
            lastMouseX = event.getX();
            lastMouseY = event.getY();
            isCtrlPressed = event.isControlDown();
            if (event.isShiftDown()){
                speed = 0.02f;
                return;
            }
            speed = 0.01F;
        }
    }

    private void handleMouseReleased(MouseEvent event) {
        if (!event.isPrimaryButtonDown()) {
            isLeftButtonPressed = false;
        }
    }

    private void handleMouseDragged(MouseEvent event) {

        if (isLeftButtonPressed) {
            float deltaX = (float) (event.getX() - lastMouseX);
            float deltaY = (float) (event.getY() - lastMouseY);

            if (isCtrlPressed) {
                float deltaHeight = (float) deltaY * speed;
                moveUp(deltaHeight);
            } else {
                // Вращение вокруг OX
                float angleX = (float) deltaY * speed;
                float angleY = (float) deltaX * speed;
                pitch(angleX);
                yaw(angleY);
            }


            lastMouseX = event.getX();
            lastMouseY = event.getY();

            render();
        }
    }

    private void yaw(float angleY) {
        Matrix4f rotationMatrix = GraphicConveyor.Rotate(0, angleY, 0);
        Vector3f direction = camera.getTarget().sub(camera.getPosition());
        Vector4f dir = new Vector4f(direction.getX(), direction.getY(), direction.getZ(), 1);
        dir = rotationMatrix.multiply(dir);
        dir.normalize();

        direction = new Vector3f(dir.getX(), dir.getY(), dir.getZ());

        direction = camera.getPosition().add(direction);
        camera.setTarget(direction);
    }

    private void pitch(float angle) {

    }

    private void handleScroll(ScrollEvent event) {
        float deltaZ = (float) event.getDeltaY() * 0.1f; // Настройте множитель по необходимости
        zoom(deltaZ);
        render();
    }

    private void zoom(float deltaZ) {
    }

    private void moveUp(float deltaHeight) {

    }


    @FXML
    public void handleCameraForward(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, 0, -TRANSLATION));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraBackward(ActionEvent actionEvent) {

        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, 0, TRANSLATION));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraLeft(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(TRANSLATION, 0, 0));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraRight(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(-TRANSLATION, 0, 0));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraUp(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, TRANSLATION, 0));
        camera.setPosition(newPosition);
    }

    @FXML
    public void handleCameraDown(ActionEvent actionEvent) {
        Vector3f currentPosition = camera.getPosition();
        Vector3f newPosition = currentPosition.add(new Vector3f(0, -TRANSLATION, 0));
        camera.setPosition(newPosition);
    }


    private TransformationParameters parseParameters() throws NumberFormatException {
        TransformationParameters params = new TransformationParameters();
        params.setAlpha(Double.parseDouble(textFieldRotationX.getText()));
        params.setBeta(Double.parseDouble(textFieldRotationY.getText()));
        params.setGamma(Double.parseDouble(textFieldRotationZ.getText()));

        params.setTranslationX(Double.parseDouble(textFieldTranslationX.getText()));
        params.setTranslationY(Double.parseDouble(textFieldTranslationY.getText()));
        params.setTranslationZ(Double.parseDouble(textFieldTranslationZ.getText()));

        params.setScaleX(Double.parseDouble(textFieldScaleX.getText()));
        params.setScaleY(Double.parseDouble(textFieldScaleY.getText()));
        params.setScaleZ(Double.parseDouble(textFieldScaleZ.getText()));

        return params;
    }

    private void showErrorAlert(String title, String header, String content) {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle(title);
        alert.setHeaderText(header);
        alert.setContentText(content);
        alert.showAndWait();
    }


    @FXML
    private void recalculationOfNormals() {
        if (mesh != null) {
            TransformationParameters params;
            try {
                params = parseParameters();
            } catch (NumberFormatException e) {
                showErrorAlert("Ошибка ввода", "Некорректные данные",
                        "Пожалуйста, убедитесь, что все поля содержат числовые значения.");
                return;
            }

            Vector3f rota = new Vector3f((float) params.getAlpha(), (float) params.getBeta(), (float) params.getGamma());
            Vector3f scale = new Vector3f((float) params.getScaleX(), (float) params.getScaleY(), (float) params.getScaleZ());
            Vector3f trans = new Vector3f((float) params.getTranslationX(), (float) params.getTranslationY(), (float) params.getTranslationZ());


            Matrix4f MAT = GraphicConveyor.scaleRotateTranslate(rota, scale, trans);


            mesh.getVertices().replaceAll(vector3f -> GraphicConveyor.multiplyMatrix4ByVector3(MAT, vector3f));
            mesh.calculateNormals();
        }


    }

    @FXML
    private void addNewCamera() {
        // для кнопки
    }

    @FXML
    private void addNewLightingModel() {
        // для кнопки
    }


    private void startRendering() {
        if (timeline == null) {
            timeline = new Timeline(new KeyFrame(Duration.millis(20), event -> render()));
            timeline.setCycleCount(Animation.INDEFINITE);
            timeline.play();
        }
    }

    private void render() {
        double width = canvas.getWidth();
        double height = canvas.getHeight();

        canvas.getGraphicsContext2D().clearRect(0, 0, width, height);
        camera.setAspectRatio((float) (width / height));

        if (mesh != null) {
            renderEngine.render(canvas.getGraphicsContext2D(), camera, mesh, (int) width, (int) height);
        }

    }


    private void addModelControlButtons() {
        counterModels++;

        Button modelButton = new Button("Model " + counterModels);
        modelButton.setOnAction(event -> selectActiveModel(counterModels));


        Button addTextureButton = new Button("Add Texture");
        addTextureButton.setOnAction(event -> addTexture(counterModels));


        Button removeTextureButton = new Button("Remove Texture");
        removeTextureButton.setOnAction(event -> removeTexture(counterModels));


        Button deleteModelButton = new Button("Delete");
        deleteModelButton.setOnAction(event -> deleteModel(counterModels, modelButton));


        HBox modelControls = new HBox(5, modelButton, addTextureButton, removeTextureButton, deleteModelButton);


        modelsManager.getChildren().add(modelControls);
    }

    private void selectActiveModel(int modelId) {
        // Установите идентификатор модели как активной
        // Здесь можно изменить переменную, указывающую на выбранную модель для рендеринга
    }

    private void addTexture(int modelId) {
        FileChooser fileChooser = new FileChooser();
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Model (*.png)", "*.png"));
        fileChooser.setTitle("Load Textrure");

        File initialDirectory = new File("3DModels");
        if (initialDirectory.exists()) {
            fileChooser.setInitialDirectory(initialDirectory);
        }

        File file = fileChooser.showOpenDialog((Stage) canvas.getScene().getWindow());
        if (file == null) {
            return;
        }
        String filePath = file.getAbsolutePath();

        mesh.loadTexture(filePath);

    }

    public void loadModel() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Model (*.obj)", "*.obj"));
        fileChooser.setTitle("Load Model");

        File initialDirectory = new File("3DModels");
        if (initialDirectory.exists()) {
            fileChooser.setInitialDirectory(initialDirectory);
        }

        File file = fileChooser.showOpenDialog((Stage) canvas.getScene().getWindow());
        if (file == null) {
            return;
        }

        try {
            String fileContent = Files.readString(Path.of(file.getAbsolutePath()));
            mesh = ObjReader.read(fileContent);
            mesh.calculateNormals();
            mesh.triangulate();

            addModelControlButtons();  // Добавляем кнопки для управления моделью
            startRendering();          // Запускаем рендеринг
        } catch (IOException e) {
            showErrorAlert("Ошибка загрузки модели", null, "Не удалось загрузить модель: " + e.getMessage());
        }
    }

    private void removeTexture(int modelId) {
        mesh.clearTexture();
    }

    private void deleteModel(int modelId, Button modelButton) {
        // Удаление модели и её контролов
        mesh = null; // Убираем текущую модель
        modelsManager.getChildren().removeIf(node -> node instanceof HBox hBox && hBox.getChildren().contains(modelButton));
    }


    private void addCameraControlButtons() {
        counterCameras++;

        Button cameraButton = new Button("Camera " + counterCameras);
        // здесь возможность выбирать активную камеру, чтобы для нее вводить в поле настройки
        Button deleteCamera = new Button("Delete");
        // кнопку привязать к удалению
        HBox camera = new HBox(5, cameraButton, deleteCamera);

        camerasManager.getChildren().add(camera);
    }

    private void addLightModelControlButtons() {
        counterLightingModels++;

        Button lightModelButton = new Button("Light model " + counterLightingModels);
        // аналогично камере
        Button deleteLightButton = new Button("Delete");
        // кнопку к удалению
        HBox lightModel = new HBox(lightModelButton, deleteLightButton);

        lightingModelsManager.getChildren().add(lightModel);
    }

    @FXML
    public void onClickCanvas() {
        canvas.requestFocus();
    }


    @FXML
    private void returnToTheStartingPosition() {

    }


    @FXML
    private void moveToTheModel() {

    }


    @FXML
    private void handleCheckBoxDrawPolygonalGrid() {
        renderEngine.setEnableWireframe(checkBoxDrawPolygonalGrid.isSelected());
        render();
    }

    @FXML
    private void handleCheckBoxUseTexture() {
        renderEngine.setEnableTextureMapping(checkBoxUseTexture.isSelected());
        render();
    }

    @FXML
    private void handleCheckBoxUseIntrLighting() {
        renderEngine.setEnableInterpolatedLighting(checkBoxUseIntrLighting.isSelected());
        render();
    }

    @FXML
    private void handleCheckBoxUseFlatLighting() {
        renderEngine.setEnableFlatLighting(checkBoxUseFlatLighting.isSelected());
        render();
    }

    @FXML
    private void handleCheckBoxFillPolygon() {
        renderEngine.setEnableFillPolygon(checkBoxUseFillPolygon.isSelected());
        render();
    }


}
я хочу вращать камерой вокруг объекта, с зажатой левой клавишуй; кроме того, используя колесико мыши, приближать объект. и зажимая контрол + левая кнопка мыши, изменять высоту. как мне это сделать?
public static Matrix4f Rotate(float alpha, float beta, float gamma) {
        return calculateRotate(alpha, beta, gamma);
    }

    // мб потом почитаю что такое кватернион и немного переделаю код, пока пусть так будет
    private static Matrix4f calculateRotate(float alpha, float beta, float gamma) {
        // вокруг x
        float[] rotateX = new float[]{
                1, 0, 0, 0,
                0, (float) cos(Math.toRadians(alpha)), (float) sin(Math.toRadians(alpha)), 0,
                0, (float) -sin(Math.toRadians(alpha)), (float) cos(Math.toRadians(alpha)), 0,
                0, 0, 0, 1
        };
        // вокруг y
        float[] rotateY = new float[]{
                (float) cos(Math.toRadians(beta)), 0, (float) sin(Math.toRadians(beta)), 0,
                0, 1, 0, 0,
                (float) -sin(Math.toRadians(beta)), 0, (float) cos(Math.toRadians(beta)), 0,
                0, 0, 0, 1
        };
        // вокруг z
        float[] rotateZ = new float[]{
                (float) cos(Math.toRadians(gamma)), (float) sin(Math.toRadians(gamma)), 0, 0,
                (float) -sin(Math.toRadians(gamma)), (float) cos(Math.toRadians(gamma)), 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1
        };

        Matrix4f rotateAboutX = new Matrix4f(rotateX);
        Matrix4f rotateAboutY = new Matrix4f(rotateY);
        Matrix4f rotateAboutZ = new Matrix4f(rotateZ);

        // сначала крутим вокруг x, потом y, потом z (да, в реале умножаем сначала Z, потом ыгрик, потом х (а) - плохо)
        rotateAboutZ.multiply(rotateAboutY);
        rotateAboutZ.multiply(rotateAboutX);

        return rotateAboutZ;
    }
package com.cgvsu.render_engine;

import com.cgvsu.math.Matrix4f;
import com.cgvsu.math.Vector3f;


public class Camera {

    private Vector3f position;
    private Vector3f target;
    private float fov;
    private float aspectRatio;
    private float nearPlane;
    private float farPlane;
    private Vector3f up;

    public Camera(
            final Vector3f position,
            final Vector3f target,
            final float fov,
            final float aspectRatio,
            final float nearPlane,
            final float farPlane) {
        this.position = position;
        this.target = target;
        this.up = new Vector3f(0, 1, 0);
        this.fov = fov;
        this.aspectRatio = aspectRatio;
        this.nearPlane = nearPlane;
        this.farPlane = farPlane;
    }

    public void setAspectRatio(final float aspectRatio) {
        this.aspectRatio = aspectRatio;
    }

    public Vector3f getPosition() {
        return position;
    }

    public void setPosition(final Vector3f position) {
        this.position = position;
    }

    public Vector3f getTarget() {
        return target;
    }

    public void setTarget(final Vector3f target) {
        this.target = target;
    }

    public void movePosition(final Vector3f translation) {
        this.position.add(translation);
    }

    public void moveTarget(final Vector3f translation) {
        this.target.add(translation);
    }

    // матрица V
    Matrix4f getViewMatrix() {
        return GraphicConveyor.lookAt(position, target);
    }

    // P
    Matrix4f getProjectionMatrix() {
        return GraphicConveyor.perspective(fov, aspectRatio, nearPlane, farPlane);
    }

}