//
//  GameViewController.m
//  PanoPainter
//
//  Created by Omar Mohamed Ali Mudhir on 07/05/17.
//  Copyright © 2017 Omar Mohamed Ali Mudhir. All rights reserved.
//

#include "pch.h"
#import "GameViewController.h"
#import <OpenGLES/ES3/glext.h>
#include "app.h"
#import "objc_utils.h"

std::deque<std::packaged_task<void()>> tasklist;
std::mutex task_mutex;

@interface GameImagePicker : UIImagePickerController
{
    @public std::promise<std::string> promise;
    @public std::function<void(std::string)> callback;
}
@end
@implementation GameImagePicker
@end

@interface GameViewController () {
    NSLock* gl_lock;
}
@property (strong, nonatomic) EAGLContext *context;

- (void)setupGL;
- (void)tearDownGL;
@end

//std::map<
bool pen_down = false;
int t_count = 0;
glm::vec2 t_pos;
int lock_count = 0;
NSThread* lock_thread;
std::recursive_mutex lock_mutex;

@implementation GameViewController
- (void)crash
{
    AppDelegate* app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [app hockeyapp_crash];
}
- (void)async_lock
{
    lock_mutex.lock();
//    if (lock_thread != [NSThread currentThread] || lock_count == 0)
//        [gl_lock lock];
//    lock_thread = [NSThread currentThread];
//    lock_count++;
    [EAGLContext setCurrentContext:self.context];
    GameView* view = (GameView*)self.view;
    [view bindDrawable];
}
- (void)async_unlock
{
//    lock_count--;
//    if (lock_count == 0)
//        [gl_lock unlock];
    lock_mutex.unlock();
}
- (void)async_swap
{
    [self.context presentRenderbuffer:GL_RENDERBUFFER];
}

- (void)share_file:(NSString *)file_path
{
    NSURL *url = [NSURL fileURLWithPath:file_path];
    NSArray *objectsToShare = @[url];
 
    UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare applicationActivities:nil];
    
    controller.excludedActivityTypes = @[];
    controller.popoverPresentationController.sourceView = self.view;
    controller.popoverPresentationController.sourceRect = CGRectMake(self.view.bounds.size.width/2, self.view.bounds.size.height/4, 0, 0);

    // Present the controller
    [self presentViewController:controller animated:YES completion:nil];
}

-(void)display_file:(std::string)filename
{
    NSString* filePath = [NSString stringWithUTF8String:filename.c_str()];
    NSURL *url = [NSURL fileURLWithPath:filePath];
    UIDocumentInteractionController *popup = [UIDocumentInteractionController interactionControllerWithURL:url];
    [popup setDelegate:self];
    [popup presentPreviewAnimated:YES];
}

-(UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller
{
    return self;
}


- (void)pick_photo:(std::function<void(std::string)>) callback
{
    GameImagePicker *picker = [[GameImagePicker alloc] init];
    picker.delegate = self;
    picker.allowsEditing = NO;
    picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    picker->callback = callback;
    [self presentViewController:picker animated:YES completion:NULL];
}

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    NSString *chosenImage = [info[UIImagePickerControllerImageURL] path];
    GameImagePicker* p = static_cast<GameImagePicker*>(picker);
    std::string path = [chosenImage cStringUsingEncoding:NSUTF8StringEncoding];
    p->callback(path);
    [picker dismissViewControllerAnimated:YES completion:^{
                    [self keyboardWasHidden:nil];
                }];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [picker dismissViewControllerAnimated:YES completion:^{
                    [self keyboardWasHidden:nil];
                }];
}

- (void)insertText:(NSString *)text
{
    if (const char* cstr = [text cStringUsingEncoding:NSASCIIStringEncoding])
        App::I.key_char(cstr[0]);
    NSLog(@"%@", text);
    // Do something with the typed character
}
- (void)deleteBackward {
    App::I.key_char('\b');
    // Handle the delete key
}
- (BOOL)hasText {
    // Return whether there's any text present
    return YES;
}
- (BOOL)canBecomeFirstResponder {
    return YES;
}

// Call this method somewhere in your view controller setup code.
- (void)registerForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillBeShown:)
                                                 name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillBeHidden:)
                                                 name:UIKeyboardWillHideNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWasShown:)
                                                 name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWasHidden:)
                                                 name:UIKeyboardDidHideNotification object:nil];
}
- (void)unregisterForKeyboardNotifications
{
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                 name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                 name:UIKeyboardWillHideNotification object:nil];

    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                 name:UIKeyboardDidShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                 name:UIKeyboardDidHideNotification object:nil];
}

