//
//  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"
#include "platform_apple/apple_platform_services.h"
#include "settings.h"
#import "objc_utils.h"
#import <MobileCoreServices/MobileCoreServices.h>

std::mutex render_mutex;
std::condition_variable render_cv;
static std::unique_ptr<pp::platform::PlatformServices> g_platform_services;

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

@interface GameFilePicker : UIDocumentPickerViewController
{
    @public std::promise<std::string> promise;
    @public std::function<void(std::string)> callback;
}
@end
@implementation GameFilePicker
@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
- (std::string)clipboard_get_string
{
    NSString* ns = [[UIPasteboard generalPasteboard] string];
    const char* ptr = [ns cStringUsingEncoding:NSUTF8StringEncoding];
    return std::string(ptr);
}
- (bool)clipboard_set_string:(const std::string &)s
{
    NSString* ns = [NSString stringWithUTF8String:s.c_str()];
    [[UIPasteboard generalPasteboard] setString:ns];
    return true;
}
- (void)crash
{
    AppDelegate* app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [app hockeyapp_crash];
}
- (void)async_lock
{
    lock_mutex.lock();
    [EAGLContext setCurrentContext:context];
//    GameView* view = (GameView*)self.view;
//    [view bindDrawable];
}
- (void)async_unlock
{
    lock_mutex.unlock();
}
- (void)async_swap
{
    [context presentRenderbuffer:GL_FRAMEBUFFER];
    [glview display];
}

