/*******************************************************************************
 * Copyright 2019 See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.talosvfx.talos.editor.widgets.ui;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.*;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.profiling.GLProfiler;
import com.badlogic.gdx.math.FloatCounter;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.PerformanceCounter;
import com.badlogic.gdx.utils.StringBuilder;
import com.badlogic.gdx.utils.TimeUtils;
import com.talosvfx.talos.TalosMain;
import com.talosvfx.talos.editor.wrappers.IDragPointProvider;
import com.talosvfx.talos.runtime.ParticleEffectInstance;
import com.talosvfx.talos.runtime.render.ParticleRenderer;
import com.talosvfx.talos.runtime.render.SpriteBatchParticleRenderer;

public class PreviewWidget extends ViewportWidget {

    Vector2 mid = new Vector2();

    Vector2 temp = new Vector2();

    private ParticleRenderer particleRenderer;

    private SpriteBatchParticleRenderer spriteBatchParticleRenderer;

    private ShapeRenderer shapeRenderer;

    private Color tmpColor = new Color();

    private PreviewImageControllerWidget previewController;

    private Image previewImage = new Image();

    private String countStr = "Particles: ";
    private String trisCountStr = "Triangles: ";
    private String nodeCallsStr = "Node Calls: ";
    private String gpuTimeStr = "GPU: ";
    private String cpuTimeStr = "CPU: ";
    private String msStr = "ms";

    private Label countLbl;
    private Label trisCountLbl;
    private Label nodeCallsLbl;
    private Label gpuTimeLbl;
    private Label cpuTimeLbl;

    private GLProfiler glProfiler = new GLProfiler(Gdx.graphics);
    private FPSLogger fpsLogger = new FPSLogger();
    private PerformanceCounter performanceCounter = new PerformanceCounter("talos");

    private StringBuilder stringBuilder = new StringBuilder();
    private int trisCount = 0;
    private FloatCounter renderTime = new FloatCounter(100);
    private FloatCounter cpuTime = new FloatCounter(100);
    private float fps = 0;

    private Array<DragPoint> dragPoints = new Array<>();
    private IDragPointProvider dragPointProvider = null;

    private String backgroundImagePath = "";
    private float gridSize;

    public PreviewWidget() {
        super();

        setWorldSize(10f);
        gridSize = 1f;

        spriteBatchParticleRenderer = new SpriteBatchParticleRenderer(null);
        particleRenderer = spriteBatchParticleRenderer;
        shapeRenderer = new ShapeRenderer();
        previewController = new PreviewImageControllerWidget(TalosMain.Instance().getSkin()) {
            @Override
            public void removeImage () {
                super.removeImage();
                previewImage.setDrawable(null);
                backgroundImagePath = "";
            }

            @Override
            public void gridSizeChanged(float size) {
                super.gridSizeChanged(size);
                setWorldSize(10f * size);
                gridSize = size;
                resetCamera();
            }
        };

        countLbl = new Label(countStr, TalosMain.Instance().getSkin());
        trisCountLbl = new Label(trisCountStr, TalosMain.Instance().getSkin());
        nodeCallsLbl = new Label(nodeCallsStr, TalosMain.Instance().getSkin());
        gpuTimeLbl = new Label(gpuTimeStr, TalosMain.Instance().getSkin());
        cpuTimeLbl = new Label(cpuTimeStr, TalosMain.Instance().getSkin());

        countLbl.setColor(Color.GRAY);
        trisCountLbl.setColor(Color.GRAY);
        nodeCallsLbl.setColor(Color.GRAY);
        gpuTimeLbl.setColor(Color.GRAY);
        cpuTimeLbl.setColor(Color.GRAY);

        add(countLbl).left().top().padLeft(5).row();
        add(trisCountLbl).left().top().padLeft(5).row();
        add(nodeCallsLbl).left().top().padLeft(5).row();
        add(cpuTimeLbl).left().top().padLeft(5).row();
        add(gpuTimeLbl).left().top().padLeft(5).row();
        add().expand();
        row();
        add(previewController).bottom().left().growX();

        cameraController.scrollOnly = true; // camera controller can't operate in this shitty custom conditions
        addListener(new InputListener() {

            boolean moving = false;
            private Vector3 tmp = new Vector3();
            private Vector3 tmp2 = new Vector3();
            private Vector2 prevPos = new Vector2();
            private Vector2 pos = new Vector2();

            private DragPoint currentlyDragging = null;

            @Override
            public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
                moving = false;
                getWorldFromLocal(tmp.set(x, y, 0));
                pos.set(tmp.x, tmp.y);
                prevPos.set(x, y);

                //detect drag points
                for(DragPoint point: dragPoints) {
                    if(pos.dst(point.position) < 0.2f * camera.zoom) {
                        // dragging a point
                        currentlyDragging = point;
                        return true;
                    }
                }

                if(button == 1) {
                    moving = true;
                    return true;
                }
                return true;
            }

            @Override
            public void touchDragged(InputEvent event, float x, float y, int pointer) {
                super.touchDragged(event, x, y, pointer);

                getWorldFromLocal(tmp.set(x, y, 0)); // I can't really explain
                pos.set(tmp.x, tmp.y);

                if(moving) {
                    final ParticleEffectInstance particleEffect = TalosMain.Instance().TalosProject().getParticleEffect();
                    particleEffect.setPosition(tmp.x, tmp.y);
                } else {
                    getWorldFromLocal(tmp.set(prevPos.x, prevPos.y, 0));
                    getWorldFromLocal(tmp2.set(x, y, 0));

                    if(currentlyDragging == null) {
                        // panning

                        camera.position.sub(tmp2.x-tmp.x, tmp2.y-tmp.y, 0);
                    } else {
                        // dragging a point
                        currentlyDragging.position.add(tmp2.x-tmp.x, tmp2.y-tmp.y);
                        dragPointProvider.dragPointChanged(currentlyDragging);
                    }
                }

                prevPos.set(x, y);
            }

            @Override
            public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
                super.touchUp(event, x, y, pointer, button);
                currentlyDragging = null;
            }

            @Override
            public boolean keyUp(InputEvent event, int keycode) {
                return super.keyUp(event, keycode);
            }
        });
    }


    public void fileDrop (float x, float y, String[] paths) {
        temp.set(x, y);
        (getStage().getViewport()).unproject(temp);
        stageToLocalCoordinates(temp);
        if(this.hit(temp.x, temp.y, false) != null) {
           addPreviewImage(paths);
        }
    }

    public void addPreviewImage (String[] paths) {
        if (paths.length == 1) {

            String resourcePath = paths[0];
            FileHandle fileHandle = Gdx.files.absolute(resourcePath);

            final String extension = fileHandle.extension();

            if (extension.endsWith("png") || extension.endsWith("jpg")) {
                fileHandle = TalosMain.Instance().ProjectController().findFile(fileHandle);
                if(fileHandle != null && fileHandle.exists()) {
                    final TextureRegion textureRegion = new TextureRegion(new Texture(fileHandle));

                    if (textureRegion != null) {
                        previewImage.setDrawable(new TextureRegionDrawable(textureRegion));
                        previewController.setImageWidth(10);

                        backgroundImagePath = fileHandle.path();

                        TalosMain.Instance().ProjectController().setDirty();
                    }
                }
            }
        }
    }

    @Override
    public void act(float delta) {
        super.act(delta);

        //stupid hack, plz do it normal way
        if(PreviewWidget.this.hasScrollFocus() && Gdx.input.isKeyJustPressed(Input.Keys.ENTER)) {
            resetCamera();
        }

        long timeBefore = TimeUtils.nanoTime();
        final ParticleEffectInstance particleEffect = TalosMain.Instance().TalosProject().getParticleEffect();
        particleEffect.update(Gdx.graphics.getDeltaTime());
        cpuTime.put( TimeUtils.timeSinceNanos(timeBefore));

        stringBuilder.clear();
        stringBuilder.append(countStr).append(particleEffect.getParticleCount());
        countLbl.setText(stringBuilder.toString());

        stringBuilder.clear();
        stringBuilder.append(trisCountStr).append(trisCount);
        trisCountLbl.setText(stringBuilder.toString());

        stringBuilder.clear();
        stringBuilder.append(nodeCallsStr).append(particleEffect.getNodeCalls());
        nodeCallsLbl.setText(stringBuilder.toString());

        float rt = renderTime.value/1000000f;
        float cp = cpuTime.value/1000000f;

        rt = (float)Math.round(rt * 10000f) / 10000f;
        cp = (float)Math.round(cp * 10000f) / 10000f;

        stringBuilder.clear();
        stringBuilder.append(gpuTimeStr).append(rt).append(msStr);
        gpuTimeLbl.setText(stringBuilder.toString());

        stringBuilder.clear();
        stringBuilder.append(cpuTimeStr).append(cp).append(msStr);
        cpuTimeLbl.setText(stringBuilder.toString());
    }

    @Override
    public void drawContent(Batch batch, float parentAlpha) {
        batch.end();
        drawGrid(batch, parentAlpha * 0.5f);
        batch.begin();

        mid.set(0, 0);

        float imagePrefWidth = previewImage.getPrefWidth();
        float imagePrefHeight = previewImage.getPrefHeight();
        float scale = imagePrefHeight / imagePrefWidth;

        float imageWidth = previewController.getImageWidth();
        float imageHeight = imageWidth * scale;
        previewController.getPreviewBoxWidth();

        previewImage.setPosition(mid.x - imageWidth / 2, mid.y - imageHeight / 2);
        previewImage.setSize(imageWidth, imageHeight);
        if (previewController.isBackground()) {
            previewImage.draw(batch, parentAlpha);
        }

        spriteBatchParticleRenderer.setBatch(batch);

        batch.flush();
        glProfiler.enable();

        long timeBefore = TimeUtils.nanoTime();

        final ParticleEffectInstance particleEffect = TalosMain.Instance().TalosProject().getParticleEffect();
        particleEffect.render(particleRenderer);

        batch.flush();
        renderTime.put(TimeUtils.timeSinceNanos(timeBefore));
        trisCount = (int) (glProfiler.getVertexCount().value / 3f);
        glProfiler.disable();


        if (!previewController.isBackground()) {
            previewImage.draw(batch, parentAlpha);
        }

        // now for the drag points
        if(dragPoints.size > 0) {
            batch.end();
            tmpColor.set(Color.ORANGE);
            tmpColor.a = 0.8f;
            Gdx.gl.glLineWidth(1f);
            Gdx.gl.glEnable(GL20.GL_BLEND);
            Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
            shapeRenderer.setProjectionMatrix(batch.getProjectionMatrix());
            shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
            shapeRenderer.setColor(tmpColor);

            for (DragPoint point : dragPoints) {
                shapeRenderer.circle(point.position.x, point.position.y, 0.1f * camera.zoom, 15);
            }

            shapeRenderer.end();
            batch.begin();
        }
    }

    public GLProfiler getGLProfiler() {
        return glProfiler;
    }

    public void registerForDragPoints(IDragPointProvider dragPointProvider) {
        this.dragPointProvider = dragPointProvider;
        DragPoint[] arr = dragPointProvider.fetchDragPoints();
        dragPoints.clear();
        for(int i = 0; i < arr.length; i++) {
            dragPoints.add(arr[i]);
        }
    }

    public void unregisterDragPoints() {
        this.dragPointProvider = null;
        dragPoints.clear();
    }

    public void unregisterDragPoints(IDragPointProvider dragPointProvider) {
        if(this.dragPointProvider == dragPointProvider) {
            this.dragPointProvider = null;
            dragPoints.clear();
        }
    }

    public String getBackgroundImagePath() {
        return backgroundImagePath;
    }

    public boolean isBackgroundImageInBack() {
        return previewController.isBackground();
    }

    public void setImagebIsBackground(boolean isBackground) {
        previewController.setIsBackground(isBackground);
    }

    public float getBgImageSize() {
        return previewController.getImageWidth();
    }

    public void setBgImageSize(float size) {
        previewController.setImageWidth(size);
    }

    public void setBackgroundImage(String bgImagePath) {
        if(bgImagePath != null) {
            addPreviewImage(new String[] {bgImagePath});
        } else {
            previewImage.setDrawable(null);
            backgroundImagePath = "";
        }
    }

    public void resetToDefaults() {
        previewImage.setDrawable(null);
        backgroundImagePath = "";
        setBgImageSize(10f);
        setImagebIsBackground(true);
        setCameraZoom(1.4285715f);
        setCameraPos(0, 0);
        unregisterDragPoints();
    }

    public float getGridSize() {
        return gridSize;
    }

    public void setGridSize(float gridSize) {
        previewController.setGridSize(gridSize);
        setWorldSize(10f * gridSize);
        this.gridSize = gridSize;
    }
}