/*******************************************************************************
* Copyright (c) 2013-2021 "Filippo Scognamiglio"
* https://github.com/Swordfish90/cool-retro-term
*
* This file is part of cool-retro-term.
*
* cool-retro-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 "utils.js" as Utils

Item {
    property ShaderEffectSource source
    property BurnInEffect burnInEffect
    property ShaderEffectSource bloomSource

    property color fontColor: appSettings.fontColor
    property color backgroundColor: appSettings.backgroundColor

    property real screenCurvature: appSettings.screenCurvature * appSettings.screenCurvatureSize

    property real chromaColor: appSettings.chromaColor

    property real ambientLight: appSettings.ambientLight * 0.2

    property real frameGloss: appSettings.frameGloss

    property size virtualResolution
    property size screenResolution

    property real _screenDensity: Math.min(
        screenResolution.width / virtualResolution.width,
        screenResolution.height / virtualResolution.height
    )

     ShaderEffect {
         id: dynamicShader

         property ShaderLibrary shaderLibrary: ShaderLibrary { }

         property ShaderEffectSource screenBuffer: frameBuffer
         property ShaderEffectSource burnInSource: burnInEffect.source
         property ShaderEffectSource frameSource: terminalFrameLoader.item

         property color fontColor: parent.fontColor
         property color backgroundColor: parent.backgroundColor
         property real screenCurvature: parent.screenCurvature
         property real chromaColor: parent.chromaColor
         property real ambientLight: parent.ambientLight
         property real frameGloss: parent.frameGloss

         property real flickering: appSettings.flickering
         property real horizontalSync: appSettings.horizontalSync
         property real horizontalSyncStrength: Utils.lint(0.05, 0.35, horizontalSync)
         property real glowingLine: appSettings.glowingLine * 0.2

         // Fast burnin properties
         property real burnIn: appSettings.burnIn
         property real burnInLastUpdate: burnInEffect.lastUpdate
         property real burnInTime: burnInEffect.burnInFadeTime

         property real jitter: appSettings.jitter
         property size jitterDisplacement: Qt.size(0.007 * jitter, 0.002 * jitter)
         property real shadowLength: 0.25 * screenCurvature * Utils.lint(0.50, 1.5, ambientLight)
         property real staticNoise: appSettings.staticNoise
         property size scaleNoiseSize: Qt.size((width * 0.75) / (noiseTexture.width * appSettings.windowScaling * appSettings.totalFontScaling),
                                               (height * 0.75) / (noiseTexture.height * appSettings.windowScaling * appSettings.totalFontScaling))

         property size virtualResolution: parent.virtualResolution

         // Rasterization might display oversamping issues if virtual resolution is close to physical display resolution.
         // We progressively disable rasterization from 4x up to 2x resolution.
         property real rasterizationIntensity: Utils.smoothstep(2.0, 4.0, _screenDensity)

         property real displayTerminalFrame: appSettings._frameMargin > 0 || appSettings.screenCurvature > 0

         property real time: timeManager.time
         property ShaderEffectSource noiseSource: noiseShaderSource

         // If something goes wrong activate the fallback version of the shader.
         property bool fallBack: false

         anchors.fill: parent
         blending: false

         //Smooth random texture used for flickering effect.
         Image {
             id: noiseTexture
             source: "images/allNoise512.png"
             width: 512
             height: 512
             fillMode: Image.Tile
             visible: false
         }
         ShaderEffectSource {
             id: noiseShaderSource
             sourceItem: noiseTexture
             wrapMode: ShaderEffectSource.Repeat
             visible: false
             smooth: true
         }

         //Print the number with a reasonable precision for the shader.
         function str(num){
             return num.toFixed(8);
         }

         vertexShader: "
             uniform highp mat4 qt_Matrix;
             uniform highp float time;

             attribute highp vec4 qt_Vertex;
             attribute highp vec2 qt_MultiTexCoord0;

             varying highp vec2 qt_TexCoord0;" +

             (!fallBack ? "
                 uniform sampler2D noiseSource;" : "") +

             (!fallBack && flickering !== 0.0 ?"
                 varying lowp float brightness;
                 uniform lowp float flickering;" : "") +

             (!fallBack && horizontalSync !== 0.0 ?"
                 uniform lowp float horizontalSyncStrength;
                 varying lowp float distortionScale;
                 varying lowp float distortionFreq;" : "") +

             "
             void main() {
                 qt_TexCoord0 = qt_MultiTexCoord0;
                 vec2 coords = vec2(fract(time/(1024.0*2.0)), fract(time/(1024.0*1024.0)));" +

                 (!fallBack && (flickering !== 0.0 || horizontalSync !== 0.0) ?
                     "vec4 initialNoiseTexel = texture2D(noiseSource, coords);"
                 : "") +

                 (!fallBack && flickering !== 0.0 ? "
                     brightness = 1.0 + (initialNoiseTexel.g - 0.5) * flickering;"
                 : "") +

                 (!fallBack && horizontalSync !== 0.0 ? "
                     float randval = horizontalSyncStrength - initialNoiseTexel.r;
                     distortionScale = step(0.0, randval) * randval * horizontalSyncStrength;
                     distortionFreq = mix(4.0, 40.0, initialNoiseTexel.g);"
                 : "") +

                 "gl_Position = qt_Matrix * qt_Vertex;
             }"

         fragmentShader: "
             #ifdef GL_ES
                 precision mediump float;
             #endif

             uniform sampler2D screenBuffer;
             uniform highp float qt_Opacity;
             uniform highp float time;
             varying highp vec2 qt_TexCoord0;

             uniform highp vec4 fontColor;
             uniform highp vec4 backgroundColor;
             uniform lowp float shadowLength;
             uniform lowp float frameGloss;

             uniform highp vec2 virtualResolution;
             uniform lowp float rasterizationIntensity;\n" +

             (burnIn !== 0 ? "
                 uniform sampler2D burnInSource;
                 uniform highp float burnInLastUpdate;
                 uniform highp float burnInTime;" : "") +
             (staticNoise !== 0 ? "
                 uniform highp float staticNoise;" : "") +
             (((staticNoise !== 0 || jitter !== 0) ||(fallBack && (flickering || horizontalSync))) ? "
                 uniform lowp sampler2D noiseSource;
                 uniform highp vec2 scaleNoiseSize;" : "") +
             (displayTerminalFrame ? "
                 uniform lowp sampler2D frameSource;" : "") +
             (screenCurvature !== 0 ? "
                 uniform highp float screenCurvature;" : "") +
             (glowingLine !== 0 ? "
                 uniform highp float glowingLine;" : "") +
             (chromaColor !== 0 ? "
                 uniform lowp float chromaColor;" : "") +
             (jitter !== 0 ? "
                 uniform lowp vec2 jitterDisplacement;" : "") +
             (ambientLight !== 0 ? "
                 uniform lowp float ambientLight;" : "") +

             (fallBack && horizontalSync !== 0 ? "
                 uniform lowp float horizontalSyncStrength;" : "") +
             (fallBack && flickering !== 0.0 ?"
                 uniform lowp float flickering;" : "") +
             (!fallBack && flickering !== 0 ? "
                 varying lowp float brightness;"
             : "") +
             (!fallBack && horizontalSync !== 0 ? "
                 varying lowp float distortionScale;
                 varying lowp float distortionFreq;" : "") +

             (glowingLine !== 0 ? "
                 float randomPass(vec2 coords){
                     return fract(smoothstep(-120.0, 0.0, coords.y - (virtualResolution.y + 120.0) * fract(time * 0.00015)));
                 }" : "") +

             shaderLibrary.min2 +
             shaderLibrary.rgb2grey +
             shaderLibrary.rasterizationShader +

             "
             float isInScreen(vec2 v) {
                 return min2(step(0.0, v) - step(1.0, v));
             }

             vec2 barrel(vec2 v, vec2 cc) {" +

                 (screenCurvature !== 0 ? "
                     float distortion = dot(cc, cc) * screenCurvature;
                     return (v - cc * (1.0 + distortion) * distortion);"
                 :
                     "return v;") +
             "}" +

             "vec3 convertWithChroma(vec3 inColor) {
                vec3 outColor = inColor;" +

                 (chromaColor !== 0 ?
                     "outColor = fontColor.rgb * mix(vec3(rgb2grey(inColor)), inColor, chromaColor);"
                 :
                     "outColor = fontColor.rgb * rgb2grey(inColor);") +

             "  return outColor;
             }" +

             //pseudo-random vector
             //https://stackoverflow.com/a/10625698
             "float random( vec2 p )
             {
                 vec2 K1 = vec2(
                     23.14069263277926, // e^pi (Gelfond's constant)
                     2.665144142690225 // 2^sqrt(2) (Gelfond-Schneider constant)
                 );
                 return fract( cos( dot(p,K1) ) * 12345.6789 );
             }" +

             "void main() {" +
                 "vec2 cc = vec2(0.5) - qt_TexCoord0;" +
                 "float distance = length(cc);" +

                 //FallBack if there are problems
                 (fallBack && (flickering !== 0.0 || horizontalSync !== 0.0) ?
                     "vec2 initialCoords = vec2(fract(time/(1024.0*2.0)), fract(time/(1024.0*1024.0)));
                      vec4 initialNoiseTexel = texture2D(noiseSource, initialCoords);"
                 : "") +
                 (fallBack && flickering !== 0.0 ? "
                     float brightness = 1.0 + (initialNoiseTexel.g - 0.5) * flickering;"
                 : "") +
                 (fallBack && horizontalSync !== 0.0 ? "
                     float randval = horizontalSyncStrength - initialNoiseTexel.r;
                     float distortionScale = step(0.0, randval) * randval * horizontalSyncStrength;
                     float distortionFreq = mix(4.0, 40.0, initialNoiseTexel.g);"
                 : "") +

                 (staticNoise ? "
                     float noise = staticNoise;" : "") +

                 (screenCurvature !== 0 ? "
                     vec2 staticCoords = barrel(qt_TexCoord0, cc);"
                 :"
                     vec2 staticCoords = qt_TexCoord0;") +

                 "vec2 coords = qt_TexCoord0;" +

                 (horizontalSync !== 0 ? "
                     float dst = sin((coords.y + time * 0.001) * distortionFreq);
                     coords.x += dst * distortionScale;" +

                     (staticNoise ? "
                         noise += distortionScale * 7.0;" : "")

                 : "") +

                 (jitter !== 0 || staticNoise !== 0 ?
                 "vec4 noiseTexel = texture2D(
                     noiseSource, scaleNoiseSize * coords
                     + vec2(0.0, random(vec2(fract(time / 237.0), 822.9582)))
                     + vec2(fract(time / 31.0), fract(time / 177.0))
                 );"
                 : "") +

                 (jitter !== 0 ? "
                     vec2 offset = vec2(noiseTexel.b, noiseTexel.a) - vec2(0.5);
                     vec2 txt_coords = coords + offset * jitterDisplacement;"
                 :  "vec2 txt_coords = coords;") +

                 "float color = 0.0001;" +

                 (staticNoise !== 0 ? "
                     float noiseVal = noiseTexel.a;
                     color += noiseVal * noise * (1.0 - distance * 1.3);" : "") +

                 (glowingLine !== 0 ? "
                     color += randomPass(coords * virtualResolution) * glowingLine;" : "") +

                 "vec3 txt_color = texture2D(screenBuffer, txt_coords).rgb;" +

                 (burnIn !== 0 ? "
                     vec4 txt_blur = texture2D(burnInSource, staticCoords);
                     float blurDecay = clamp(pow(0.5, burnInTime * (time - burnInLastUpdate)), 0.0, 1.0);
                     vec3 burnInColor = txt_blur.rgb * vec3(blurDecay);
                     txt_color = max(txt_color, convertWithChroma(burnInColor));"
                 : "") +

                  "txt_color += fontColor.rgb * vec3(color);" +

                  "txt_color = applyRasterization(staticCoords, txt_color, virtualResolution, rasterizationIntensity);\n" +

                 "vec3 finalColor = txt_color;" +

                 (flickering !== 0 ? "
                     finalColor *= brightness;" : "") +

                 (ambientLight !== 0 ? "
                     finalColor += vec3(ambientLight) * (1.0 - distance) * (1.0 - distance);" : "") +

                 (displayTerminalFrame ?
                    "vec4 frameColor = texture2D(frameSource, qt_TexCoord0);
                     finalColor = mix(finalColor, frameColor.rgb + (finalColor*frameGloss), frameColor.a);"
                 : "") +

                 "gl_FragColor = vec4(finalColor, qt_Opacity);" +
             "}"

          onStatusChanged: {
              // Print warning messages
              if (log)
                  console.log(log);

              // Activate fallback mode
              if (status == ShaderEffect.Error) {
                 fallBack = true;
              }
          }
     }

     Loader {
         id: terminalFrameLoader

         active: dynamicShader.displayTerminalFrame

         width: staticShader.width
         height: staticShader.height

         sourceComponent: ShaderEffectSource {

             sourceItem: terminalFrame
             hideSource: true
             visible: false
             format: ShaderEffectSource.RGBA

             TerminalFrame {
                 id: terminalFrame
                 blending: false
                 anchors.fill: parent
             }
         }
     }

     ShaderLibrary {
         id: shaderLibrary
     }

     ShaderEffect {
         id: staticShader

         width: parent.width * appSettings.windowScaling
         height: parent.height * appSettings.windowScaling

         property ShaderEffectSource source: parent.source
         property ShaderEffectSource bloomSource: parent.bloomSource

         property color fontColor: parent.fontColor
         property color backgroundColor: parent.backgroundColor
         property real bloom: appSettings.bloom * 2.5

         property real screenCurvature: parent.screenCurvature

         property real chromaColor: appSettings.chromaColor;

         property real rbgShift: (appSettings.rbgShift / width) * appSettings.totalFontScaling // TODO FILIPPO width here is wrong.

         property int rasterization: appSettings.rasterization

         property real screen_brightness: Utils.lint(0.5, 1.5, appSettings.brightness)

         property real ambientLight: parent.ambientLight

         property size virtualResolution: parent.virtualResolution

         blending: false
         visible: false

         //Print the number with a reasonable precision for the shader.
         function str(num){
             return num.toFixed(8);
         }

         fragmentShader: "
             #ifdef GL_ES
                 precision mediump float;
             #endif

             uniform sampler2D source;
             uniform highp float qt_Opacity;
             varying highp vec2 qt_TexCoord0;

             uniform highp vec4 fontColor;
             uniform highp vec4 backgroundColor;
             uniform lowp float screen_brightness;

             uniform highp vec2 virtualResolution;" +

             (bloom !== 0 ? "
                 uniform highp sampler2D bloomSource;
                 uniform lowp float bloom;" : "") +

             (screenCurvature !== 0 ? "
                 uniform highp float screenCurvature;" : "") +

             (chromaColor !== 0 ? "
                 uniform lowp float chromaColor;" : "") +

             (rbgShift !== 0 ? "
                 uniform lowp float rbgShift;" : "") +

             (ambientLight !== 0 ? "
                 uniform lowp float ambientLight;" : "") +

             shaderLibrary.min2 +
             shaderLibrary.sum2 +
             shaderLibrary.rgb2grey +

             "vec3 convertWithChroma(vec3 inColor) {
                vec3 outColor = inColor;" +

                 (chromaColor !== 0 ?
                     "outColor = fontColor.rgb * mix(vec3(rgb2grey(inColor)), inColor, chromaColor);"
                 :
                     "outColor = fontColor.rgb * rgb2grey(inColor);") +

             "  return outColor;
             }" +

             shaderLibrary.rasterizationShader +

             "void main() {" +
                 "vec2 cc = vec2(0.5) - qt_TexCoord0;" +

                 (screenCurvature !== 0 ? "
                     float distortion = dot(cc, cc) * screenCurvature;
                     vec2 curvatureCoords = (qt_TexCoord0 - cc * (1.0 + distortion) * distortion);
                     vec2 txt_coords = - 2.0 * curvatureCoords + 3.0 * step(vec2(0.0), curvatureCoords) * curvatureCoords - 3.0 * step(vec2(1.0), curvatureCoords) * curvatureCoords;"
                 :"
                     vec2 txt_coords = qt_TexCoord0;") +

                 "vec3 txt_color = texture2D(source, txt_coords).rgb;" +

                 (rbgShift !== 0 ? "
                     vec2 displacement = vec2(12.0, 0.0) * rbgShift;
                     vec3 rightColor = texture2D(source, txt_coords + displacement).rgb;
                     vec3 leftColor = texture2D(source, txt_coords - displacement).rgb;
                     txt_color.r = leftColor.r * 0.10 + rightColor.r * 0.30 + txt_color.r * 0.60;
                     txt_color.g = leftColor.g * 0.20 + rightColor.g * 0.20 + txt_color.g * 0.60;
                     txt_color.b = leftColor.b * 0.30 + rightColor.b * 0.10 + txt_color.b * 0.60;
                 " : "") +

                  "txt_color += vec3(0.0001);" +
                  "float greyscale_color = rgb2grey(txt_color);" +

                 (screenCurvature !== 0 ? "
                     float reflectionMask = sum2(step(vec2(0.0), curvatureCoords) - step(vec2(1.0), curvatureCoords));
                     reflectionMask = clamp(reflectionMask, 0.0, 1.0);"
                 :
                     "float reflectionMask = 1.0;") +

                 (chromaColor !== 0 ?
                     "vec3 foregroundColor = mix(fontColor.rgb, txt_color * fontColor.rgb / greyscale_color, chromaColor);
                      vec3 finalColor = mix(backgroundColor.rgb, foregroundColor, greyscale_color * reflectionMask);"
                 :
                     "vec3 finalColor = mix(backgroundColor.rgb, fontColor.rgb, greyscale_color * reflectionMask);") +

                     (bloom !== 0 ?
                         "vec4 bloomFullColor = texture2D(bloomSource, txt_coords);
                          vec3 bloomColor = bloomFullColor.rgb;
                          float bloomAlpha = bloomFullColor.a;
                          bloomColor = convertWithChroma(bloomColor);
                          finalColor += clamp(bloomColor * bloom * bloomAlpha, 0.0, 0.5);"
                     : "") +

                 "finalColor *= screen_brightness;" +

                 "gl_FragColor = vec4(finalColor, qt_Opacity);" +
             "}"

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

     ShaderEffectSource {
         id: frameBuffer
         visible: false
         sourceItem: staticShader
         hideSource: true
     }
}