- (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)init_dirs
{
    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString* docpath = (NSString*)[paths objectAtIndex:0];
    const std::string data_path = [docpath cStringUsingEncoding:NSASCIIStringEncoding];

    NSError* err = nil;

    NSString* recpath = [docpath stringByAppendingString:@"/rec"];
    const std::string recording_path = [recpath cStringUsingEncoding:NSASCIIStringEncoding];
    if (![[NSFileManager defaultManager] createDirectoryAtPath:recpath withIntermediateDirectories:YES attributes:nil error:&err])
    {
        LOG("error creating rec path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
    }
    // tmp
    NSString* tmppath = [docpath stringByAppendingString:@"/tmp"];
    const std::string temporary_path = [tmppath cStringUsingEncoding:NSASCIIStringEncoding];
    if (![[NSFileManager defaultManager] createDirectoryAtPath:tmppath withIntermediateDirectories:YES attributes:nil error:&err])
    {
        LOG("error creating rec path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
    }
    // brushes
    if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/brushes"] withIntermediateDirectories:YES attributes:nil error:&err])
    {
        LOG("error creating brushes path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
    }
    if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/brushes/thumbs"] withIntermediateDirectories:YES attributes:nil error:&err])
    {
        LOG("error creating brushes thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
    }
    // patterns
    if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/patterns"] withIntermediateDirectories:YES attributes:nil error:&err])
    {
        LOG("error creating patterns path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
    }
    if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/patterns/thumbs"] withIntermediateDirectories:YES attributes:nil error:&err])
    {
        LOG("error creating patterns thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
    }
    // settings
    if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/settings"] withIntermediateDirectories:YES attributes:nil error:&err])
    {
        LOG("error creating settings path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]);
    }

    pp::platform::apple::set_legacy_apple_storage_paths({
        data_path,
        data_path,
        recording_path,
        temporary_path,
    });
}

- (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:NULL];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [picker dismissViewControllerAnimated:YES completion:NULL];
}

- (void)pick_file:(NSArray<NSString*>*)types then:(std::function<void(std::string)>)callback
{
    // convert extension into UTIs from the system database
    NSMutableArray* UTIs = [NSMutableArray arrayWithCapacity:types.count];
    for (NSString* ext : types)
    {
        NSString *typeForExt = (__bridge NSString*)
            UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
            (__bridge CFStringRef)ext, NULL);
        [UTIs addObject:typeForExt];
    }

    GameFilePicker *picker = [[GameFilePicker alloc] initWithDocumentTypes:UTIs inMode:UIDocumentPickerModeImport];
    picker.delegate = self;
    picker->callback = callback;
    [self presentViewController:picker animated:YES completion:NULL];
}
- (void)pick_file_save:(std::string)path
{
    NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.c_str()]];
    GameFilePicker *picker = [[GameFilePicker alloc] initWithURL:url inMode:UIDocumentPickerModeExportToService];
    picker.delegate = self;
    [self presentViewController:picker animated:YES completion:NULL];
}
-(void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentAtURL:(NSURL *)url
{
    NSString *chosenImage = [url path];
    GameFilePicker* p = static_cast<GameFilePicker*>(controller);
    if (p->callback)
    {
        std::string path = [chosenImage cStringUsingEncoding:NSUTF8StringEncoding];
        p->callback(path);
    }
    [controller dismissViewControllerAnimated:YES completion:NULL];
}
-(void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller
{
    [controller dismissViewControllerAnimated:YES completion:NULL];
}

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

-(void)show_keyboard
{
    input_enabled = YES;
    [self becomeFirstResponder];
}

-(void)hide_keyboard
{
    input_enabled = NO;
    [self resignFirstResponder];
}

// 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;
    CGRect frame = [[UIScreen mainScreen] bounds];
    frame.size.height -= kbSize.height;
    view.frame = frame;

    App::I->ui_task_async([size=frame.size, f=self.view.contentScaleFactor]
    {
        App::I->resize(size.width * f, size.height * f);
    });
    App::I->animate = false;
}

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

- (void)reset_touch
{
    App::I->ui_task_async([=]
    {
        if (t_count == 2)
            App::I->gesture_end();
        else
            App::I->mouse_cancel(0);
    });
    t_count = 0;
}

float get_force(UITouch* t)
{
    //glm::pow(t.force / t.maximumPossibleForce, 0.5);
    return glm::clamp(t.force / 1.0, 0.0, 1.0);
}

std::set<UITouch*> ignored_touch;
std::map<UITouch*, glm::vec2> touch_start;
int max_touch_count = 0;
bool is_tap = true;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    max_touch_count = (int)[[event allTouches] count];
    UITouch *touch = [[event allTouches] anyObject];

    CGPoint touchLocation = [touch locationInView:self.view];
    float scale = self.view.contentScaleFactor;

    float force = get_force(touch);
    AppDelegate* app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    if ([app sonarpen_present])
        force = [app sonarpen_pressure];

    kEventSource source = ((touch.type == UITouchType::UITouchTypeStylus) || [app sonarpen_present]) ?
        kEventSource::Stylus : kEventSource::Touch;
    
    // apple pencil
    if (touch.type == UITouchType::UITouchTypeStylus)
    {
        pen_down = true;
        is_tap = false;
    }
    else
    {
        is_tap = true;
        touch_start.clear();
    }
    
    App::I->ui_task_async([touchLocation, scale, f=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
{
    is_tap = false;
    int n = (int)[[event allTouches] count];
    float scale = self.view.contentScaleFactor;
    max_touch_count = std::max<int>(max_touch_count, n);
    UITouch* stylus_touch = nullptr;

    std::vector<UITouch*> valid_touches;
    for (int i = 0; i < n; i++)
    {
        auto t = [[[event allTouches] allObjects] objectAtIndex:i];
        if (t.type == UITouchType::UITouchTypeStylus)
            stylus_touch = t;
        auto it = ignored_touch.find(t);
        if (it == ignored_touch.end())
        {
            valid_touches.push_back(t);
            if (touch_start.find(t) == touch_start.end())
            {
                auto loc = [t locationInView:self.view];
                touch_start[t] = { loc.x, loc.y };
            }
        }
        else
        {
            NSLog(@"ignored moved");
        }
    }
    n = (int)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)
    {
        if (stylus_touch)
        {
            auto loc = [stylus_touch locationInView:self.view];
            auto p = glm::vec2(loc.x, loc.y) * scale;
            App::I->ui_task_async([tt=stylus_touch.type,p,f=get_force(stylus_touch)] {
                App::I->mouse_move(p.x, p.y, f, kEventSource::Stylus, 0);
            });
        }
    }
    else if (stylus_touch)
    {
        pen_down = true;
        auto loc = [stylus_touch locationInView:self.view];
        auto p = glm::vec2(loc.x, loc.y) * scale;
        float f = get_force(stylus_touch);
        App::I->ui_task_async([tc=t_count, p, f] {
            if (tc == 2)
                App::I->gesture_end();
            else if (tc == 1)
                App::I->mouse_cancel(0);
            App::I->mouse_down(0, p.x, p.y, f, kEventSource::Stylus, 0);
            App::I->mouse_move(p.x, p.y, f, kEventSource::Stylus, 0);
        });
        t_count = 0;
    }
    else if (n == 2)
    {
        App::I->ui_task_async([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 ? get_force(touch) : 1.0f;
        AppDelegate* app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
        if ([app sonarpen_present])
            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) || [app sonarpen_present]) ?
                kEventSource::Stylus : kEventSource::Touch;
            App::I->ui_task_async([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
{
    static NSTimeInterval last_time = 0;
    static int tap_count = 1;
    
    int n = (int)[[event allTouches] count];
    UITouch *touch = [[event allTouches] anyObject];
    CGPoint touchLocation = [touch locationInView:self.view];
    float scale = self.view.contentScaleFactor;
    
    if (touch.timestamp - last_time < 0.2)
        tap_count++;
    else
        tap_count = 1;
    last_time = touch.timestamp;
    
    glm::vec2 center(0);
    float max_dist = 0;
    for (UITouch* t : [event allTouches])
    {
        auto loc = [t locationInView:self.view];
        center += glm::vec2(loc.x, loc.y);
        if (touch_start.find(t) != touch_start.end())
        {
            max_dist = std::max<float>(max_dist, glm::distance(touch_start[t], { loc.x, loc.y }));
        }
    }
    center /= (float)n;
    //LOG("max touches (%d-%d) (max %d) dist %f",
    //    n, (int)touch_start.size(), max_touch_count, (int)max_dist);
    
    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;

    App::I->ui_task_async([tc=t_count, touchLocation, scale, source, n, center, max_dist] {
        if (tc == 2)
            App::I->gesture_end();
        else
            App::I->mouse_up(0, touchLocation.x * scale, touchLocation.y * scale, source, 0);
        if (is_tap || max_dist < 10)
            App::I->touch_tap(center, n == 1 ? max_touch_count : n, tap_count);
    });

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

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

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

- (void)viewDidLoad
{
    [super viewDidLoad];
    input_enabled = NO;
    App::I = new App;
    pp::platform::apple::set_legacy_apple_state(
        self,
        (AppDelegate*)[[UIApplication sharedApplication] delegate]);
    g_platform_services = pp::platform::apple::create_apple_platform_services();
    App::I->set_platform_services(g_platform_services.get());
    App::I->initLog();
    
    //self.preferredFramesPerSecond = 60;
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    
    if (!context) {
        NSLog(@"Failed to create ES context");
    }
    
    GameView *view = (GameView *)self.view;
    view.context = 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;
    App::I->zoom = Settings::value_or<Serializer::Float>("ui-scale", (float)view.contentScaleFactor);

    App::I->render_thread_start();
    App::I->ui_thread_start();
}

- (BOOL)prefersStatusBarHidden {
    return YES;
}

@end

@implementation GameView

@end
