import * as THREE from 'three';
import TrackballControls from 'three-trackballcontrols';
import {initiateScene} from './AppletControls.js';

const MATRICES_COUNT = 6;

/**
 * Main function links DOM elements to listeners.
 * @param x
 * @constructor
 */
function Scene(x) {
    var selects = []
    selects.push(document.getElementById('select_matrix_0'));
    selects.push(document.getElementById('select_matrix_1'));
    selects.push(document.getElementById('select_matrix_2'));
    selects.push(document.getElementById('select_matrix_3'));
    selects.push(document.getElementById('select_matrix_4'));
    selects.push(document.getElementById('select_matrix_5'));

    var selectShape = document.getElementById("select_shape");
    var sceneCanvasM = document.getElementById("scene_m");
    var buttonCreate = document.getElementById("button_create");
    var buttonTransform = document.getElementById("button_transform");

    var objects;
    var matrixOldfinal = new THREE.Matrix4();

    // set basic matrices values to every frontEnd matrix
    for (let i = 0; i < MATRICES_COUNT; i++) {
        setMatrix(new THREE.Matrix4(), i);
    }

    // initiate scene
    const sceneMObjects = initiateScene(sceneCanvasM);

    // add axis
    var axesHelper = new THREE.AxesHelper(8);
    sceneMObjects.scene.add(axesHelper);

    // create objects
    objects = createObjects(selectShape.value, sceneMObjects.scene);

    // button create listener
    buttonCreate.addEventListener('click', function(){
        removeObjects(objects, sceneMObjects.scene)
        objects = createObjects(selectShape.value, sceneMObjects.scene);
        matrixOldfinal = new THREE.Matrix4();
        sceneMObjects.controls.reset();
    });

    // add matrices selects listeners
    selects.forEach(function (select, matrixId) {

        select.addEventListener('change', function(){
            const matrix4 = getType(select.value);
            setMatrix(matrix4, matrixId);
        });

    });


    // add Transform button listener
    buttonTransform.addEventListener('click', function(){
        let matrixFinal = calculateFinalMatrix();
        objects = transformObjects(objects, matrixFinal, matrixOldfinal);
        matrixOldfinal = matrixFinal;
    });

}

/**
 * Function sets values from Matrix4 object to matrix in front end.
 * @param matrix4
 * @param matrixId
 */
function setMatrix(matrix4, matrixId ) {

    const nameSuffix = '-' + matrixId;

    matrix4.transpose();

    for (let i = 0; i < 16; i++) {
        document.getElementById(i + nameSuffix).value = matrix4.elements[i];
    }

}

/**
 * Function gets number of selected transformation and creates Matrix4 object with selected transformation.
 * @param selectShapeValue
 * @returns {Matrix4}
 */
function getType (selectShapeValue) {
    var tmpMatrix = new THREE.Matrix4();

    switch (selectShapeValue) {
        case '1':
            tmpMatrix.makeTranslation(2,2,2);
            break;
        case '2':
            tmpMatrix = tmpMatrix.makeScale(2,2,2);
            break;
        case '3':
            tmpMatrix = tmpMatrix.makeShear(2,2,2); // TODO check issue
            break;
        case '4':
            tmpMatrix = tmpMatrix.makeRotationX(2);
            break;
    }

    return tmpMatrix;
}

/**
 * Function removes objects from the scene.
 * @param objects
 * @param scene
 */
function removeObjects(objects, scene) {
    scene.remove(objects.newObject);
    scene.remove(objects.oldObject);
}

/**
 * Function multiplies transformation matrices to create final transformation matrix.
 * @returns {Matrix4}
 */
function calculateFinalMatrix () {
    var nameSuffix = '-';

    var sourceMatrices = [];
    var matrices4 = [];
    var matrixFinal = new THREE.Matrix4();

    for (let i = 0; i < MATRICES_COUNT; i++) {
        let matrix4 = getMatrixInput(i);
        matrices4.push(matrix4);
        // setMatrix(matrix4, i);
    }

    matrices4.forEach(function (matrix4, matrixId) {

        matrixFinal.multiplyMatrices(matrixFinal, matrix4);

    });


    setMatrix(matrixFinal, 'f');

    matrixFinal.transpose();
    return matrixFinal;

}

/**
 * Function applies transformation matrix to new object and old transformation matrix to old object.
 * @param objects
 * @param matrixFinal
 * @param matrixOldFinal
 * @returns {*}
 */
function transformObjects(objects, matrixFinal, matrixOldFinal) {

        objects.newObject.geometry.applyMatrix4(matrixFinal);
        objects.oldObject.geometry.applyMatrix4(matrixOldFinal);

        return objects;
}

/**
 * Function loads values from selected matrix.
 * @param matrixId
 * @returns {Matrix4}
 */
function getMatrixInput(matrixId) {

    var nameSuffix = '-' + matrixId;

    var sourceMatrix =[];
    var matrix4 = new THREE.Matrix4();

    for (let i = 0; i < 16; i++) {
        sourceMatrix.push(document.getElementById(i + nameSuffix).value);
    }

    matrix4.elements = sourceMatrix;

    matrix4.transpose();

    return matrix4;
}

/**
 * Function creates object based on selected shape.
 * @param selectID
 * @param scene
 * @returns {{newObject: Mesh<BoxGeometry | SphereGeometry | TorusGeometry, MeshLambertMaterial>, oldObject: Mesh<BoxGeometry | SphereGeometry | TorusGeometry, MeshLambertMaterial>}}
 */
function createObjects( selectID, scene ) {
    // GEOMETRY & MATERIALS

    // 0x123456 old color
    var newMaterial = new THREE.MeshLambertMaterial({color: 0x277AAC, transparent: true, opacity: 1, flatShading: false});
    var oldMaterial = new THREE.MeshLambertMaterial({color: 0x156611, transparent: true, opacity: 0.8, flatShading: false}); //0x156611
    newMaterial.depthTest = false;
    oldMaterial.depthTest = false;

    var geometry;

    switch (selectID) {
        case '1':
            geometry = new THREE.BoxGeometry(2, 2, 2);
            break;
        case '2':
            geometry = new THREE.SphereGeometry(2, 16, 16);
            break;
        case '3':
            geometry = new THREE.TorusGeometry(2, 0.5, 20, 50);
            break;
        case '4':
            geometry = new THREE.BoxGeometry(3, 3, 3); // TODO add last shape
            break;
    }

    var newObject = new THREE.Mesh(geometry, newMaterial);
    var oldObject = new THREE.Mesh(geometry.clone(), oldMaterial);


    scene.add(oldObject);
    scene.add(newObject);


    newObject.matrixAutoUpdate = false;
    oldObject.matrixAutoUpdate = false;

    return {newObject, oldObject};
}

export default Scene;