- (void)keyboardWillBeShown:(NSNotification*)aNotification
{
    App::I.redraw = true;
    App::I.animate = true;
}
- (void)keyboardWasHidden:(NSNotification*)aNotification
{
    App::I.redraw = true;
    App::I.animate = false;
    [self unregisterForKeyboardNotifications];
}

// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
    
    GameView *view = (GameView *)self.view;
    CGRect frame = view.frame;
    frame.size.height -= kbSize.height;
    view.frame = frame;

    [self async_lock];
    App::I.resize(frame.size.width * view.contentScaleFactor,
                  frame.size.height * view.contentScaleFactor);
    App::I.animate = false;
    [self async_unlock];
}

// Called when the UIKeyboardWillHideNotification is sent
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
    CGRect frame = [[UIScreen mainScreen] bounds];
    self.view.frame = frame;
    [self async_lock];
    App::I.resize(frame.size.width * self.view.contentScaleFactor,
                  frame.size.height * self.view.contentScaleFactor);
    App::I.animate = true;
    [self async_unlock];
}

- (void)reset_touch
{
    [self async_lock];
    if (t_count == 2)
        App::I.gesture_end();
    else
        App::I.mouse_cancel(0);
    [self async_unlock];
    t_count = 0;
}

std::set<UITouch*> ignored_touch;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];

    CGPoint touchLocation = [touch locationInView:self.view];
    float scale = self.view.contentScaleFactor;
    
    kEventSource source = touch.type == UITouchType::UITouchTypeStylus ? kEventSource::Stylus : kEventSource::Touch;
    if (source == kEventSource::Stylus)
        pen_down = true;
    
    std::lock_guard<std::mutex> lock(task_mutex);
    tasklist.emplace_back([touchLocation, scale, f=touch.force, source] {
        App::I.mouse_down(0, touchLocation.x * scale, touchLocation.y * scale, f, source, 0);
    });
    t_count = 1;
    t_pos = {touchLocation.x * scale, touchLocation.y * scale};
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSUInteger n = [[event allTouches] count];
    float scale = self.view.contentScaleFactor;
    
    std::vector<UITouch*> valid_touches;
    for (int i = 0; i < n; i++)
    {
        auto t = [[[event allTouches] allObjects] objectAtIndex:i];
        auto it = ignored_touch.find(t);
        if (it == ignored_touch.end())
        {
            valid_touches.push_back(t);
        }
        else
        {
            NSLog(@"ignored moved");
        }
    }
    n = valid_touches.size();
    if (n == 0)
        return;
    
    UITouch* t0 = nullptr;
    UITouch* t1 = nullptr;
    
    t0 = valid_touches[0];
    CGPoint tl0 = [t0 locationInView:self.view];
    glm::vec2 p0 = glm::vec2(tl0.x * scale, tl0.y * scale);
    glm::vec2 p1;
    if  (n > 1)
    {
        t1 = valid_touches[1];
        CGPoint tl1 = [t1 locationInView:self.view];
        p1 = glm::vec2(tl1.x * scale, tl1.y * scale);
    }
    
    if (pen_down)
    {
        std::lock_guard<std::mutex> lock(task_mutex);
        tasklist.emplace_back([tt0=t0.type,tt1=t1.type,p0,p1,f0=t0.force,f1=t1.force] {
            if (tt0 == UITouchType::UITouchTypeStylus)
                App::I.mouse_move(p0.x, p0.y, f0, kEventSource::Stylus, 0);
            if (tt1 == UITouchType::UITouchTypeStylus)
                App::I.mouse_move(p1.x, p1.y, f1, kEventSource::Stylus, 0);
        });
    }
    else if (n == 2)
    {
        std::lock_guard<std::mutex> lock(task_mutex);
        tasklist.emplace_back([c=t_count, p0, p1] {
            if (c == 1)
            {
                App::I.mouse_cancel(0);
                App::I.gesture_start(p0, p1);
            }
            else
                App::I.gesture_move(p0, p1);
        });
        t_count = 2;
    }
    else if (n == 1)
    {
        UITouch *touch = valid_touches[0];
        CGPoint touchLocation = [touch locationInView:self.view];
        float force = touch.type == UITouchType::UITouchTypeStylus ? touch.force : 1.0f;
        //AppDelegate* app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
        //force = [app sonarpen_pressure];
        if (t_count == 2)
        {
            //App::I.gesture_end();
            //App::I.mouse_down(0, touchLocation.x * scale, touchLocation.y * scale, force);
        }
        else
        {
            kEventSource source = touch.type == UITouchType::UITouchTypeStylus ? kEventSource::Stylus : kEventSource::Touch;
            std::lock_guard<std::mutex> lock(task_mutex);
            tasklist.emplace_back([touchLocation, scale, source, force] {
                App::I.mouse_move(touchLocation.x * scale, touchLocation.y * scale, force, source, 0);
            });
        }
    }
    t_pos = {tl0.x * scale, tl0.y * scale};
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self.view];
    float scale = self.view.contentScaleFactor;
    
    if (ignored_touch.count(touch))
    {
        ignored_touch.erase(touch);
        NSLog(@"remove ignored");
    }

    kEventSource source = touch.type == UITouchType::UITouchTypeStylus ? kEventSource::Stylus : kEventSource::Touch;
    pen_down = false;

    std::lock_guard<std::mutex> lock(task_mutex);
    tasklist.emplace_back([tc=t_count, touchLocation, scale, source] {
        if (tc == 2)
            App::I.gesture_end();
        else
            App::I.mouse_up(0, touchLocation.x * scale, touchLocation.y * scale, source, 0);
    });

    t_count = 0;
    t_pos = {touchLocation.x * scale, touchLocation.y * scale};
}

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator
{
    [self async_lock];
    App::I.resize(size.width * self.view.contentScaleFactor,
                  size.height * self.view.contentScaleFactor);
    [self async_unlock];
}

