/*******************************************************************************
* Copyright (c) 2013 "Filippo Scognamiglio"
* https://github.com/Swordifish90/cool-old-term
*
* This file is part of cool-old-term.
*
* cool-old-term is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/

import QtQuick 2.2
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.1

import org.kde.konsole 0.1

Item{
    id: terminalContainer
    property variant theSource: finalSource
    property variant bloomSource: bloomSourceLoader.item
    property variant rasterizationSource: rasterizationEffectSource
    property variant staticNoiseSource: staticNoiseSource

    property alias kterminal: kterminal

    signal sizeChanged
    onWidthChanged: sizeChanged()
    onHeightChanged: sizeChanged()

    //The blur effect has to take into account the framerate
    property int fps: shadersettings.fps !== 0 ? shadersettings.fps : 60
    property real fpsAttenuation: Math.sqrt(60 / fps)
    property real mBlur: shadersettings.motion_blur
    property real motionBlurCoefficient: (_maxBlurCoefficient * mBlur + _minBlurCoefficient * (1 - mBlur))
    property real _minBlurCoefficient: 0.70
    property real _maxBlurCoefficient: 0.90

    property size virtualPxSize: Qt.size(1,1)
    property size virtual_resolution: Qt.size(width / virtualPxSize.width, height / virtualPxSize.height)
    property real deltay: 0.5 / virtual_resolution.height
    property real deltax: 0.5 / virtual_resolution.width

    property real mBloom: shadersettings.bloom_strength
    property int mScanlines: shadersettings.rasterization
    onMScanlinesChanged: restartBlurredSource()

    property size terminalSize: kterminal.terminalSize
    property size paintedTextSize

    onMBlurChanged: restartBlurredSource()

    function restartBlurredSource(){
        if(!blurredSource) return;
        blurredSource.live = true;
        livetimer.restart()
    }
    function pasteClipboard(){
        kterminal.pasteClipboard();
    }
    function copyClipboard(){
        kterminal.copyClipboard();
    }

    KTerminal {
        id: kterminal
        anchors.fill: parent

        colorScheme: "cool-old-term"

        session: KSession {
            id: ksession
            kbScheme: "linux"

            onFinished: {
                Qt.quit()
            }
        }

        FontLoader{ id: fontLoader }
        Text{id: fontMetrics; text: "B"; visible: false}

        function getPaintedSize(pixelSize){
            fontMetrics.font.family = fontLoader.name;
            fontMetrics.font.pixelSize = pixelSize;
            return Qt.size(fontMetrics.paintedWidth, fontMetrics.paintedHeight);
        }
        function isValid(size){
            return size.width >= 0 && size.height >= 0;
        }
        function handleFontChange(fontSource, pixelSize, lineSpacing, virtualCharSize){
            fontLoader.source = fontSource;
            font.pixelSize = pixelSize * shadersettings.window_scaling;
            font.family = fontLoader.name;

            var paintedSize = getPaintedSize(pixelSize);
            var charSize = isValid(virtualCharSize)
                    ? virtualCharSize
                    : Qt.size(paintedSize.width / 2, paintedSize.height / 2);

            var virtualPxSize = Qt.size((paintedSize.width  / charSize.width) * shadersettings.window_scaling,
                                        (paintedSize.height / charSize.height) * shadersettings.window_scaling)

            terminalContainer.virtualPxSize = virtualPxSize;

            setLineSpacing(lineSpacing * shadersettings.window_scaling);
            restartBlurredSource();
        }
        Component.onCompleted: {
            shadersettings.terminalFontChanged.connect(handleFontChange);
            forceActiveFocus();
        }
    }
    Menu{
        id: contextmenu

        MenuItem{action: copyAction}
        MenuItem{action: pasteAction}
        MenuSeparator{}
        MenuItem{action: fullscreenAction}
        MenuItem{action: showMenubarAction}
    }
    MouseArea{
        acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
        anchors.fill: parent
        onWheel:{
            var coord = correctDistortion(wheel.x, wheel.y);
            var lines = wheel.angleDelta.y > 0 ? -2 : 2;
            kterminal.scrollWheel(coord.width, coord.height, lines);
        }
        onClicked: {
            if (mouse.button == Qt.RightButton){
                contextmenu.popup();
            } else if (mouse.button == Qt.MiddleButton){
                kterminal.pasteSelection();
            }
        }
        onDoubleClicked: {
            if (mouse.button == Qt.LeftButton){
                var coord = correctDistortion(mouse.x, mouse.y);
                kterminal.mouseDoubleClick(coord.width, coord.height);
            }
        }
        onPositionChanged: {
            if (pressedButtons & Qt.LeftButton){
                var coord = correctDistortion(mouse.x, mouse.y);
                kterminal.mouseMove(coord.width, coord.height);
            }
        }
        onPressed: {
            if (mouse.button == Qt.LeftButton){
                var coord = correctDistortion(mouse.x, mouse.y);
                kterminal.mousePress(coord.width, coord.height);
            }
        }
        onReleased: {
            if (mouse.button == Qt.LeftButton){
                var coord = correctDistortion(mouse.x, mouse.y);
                kterminal.mouseRelease(coord.width, coord.height);
            }
        }

        //Frame displacement properties
        property real dtop: frame.item.displacementTop
        property real dleft:frame.item.displacementLeft
        property real dright: frame.item.displacementRight
        property real dbottom: frame.item.displacementBottom

        function correctDistortion(x, y){
            x = x / width;
            y = y / height;

            x = (-dleft + x * (width + dleft + dright)) / width
            y = (-dtop  + y * (height + dtop + dbottom)) / height

            var cc = Qt.size(0.5 - x, 0.5 - y);
            var distortion = (cc.height * cc.height + cc.width * cc.width) * shadersettings.screen_distortion;

            return Qt.size((x - cc.width  * (1+distortion) * distortion) * width,
                           (y - cc.height * (1+distortion) * distortion) * height)
        }
    }
    ShaderEffectSource{
        id: source
        sourceItem: kterminal
        hideSource: true
        smooth: false
    }
    ShaderEffectSource{
        id: blurredSource
        sourceItem: blurredterminal
        recursive: true
        live: false

        hideSource: true

        smooth: false
        antialiasing: false

        Timer{
            id: livetimer
            running: true
            onRunningChanged: running ?
                                  timeManager.onTimeChanged.connect(blurredSource.scheduleUpdate) :
                                  timeManager.onTimeChanged.disconnect(blurredSource.scheduleUpdate)

            Component.onCompleted: kterminal.updatedImage.connect(restart);
        }
    }
    ShaderEffectSource{
        id: finalSource
        sourceItem: blurredterminal
        sourceRect: frame.sourceRect
        //format: ShaderEffectSource.Alpha
        hideSource: true
    }
    ShaderEffect {
        id: blurredterminal
        anchors.fill: parent
        property variant source: source
        property variant blurredSource: (mBlur !== 0) ? blurredSource : undefined
        property size virtual_resolution: parent.virtual_resolution
        property size delta: Qt.size((mScanlines == shadersettings.pixel_rasterization ? deltax : 0),
                                     mScanlines != shadersettings.no_rasterization ? deltay : 0)
        blending: false

        fragmentShader:
            "uniform lowp float qt_Opacity;" +
            "uniform lowp sampler2D source;" +
            "uniform highp vec2 delta;" +

            "varying highp vec2 qt_TexCoord0;

             uniform highp vec2 virtual_resolution;" +

            (mBlur !== 0 ?
            "uniform lowp sampler2D blurredSource;"
            : "") +

            "void main() {" +
                "vec2 coords = qt_TexCoord0;" +
                (mScanlines != shadersettings.no_rasterization ? "
                            coords.y = floor(virtual_resolution.y * coords.y) / virtual_resolution.y;" +
                (mScanlines == shadersettings.pixel_rasterization ? "
                            coords.x = floor(virtual_resolution.x * coords.x) / virtual_resolution.x;" : "")
                : "") +
                "coords = coords + delta;" +
                "vec4 vcolor = texture2D(source, coords) * 256.0;
                 float color = vcolor.r * 0.21 + vcolor.g * 0.72 + vcolor.b + 0.04;" +
                (mBlur !== 0 ?
                    "float blurredSourceColor = texture2D(blurredSource, coords).a * 256.0;" +
                    "blurredSourceColor = blurredSourceColor - blurredSourceColor * " + (1.0 - motionBlurCoefficient) * fpsAttenuation+ ";" +
                    "color = step(1.0, color) * color + step(color, 1.0) * blurredSourceColor;"
                : "") +


                "gl_FragColor.a = floor(color) / 256.0;" +
            "}"

        onStatusChanged: if (log) console.log(log) //Print warning messages
    }
    ///////////////////////////////////////////////////////////////////////////
    //  EFFECTS  //////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////

    //  BLOOM  ////////////////////////////////////////////////////////////////

    Loader{
        id: bloomEffectLoader
        active: mBloom != 0
        anchors.fill: parent
        sourceComponent: FastBlur{
            radius: 48
            source: kterminal
            transparentBorder: true
            smooth: false
        }
    }
    Loader{
        id: bloomSourceLoader
        active: mBloom != 0
        sourceComponent: ShaderEffectSource{
            sourceItem: bloomEffectLoader.item
            hideSource: true
            sourceRect: frame.sourceRect
            smooth: false
        }
    }

    //  NOISE  ////////////////////////////////////////////////////////////////

    ShaderEffect {
        id: staticNoiseEffect
        anchors.fill: parent
        property size virtual_resolution: terminalContainer.virtual_resolution

        blending: false

        fragmentShader:
            "uniform lowp float qt_Opacity;
             varying highp vec2 qt_TexCoord0;
             uniform highp vec2 virtual_resolution;" +

            "highp float noise(vec2 co)
            {
                highp float a = 12.9898;
                highp float b = 78.233;
                highp float c = 43758.5453;
                highp float dt= dot(co.xy ,vec2(a,b));
                highp float sn= mod(dt,3.14);
                return fract(sin(sn) * c);
            }

            vec2 sw(vec2 p) {return vec2( floor(p.x) , floor(p.y) );}
            vec2 se(vec2 p) {return vec2( ceil(p.x)  , floor(p.y) );}
            vec2 nw(vec2 p) {return vec2( floor(p.x) , ceil(p.y)  );}
            vec2 ne(vec2 p) {return vec2( ceil(p.x)  , ceil(p.y)  );}

            float smoothNoise(vec2 p) {
                vec2 inter = smoothstep(0., 1., fract(p));
                float s = mix(noise(sw(p)), noise(se(p)), inter.x);
                float n = mix(noise(nw(p)), noise(ne(p)), inter.x);
                return mix(s, n, inter.y);
            }" +

        "void main() {" +
            "gl_FragColor.a = smoothNoise(qt_TexCoord0 * virtual_resolution);" +
        "}"

        onStatusChanged: if (log) console.log(log) //Print warning messages
    }
    ShaderEffectSource{
        id: staticNoiseSource
        sourceItem: staticNoiseEffect
        textureSize: Qt.size(parent.width, parent.height)
        wrapMode: ShaderEffectSource.Repeat
        smooth: true
        hideSource: true
        //format: ShaderEffectSource.Alpha
    }

    // RASTERIZATION //////////////////////////////////////////////////////////

    ShaderEffect{
        id: rasterizationContainer
        width: frame.sourceRect.width
        height: frame.sourceRect.height
        property size offset: Qt.size(width - rasterizationEffect.width, height - rasterizationEffect.height)
        property size txtRes: Qt.size(width, height)

        blending: false

        fragmentShader:
            "uniform lowp float qt_Opacity;
             uniform highp vec2 offset;
             uniform highp vec2 txtRes;" +

            "varying highp vec2 qt_TexCoord0;" +

            "void main() {" +
                "float color = 1.0;
                 color *= smoothstep(0.0, offset.x / txtRes.x, qt_TexCoord0.x);
                 color *= smoothstep(0.0, offset.y / txtRes.y, qt_TexCoord0.y);
                 color *= smoothstep(0.0, offset.x / txtRes.x, 1.0 - qt_TexCoord0.x);
                 color *= smoothstep(0.0, offset.y / txtRes.y, 1.0 - qt_TexCoord0.y);" +

                "float distance = length(vec2(0.5) - qt_TexCoord0);" +
                "color = mix(color, 0.0, 1.2 * distance * distance);" +

                "gl_FragColor.a = color;" +
            "}"

        ShaderEffect {
            id: rasterizationEffect
            width: terminalContainer.width
            height: terminalContainer.height
            anchors.centerIn: parent
            property size virtual_resolution: terminalContainer.virtual_resolution

            blending: false

            fragmentShader:
                "uniform lowp float qt_Opacity;" +

                "varying highp vec2 qt_TexCoord0;
                     uniform highp vec2 virtual_resolution;

                     float getScanlineIntensity(vec2 coords) {
                        float result = 1.0;" +
                        (mScanlines != shadersettings.no_rasterization ?
                            "result *= abs(sin(coords.y * virtual_resolution.y * "+Math.PI+"));" : "") +
                        (mScanlines == shadersettings.pixel_rasterization ?
                            "result *= abs(sin(coords.x * virtual_resolution.x * "+Math.PI+"));" : "") + "
                        return result;
                     }" +

            "void main() {" +
                "float color = getScanlineIntensity(qt_TexCoord0);" +

                "float distance = length(vec2(0.5) - qt_TexCoord0);" +
                "color = mix(color, 0.0, 1.2 * distance * distance);" +

                "gl_FragColor.a = color;" +
            "}"

            onStatusChanged: if (log) console.log(log) //Print warning messages
        }
        onStatusChanged: if (log) console.log(log) //Print warning messages
    }
    ShaderEffectSource{
        id: rasterizationEffectSource
        sourceItem: rasterizationContainer
        hideSource: true
        smooth: true
        //format: ShaderEffectSource.Alpha
    }
}