package com.dark98.santoku.slic3r;

import static android.opengl.GLES30.*;
import static com.dark98.santoku.utils.DebugUtils.assertTrue;


import java.io.File;

import com.dark98.santoku.R;
import com.dark98.santoku.render.CoordAxes;
import com.dark98.santoku.theme.ThemesRepo;
import com.dark98.santoku.utils.DoubleMatrix;
import com.dark98.santoku.utils.Vec3d;
import com.dark98.santoku.utils.ViewUtils;

public class Bed3D {
    private final static float GROUND_Z = -0.02f;

    private long pointer;

    private GLModel triangles;
    private GLModel gridlines;
    private GLModel contourlines;

    private CoordAxes axes = new CoordAxes();
    private double[] boundingVolume;
    private Vec3d min, max;

    private boolean likelyDelta;

    private double[] modelMatrix = new double[16];
    private double[] outModelMatrix = new double[16];

    public Bed3D() {
        long[] data = new long[3];
        pointer = Native.bed_create(data);
        triangles = new GLModel(data[0]);
        gridlines = new GLModel(data[1]);
        contourlines = new GLModel(data[2]);
    }

    public void configure(File f) {
        configure(f.getAbsolutePath());
    }

    private void configure(String path) {
        Native.bed_configure(pointer, path);
        Native.bed_init_triangles_mesh(pointer, triangles.pointer);
        boundingVolume = Native.bed_get_bounding_volume(pointer);

        min = max = null;

        axes.origin.set(0, 0, GROUND_Z);
        axes.setStemLength(0.1f * Native.bed_get_bounding_volume_max_size(pointer));

        if (isValid()) {
            Vec3d center = getVolumeMin().center(getVolumeMax());
            likelyDelta = (center.x == 0 || center.y == 0) && getVolumeMin().x < 0 && getVolumeMin().y < 0;
        } else {
            likelyDelta = false;
        }
    }

    public void arrange(Model model) {
        Native.bed_arrange(pointer, model.pointer);
    }

    public Vec3d getVolumeMin() {
        if (min == null && boundingVolume != null) min = new Vec3d(boundingVolume[0], boundingVolume[1], boundingVolume[2]);
        return min;
    }

    public Vec3d getVolumeMax() {
        if (max == null && boundingVolume != null) max = new Vec3d(boundingVolume[3], boundingVolume[4], boundingVolume[5]);
        return max;
    }

    public boolean isValid() {
        return boundingVolume != null;
    }

    public GLModel.MeshRaycaster getRaycaster() {
        return triangles.getRaycaster();
    }

    public void render(GLShadersManager shadersManager, boolean bottom, double[] viewModelMatrix, double[] projectionMatrix, float invZoom) {
        assertTrue(viewModelMatrix.length == 16);
        assertTrue(projectionMatrix.length == 16);

        DoubleMatrix.setIdentityM(modelMatrix, 0);
        DoubleMatrix.multiplyMM(outModelMatrix, 0, viewModelMatrix, 0, modelMatrix, 0);
        renderDefaultBed(shadersManager, bottom, outModelMatrix, projectionMatrix);
        axes.render(shadersManager, viewModelMatrix, projectionMatrix, 0.25f, invZoom);
    }

    private void renderDefaultBed(GLShadersManager shadersManager, boolean bottom, double[] viewModelMatrix, double[] projectionMatrix) {
        GLShaderProgram shader = shadersManager.get(GLShadersManager.SHADER_FLAT);
        shader.startUsing();

        shader.setUniformMatrix4fv("view_model_matrix", viewModelMatrix);
        shader.setUniformMatrix4fv("projection_matrix", projectionMatrix);

        glEnable(GL_DEPTH_TEST);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        if (!bottom) {
            glDepthMask(false);
            triangles.setColor(ThemesRepo.getColor(R.attr.defaultBedColor));
            triangles.render();
            glDepthMask(true);
        }

        glLineWidth(ViewUtils.dp(1));
        gridlines.setColor(ThemesRepo.getColor(R.attr.bedGridlinesColor));
        gridlines.render();

        contourlines.setColor(ThemesRepo.getColor(R.attr.bedContourlinesColor));
        contourlines.render();

        glDisable(GL_BLEND);

        shader.stopUsing();
    }

    private void renderTexturedBed(GLShadersManager shadersManager, boolean bottom, float[] viewModelMatrix, float[] projectionMatrix) {
        GLShaderProgram shader = shadersManager.get(GLShadersManager.SHADER_PRINTBED);
        shader.startUsing();

        shader.setUniform3f("view_model_matrix", viewModelMatrix);
        shader.setUniform3f("projection_matrix", projectionMatrix);
        shader.setUniform("transparent_background", bottom);
        shader.setUniform("svg_source", false);

        glEnable(GL_DEPTH_TEST);
        if (bottom) {
            glDepthMask(false);
        }
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        if (bottom) {
            glFrontFace(GL_CW);
        }

        // TODO: glBindTexture(GL_TEXTURE_2D, tex_id);
        glBindTexture(GL_TEXTURE_2D, 0);

        if (bottom) {
            glFrontFace(GL_CCW);
        }
        glDisable(GL_BLEND);
        if (bottom) {
            glDepthMask(true);
        }

        shader.stopUsing();
    }

    public void release() {
        if (pointer != 0) {
            Native.bed_release(pointer);
            axes.release();
            pointer = 0;

//        triangles.release();
//        gridlines.release();
//        contourlines.release();
        }
    }

    @Override
    protected void finalize() throws Throwable {
        release();
    }
}