- (void)viewDidAppear:(BOOL)animated
{
    App::I.redraw = true;
    [self resignFirstResponder];
    //[self registerForKeyboardNotifications];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    App::I.initLog();
    App::I.ios_view = self;
    
    //self.preferredFramesPerSecond = 60;
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    
    if (!self.context) {
        NSLog(@"Failed to create ES context");
    }
    
    gl_lock = [[NSLock alloc] init];
    
    GameView *view = (GameView *)self.view;
    view.context = self.context;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    view.enableSetNeedsDisplay = NO;
    glview = view;

    App::I.width = view.frame.size.width * view.contentScaleFactor;
    App::I.height = view.frame.size.height * view.contentScaleFactor;
    
    [self setupGL];
}

- (void)dealloc
{    
    [self tearDownGL];
    
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];

    if ([self isViewLoaded] && ([[self view] window] == nil)) {
        self.view = nil;
        
        [self tearDownGL];
        
        if ([EAGLContext currentContext] == self.context) {
            [EAGLContext setCurrentContext:nil];
        }
        self.context = nil;
    }

    // Dispose of any resources that can be recreated.
}

- (BOOL)prefersStatusBarHidden {
    return YES;
}

- (void)setupGL
{
    [self async_lock];
    App::I.init();
    [self async_unlock];
    
    std::thread([self]{
        while (true)
        {
            static auto time = std::chrono::steady_clock::now();
            static double elapsed = 0;
            static double min_fps_timer = 0;
            auto now = std::chrono::steady_clock::now();
            auto dt = std::chrono::duration<float>(now - time);
            time = now;
            elapsed += dt.count();
            min_fps_timer += dt.count();
            
            if (min_fps_timer > 0.1)
            {
                App::I.redraw = true;
                min_fps_timer = 0;
            }
            
            std::deque<std::packaged_task<void()>> working_list;
            if (!tasklist.empty())
            {
                std::lock_guard<std::mutex> lock(task_mutex);
                working_list = std::move(tasklist);
            }
            
            App::I.tick(dt.count());

            [self async_lock];
            if (!(App::I.redraw || App::I.animate || !working_list.empty()))
            {
                //[self.context presentRenderbuffer:GL_FRAMEBUFFER];
                [self async_unlock];
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
                continue;
            }

            while (!working_list.empty())
            {
                working_list.front()();
                working_list.pop_front();
            }
            App::I.clear();
            App::I.update(elapsed);
            [self.context presentRenderbuffer:GL_FRAMEBUFFER];
            [self async_unlock];
            [self->glview display];
            elapsed = 0;
        }
    }).detach();
}

- (void)tearDownGL
{
    [EAGLContext setCurrentContext:self.context];
}

@end

@implementation GameView

@end
