
#import "RNGLContext.h"
#import "RCTConvert.h"
#import "RCTLog.h"


@implementation RNGLContext
{
  NSMutableDictionary *_shaders;
  EAGLContext *_context;
  NSMutableDictionary *_fbos;
}

@synthesize bridge = _bridge;

RCT_EXPORT_MODULE()

- (void)setBridge:(RCTBridge *)bridge
{
  _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
  if (!_context) {
    RCTLogError(@"Failed to initialize OpenGLES 2.0 context");
  }
  _shaders = @{}.mutableCopy;
  _fbos = @{}.mutableCopy;
}

- (GLShader *) getShader: (NSNumber *)id
{
  return _shaders[id];
}

- (GLFBO *) getFBO: (NSNumber *)id
{
  GLFBO *fbo = _fbos[id];
  if (!fbo) {
    fbo = [[GLFBO alloc] init];
    _fbos[id] = fbo;
  }
  return fbo;
}

- (EAGLContext *) getContext
{
  return _context;
}

- (void)_addShader:(nonnull NSNumber *)id
withConfig:(NSDictionary *)config
withOnCompile:(RCTResponseSenderBlock)onCompile
{
  NSString *frag = [RCTConvert NSString:config[@"frag"]];
  NSString *name = [RCTConvert NSString:config[@"name"]];
  if (!frag) {
    RCTLogError(@"Shader '%@': missing frag field", name);
    return;
  }
  GLShader *shader = [[GLShader alloc] initWithContext:_context withName:name withVert:fullViewportVert withFrag:frag];
  NSError *error;
  bool success = [shader ensureCompiles:&error];
  if (onCompile) {
    if (!success) {
      onCompile(@[error.domain]);
    }
    else {
      onCompile(@[[NSNull null],
                  @{
                    @"uniforms": shader.uniformTypes
                    }]);
    }
  }
  else {
    if (!success) {
      RCTLogError(@"Shader '%@': %@", name, error.domain);
    }
  }
  _shaders[id] = shader;
}

static NSString* fullViewportVert = @"attribute vec2 position;varying vec2 uv;void main() {gl_Position = vec4(position,0.0,1.0);uv = vec2(0.5, 0.5) * (position+vec2(1.0, 1.0));}";

NSString* glTypeString (int type) {
  switch (type) {
    case GL_FLOAT: return @"float";
    case GL_FLOAT_VEC2: return @"vec2";
    case GL_FLOAT_VEC3: return @"vec3";
    case GL_FLOAT_VEC4: return @"vec4";
    case GL_INT: return @"int";
    case GL_INT_VEC2: return @"ivec2";
    case GL_INT_VEC3: return @"ivec3";
    case GL_INT_VEC4: return @"ivec4";
    case GL_BOOL: return @"bool";
    case GL_BOOL_VEC2: return @"bvec2";
    case GL_BOOL_VEC3: return @"bvec3";
    case GL_BOOL_VEC4: return @"bvec4";
    case GL_FLOAT_MAT2: return @"mat2";
    case GL_FLOAT_MAT3: return @"mat3";
    case GL_FLOAT_MAT4: return @"mat4";
    case GL_SAMPLER_2D: return @"sampler2D";
    case GL_SAMPLER_CUBE: return @"samplerCube";
  }
  return @"";
}
NSDictionary* glTypesString (NSDictionary *types) {
  NSMutableDictionary *dict = types.mutableCopy;
  for (NSString *key in [dict allKeys]) {
    dict[key] = glTypeString([dict[key] intValue]);
  }
  return dict;
}

RCT_EXPORT_METHOD(addShader:(nonnull NSNumber *)id
                  withConfig:(NSDictionary *)config
                  withOnCompile:(RCTResponseSenderBlock)onCompile) {
  NSString *frag = [RCTConvert NSString:config[@"frag"]];
  NSString *name = [RCTConvert NSString:config[@"name"]];
  if (!frag) {
    RCTLogError(@"Shader '%@': missing frag field", name);
    return;
  }
  GLShader *shader = [[GLShader alloc] initWithContext:_context withName:name withVert:fullViewportVert withFrag:frag];
  NSError *error;
  bool success = [shader ensureCompiles:&error];
  if (onCompile) {
    if (!success) {
      onCompile(@[error.domain]);
    }
    else {
      onCompile(@[[NSNull null],
                  @{
                    @"uniforms": glTypesString(shader.uniformTypes)
                    }]);
    }
  }
  else {
    if (!success) {
      RCTLogError(@"Shader '%@': %@", name, error.domain);
    }
  }
  _shaders[id] = shader;
}

RCT_EXPORT_METHOD(removeShader:(nonnull NSNumber *)id) {
  GLShader *shader = [_shaders objectForKey:id];
  if (!shader) {
    RCTLogError(@"removeShader(%@): shader does not exist", id);
    return;
  }
  [_shaders removeObjectForKey:id];
}

@end

@implementation RCTBridge (RNGLContext)

- (RNGLContext *)rnglContext
{
  return [self moduleForClass:[RNGLContext class]];
}

@end