#import "PGGrainTool.h"

@interface PGGrainTool ()
{
    PGPhotoProcessPassParameter *_parameter;
}
@end

@implementation PGGrainTool

- (instancetype)init
{
    self = [super init];
    if (self != nil)
    {
        _identifier = @"grain";
        _type = PGPhotoToolTypeShader;
        _order = 12;
        
        _minimumValue = 0;
        _maximumValue = 100;
        _defaultValue = 0;
        
        self.value = @(_defaultValue);
    }
    return self;
}

- (NSString *)title
{
    return TGLocalized(@"PhotoEditor.GrainTool");
}

- (UIImage *)image
{
    return [UIImage imageNamed:@"PhotoEditorGrainTool"];
}

- (bool)shouldBeSkipped
{
    return (ABS(((NSNumber *)self.displayValue).floatValue - (float)self.defaultValue) < FLT_EPSILON);
}

- (NSArray *)parameters
{
    if (!_parameters)
    {
        _parameter = [PGPhotoProcessPassParameter parameterWithName:@"grain" type:@"lowp float"];
        _parameters = @[ _parameter,
                         [PGPhotoProcessPassParameter constWithName:@"permTexUnit" type:@"lowp float" value:@"1.0 / 256.0"],
                         [PGPhotoProcessPassParameter constWithName:@"permTexUnitHalf" type:@"lowp float" value:@"0.5 / 256.0"],
                         [PGPhotoProcessPassParameter constWithName:@"grainsize" type:@"lowp float" value:@"2.3"] ];
    }
    
    return _parameters;
}

- (void)updateParameters
{
    NSNumber *value = (NSNumber *)self.displayValue;
    
    CGFloat parameterValue = value.floatValue / 100.0f * 0.04f;
    [_parameter setFloatValue:parameterValue];
}

- (NSString *)ancillaryShaderString
{
    return PGShaderString
    (
        highp vec4 rnm(in highp vec2 tc) {
          highp float noise = sin(dot(tc,vec2(12.9898,78.233))) * 43758.5453;
          
          highp float noiseR = fract(noise)*2.0-1.0;
          highp float noiseG = fract(noise*1.2154)*2.0-1.0;
          highp float noiseB = fract(noise*1.3453)*2.0-1.0;
          highp float noiseA = fract(noise*1.3647)*2.0-1.0;
          
          return vec4(noiseR,noiseG,noiseB,noiseA);
        }

        highp float fade(in highp float t) {
          return t*t*t*(t*(t*6.0-15.0)+10.0);
        }

        highp float pnoise3D(in highp vec3 p)
        {
          highp vec3 pi = permTexUnit*floor(p)+permTexUnitHalf;
          highp vec3 pf = fract(p);
          
          // Noise contributions from (x=0, y=0), z=0 and z=1
          highp float perm00 = rnm(pi.xy).a ;
          highp vec3  grad000 = rnm(vec2(perm00, pi.z)).rgb * 4.0 - 1.0;
          highp float n000 = dot(grad000, pf);
          highp vec3  grad001 = rnm(vec2(perm00, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
          highp float n001 = dot(grad001, pf - vec3(0.0, 0.0, 1.0));
          
          // Noise contributions from (x=0, y=1), z=0 and z=1
          highp float perm01 = rnm(pi.xy + vec2(0.0, permTexUnit)).a ;
          highp vec3  grad010 = rnm(vec2(perm01, pi.z)).rgb * 4.0 - 1.0;
          highp float n010 = dot(grad010, pf - vec3(0.0, 1.0, 0.0));
          highp vec3  grad011 = rnm(vec2(perm01, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
          highp float n011 = dot(grad011, pf - vec3(0.0, 1.0, 1.0));
          
          // Noise contributions from (x=1, y=0), z=0 and z=1
          highp float perm10 = rnm(pi.xy + vec2(permTexUnit, 0.0)).a ;
          highp vec3  grad100 = rnm(vec2(perm10, pi.z)).rgb * 4.0 - 1.0;
          highp float n100 = dot(grad100, pf - vec3(1.0, 0.0, 0.0));
          highp vec3  grad101 = rnm(vec2(perm10, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
          highp float n101 = dot(grad101, pf - vec3(1.0, 0.0, 1.0));
          
          // Noise contributions from (x=1, y=1), z=0 and z=1
          highp float perm11 = rnm(pi.xy + vec2(permTexUnit, permTexUnit)).a ;
          highp vec3  grad110 = rnm(vec2(perm11, pi.z)).rgb * 4.0 - 1.0;
          highp float n110 = dot(grad110, pf - vec3(1.0, 1.0, 0.0));
          highp vec3  grad111 = rnm(vec2(perm11, pi.z + permTexUnit)).rgb * 4.0 - 1.0;
          highp float n111 = dot(grad111, pf - vec3(1.0, 1.0, 1.0));
          
          // Blend contributions along x
          highp vec4 n_x = mix(vec4(n000, n001, n010, n011), vec4(n100, n101, n110, n111), fade(pf.x));
          
          // Blend contributions along y
          highp vec2 n_xy = mix(n_x.xy, n_x.zw, fade(pf.y));
          
          // Blend contributions along z
          highp float n_xyz = mix(n_xy.x, n_xy.y, fade(pf.z));
          
          return n_xyz;
        }

        lowp vec2 coordRot(in lowp vec2 tc, in lowp float angle)
        {
          lowp float rotX = ((tc.x * 2.0 - 1.0) * cos(angle)) - ((tc.y * 2.0 - 1.0) * sin(angle));
          lowp float rotY = ((tc.y * 2.0 - 1.0) * cos(angle)) + ((tc.x * 2.0 - 1.0) * sin(angle));
          rotX = rotX * 0.5 + 0.5;
          rotY = rotY * 0.5 + 0.5;
          return vec2(rotX,rotY);
        }
     );
}

- (NSString *)shaderString
{
    return PGShaderString
    (
     if (abs(grain) > toolEpsilon) {
         highp vec3 rotOffset = vec3(1.425, 3.892, 5.835);
         highp vec2 rotCoordsR = coordRot(texCoord, rotOffset.x);
         highp vec3 noise = vec3(pnoise3D(vec3(rotCoordsR * vec2(width / grainsize, height / grainsize),0.0)));
         
         lowp vec3 lumcoeff = vec3(0.299,0.587,0.114);
         lowp float luminance = dot(result.rgb, lumcoeff);
         lowp float lum = smoothstep(0.2, 0.0, luminance);
         lum += luminance;
         
         noise = mix(noise,vec3(0.0),pow(lum,4.0));
         result.rgb = result.rgb + noise * grain;
     }
    );
}

@end
