//
//  jibeStreamMapModule.m
//  mapNativeSDK
//
//  Created by Sonam Sodani on 2023-08-21.
//

#import "jibeStreamMapModule.h"
#import <React/RCTLog.h>
#import "Headers/JMapCoreKit.h"
#import "Headers/JMapControllerKit.h"
#import "Headers/JMapRenderingKit.h"
#import "EventEmitter.h"
#import "Headers/extend.h"
#import "Headers/JMapUI.h"
#import "Types.h"
// #import "AppDelegate.h"

@interface jibeStreamMapModule() <JMapJCoreDelegate, OnExtendDelegate, JMapRenderingKitDelegate, JMapDelegate, JMapUIDelegate>

@property (nonatomic) JMap *jMap;
@property (nonatomic) JMapJCore *jCore;
@property (nonatomic) JMapController *controller;
@property (strong, nonatomic) JMapCanvas *canvas;
@property (nonatomic) JMapUI *mapUIKit;

@property (strong, nonatomic) Extend * extend;

@end

@implementation jibeStreamMapModule

RCTPromiseResolveBlock initializeResolveCallbackId;
RCTPromiseRejectBlock initializeRejectCallbackId;

CTPEventEmitter *eventEmitter;
JMapOptions *options;

// To export a module named DateModule
RCT_EXPORT_MODULE(jibestreamMap);

- (UIView *)view
{

    
    self.canvas = [[JMapCanvas alloc] initWithFrame:CGRectMake(0, 0, [UIApplication sharedApplication].delegate.window.frame.size.width, [UIApplication sharedApplication].delegate.window.frame.size.height)];
    
      eventEmitter = [CTPEventEmitter allocWithZone: nil];
    self.extend = [Extend sharedExtendWithKey:@"Aw8AAAAAAAAAAAAAB_EQgAAAAAAH526AAAABjcz8gTOSGNRz5n_zeh6fqGzVpqMQTcWa6rvA2C_s7-PzmEzERIBb0xNK1qFWBg9q5GgyMPjrnopRBhDlMaQLhZ1GzhhM72FXlGLxx8sEa4cS-9tLRLH5zbaTNNDeUgrgtKu23mY4UieyiVio-_4jOec-siieHeH760m9hfxbMVA" url:@"https://v2.tendegrees.net" delegate:self];
    [self.extend setVicinityEnabled:true];
    
    return self.canvas;
}

RCT_REMAP_METHOD(loadMap,
                 withDestroyResolver:(RCTPromiseResolveBlock)resolveDestroy
                 withDestroyRejecter:(RCTPromiseRejectBlock)rejectDestroy)
{
    initializeResolveCallbackId = resolveDestroy;
    initializeRejectCallbackId = rejectDestroy;
    [self loadMap];
    
}

RCT_EXPORT_METHOD(start:(NSString *) key url:(NSString *) url)
{
    NSLog(@"IOS Start Method Called");
};

RCT_EXPORT_METHOD(create:(nonnull NSNumber *) reactTag)
{
    NSLog(@"Create - Create function is not required for IOS.");
};

RCT_EXPORT_METHOD(center:(nonnull NSNumber *) reactTag 
                    rotation:(nonnull NSNumber *) rotation
                    animationDuration:(nonnull NSNumber *) animationDuration)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSValue *centerPoint = [NSValue valueWithCGPoint:CGPointMake(self.controller.userLocation.center.x, self.controller.userLocation.center.y)];
        
        JMapTransform *transform = [[JMapTransform alloc] initWithX:@(self.controller.userLocation.center.x) y:@(self.controller.userLocation.center.y) scale:nil rotation:rotation atCenter:nil duration:(animationDuration.floatValue / 1000.0)];
        [self.controller setMapTransform:transform];
    });
};

RCT_EXPORT_METHOD(setMapVenue:(nonnull NSNumber *) reactTag
                  credentials:(NSDictionary *) credentials
                  compass:(NSDictionary *) compass
                  floorSelector:(NSDictionary *) floorSelector
                  zoomButtons:(NSDictionary *) zoomButtons
                  search:(NSDictionary *) search)
{

    NSLog(@"Received SetMapVenue");
    printf("%i",([[credentials objectForKey:@"venueId"] intValue]));
    options = [[JMapOptions alloc] init];
    options.venueId = [[credentials objectForKey:@"venueId"] intValue];
    options.host = [credentials objectForKey:@"host"];
    options.customerId = [[credentials objectForKey:@"customerId"] intValue];
    options.clientId = [credentials objectForKey:@"clientId"];
    options.clientSecret = [credentials objectForKey:@"clientSecret"];
    options.stage = self.canvas;
    options.clearCacheData = true;
    options.autoReloadCache = [NSNumber numberWithBool:true];

    [self loadMap];
};

RCT_EXPORT_METHOD(centerMapOnPoint:(nonnull NSNumber *) reactTag
                    point:(NSArray *) point)
{
    NSLog(@"centerMapOnPoint - Not implemented yet");
};

RCT_EXPORT_METHOD(setFollowBlueDot:(nonnull NSNumber *)
                  reactTag enabled:(BOOL) enabled
                    rotate:(nonnull NSNumber *) rotate
                    interval:(nonnull NSNumber *) interval)
{
    NSLog(@"setFollowBlueDot - Not available yet, testing in progress");
};


RCT_EXPORT_METHOD(setUserPosition:(nonnull NSNumber *) reactTag
    x:(nonnull NSNumber *) x
    y:(nonnull NSNumber *) y
    heading:(nonnull NSNumber *) heading
    format:(NSString *) format)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        CGPoint convertPoint = CGPointMake(y.floatValue, x.floatValue);
        convertPoint = [JMapUtils convertPoint:convertPoint fromProjection:@"EPSG:4326" toProjection:@"jmap:local" inVenue:self.controller.activeVenue];


        [self.controller updateUserLocation:convertPoint floorMap:self.controller.currentMap orientation:0 confidenceRadius:@100];
        NSArray *newUserLocation = @[@(convertPoint.x), @(convertPoint.y)];
        [eventEmitter sendEvent:@"USER_POSITION_UPDATED" body: newUserLocation];
        
    });
    // CGPoint point = CGPointMake(x.floatValue, y.floatValue);
    // [self setMapUserPosition:point];
};

RCT_EXPORT_METHOD(setUserPositionXY:(nonnull NSNumber *) reactTag
                 x:(nonnull NSNumber *) x
                 y:(nonnull NSNumber *) y)
{
    CGPoint point = CGPointMake(x.floatValue, y.floatValue);
    [self setMapUserPosition:point];
};

RCT_EXPORT_METHOD(clearWayfindingPath:(nonnull NSNumber *) reactTag)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.controller clearWayfindingPath];
    });
};

RCT_EXPORT_METHOD(wayfindToWaypoint:(nonnull NSNumber *) reactTag
                  waypointId:(nonnull NSNumber *) waypointId)
{
    NSLog(@"wayfindToWaypoint - Not available yet, testing in progress");
};

RCT_EXPORT_METHOD(wayfindBetweenWaypoints:(nonnull NSNumber *) reactTag
                    originId:(nonnull NSNumber *) originId
                    destinationId:(nonnull NSNumber *) destinationId)
{

    NSArray * destinations = [self.controller.activeVenue.destinations getByMap:self.controller.currentMap];
     
    if (destinations.count > originId.intValue && destinations.count > destinationId.intValue) {
        // Get 2 destinations
        JMapDestination * d1 = [destinations objectAtIndex:originId.intValue];
        JMapDestination * d2 = [destinations objectAtIndex:destinationId.intValue];
            
        // Extract waypoints
        NSArray <JMapWaypoint*>* wapoints1 = [self.controller.currentMap.waypoints getByDestination:d1];
        NSArray <JMapWaypoint*>* wapoints2 = [self.controller.currentMap.waypoints getByDestination:d2];
            
        JMapWaypoint * to = wapoints1.count > 0 ? [wapoints1 firstObject] : nil;
        JMapWaypoint * from = wapoints2.count > 0 ? [wapoints2 firstObject] : nil;
            
        // If waypoints exists, pass it to drawPath method below
        if (to && from) {
            // Calculate a wayfinding path
            NSArray<JMapPathPerFloor*>* paths = [self.controller wayfindBetweenWaypoint:from andWaypoint:to withAccessibility:100 withObstacle:nil];
    
            // Iterate through paths to be drawn
            for (JMapPathPerFloor * path in paths) {
                // Pass the path to the controller to be drawn
                [self.controller drawWayfindingPath:path];
            }
        }
    } 
};

RCT_EXPORT_METHOD(drawNavigationPath:(nonnull NSNumber *)reactTag
                    pathId:(nonnull NSString *) pathId
                    shouldFocus:(BOOL) shouldFocus
                    style:(NSDictionary *) style)
{
    NSLog(@"drawNavigationPath - Not available yet, testing in progress");
};

RCT_EXPORT_METHOD(styleUnitsForDestination:(nonnull NSNumber *) reactTag
                    destination:(NSDictionary *) destination
                    style:(NSDictionary *) style)
{
    NSLog(@"styleUnitsForDestination - Not implemented yet");
};

RCT_EXPORT_METHOD(styleShape:(nonnull NSNumber *) reactTag
                  destination:(nonnull NSNumber *) sid
                  style:(NSDictionary *) style)
{
    NSLog(@"styleShape - Not implemented yet");
};



- (void)loadMap{
    _jMap = [[JMap alloc] initWithOptions:options];
    _jMap.delegate = self;
}


- (void)jmapInitialized:(JMapError *)error {
    if (error) {
        NSLog(@"Error: %@", error.message);
        // initializeRejectCallbackId(@"message",error.message, nil);
    } else {
        self.controller = _jMap.controller;
        [self setupUIKit];
        
        [eventEmitter sendEvent:@"WINDOW_LOADED" body: nil];
        // initializeResolveCallbackId(@"Success");
    }
}

-(void) setMapUserPosition:(CGPoint)point{
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.controller updateUserLocation:point floorMap:self.controller.currentMap orientation:0 confidenceRadius:@100];
        NSArray *newUserLocation = @[@(point.x), @(point.y)];
        [eventEmitter sendEvent:@"USER_POSITION_UPDATED" body: newUserLocation];
        
    });
}

static id ObjectOrNull(id object){
    return object ?: [NSNull null];
}

- (void) onReady:(bool)ready
{
    NSDictionary *eventData = @{
        @"ready": @(ready),
    };
    
    NSLog(@"Ready!");
    [eventEmitter sendEvent:@"ODP_READY" body:eventData];
}

- (void) onKeyValidation:(bool)validated
{
    
    if (![self.extend isServiceStarted] && validated)
    {
        NSLog(@"On Key Validation");
        [self.extend setVicinityEnabled:true];
        [self.extend start];
        
    }else{
        NSLog(@"Failed to Validate Key");
    }
    
    NSDictionary *eventData = @{
        @"validated": @(validated),
    };
    
    [eventEmitter sendEvent:@"ODP_KEY_VALIDATION" body:eventData];
}

- (void) onNavigationUpdate:(TDNavOutput *)navOutput{
    NSDictionary *eventData = @{
        @"lat": ObjectOrNull(@(navOutput.latitude)),
        @"long": ObjectOrNull(@(navOutput.longitude)),
        @"bearing": ObjectOrNull(@(navOutput.bearing)),
        @"viewHeading": ObjectOrNull(@(navOutput.viewHeading)),
        @"floor": ObjectOrNull(@(navOutput.floor)),
        @"accuracyRadius": ObjectOrNull(@(navOutput.accuracyRadius)),
        @"utc": ObjectOrNull(@(navOutput.utc)),
        @"x": ObjectOrNull(@(navOutput.x)),
        @"y": ObjectOrNull(@(navOutput.y)),
        @"xFom": ObjectOrNull(@(navOutput.xFom)),
        @"yFom": ObjectOrNull(@(navOutput.yFom)),
        @"usedSystems": ObjectOrNull(@(navOutput.usedSystems)),
        @"dynamicState": ObjectOrNull(@(navOutput.dynamicState)),
        @"elevationState": ObjectOrNull(@(navOutput.elevationState)),
        @"pdrSpeedLimitExceeded": ObjectOrNull(@(navOutput.pdrSpeedLimitExceeded))
    };
    
    CGPoint convertPoint = CGPointMake(navOutput.longitude, navOutput.latitude);
    convertPoint = [JMapUtils convertPoint:convertPoint fromProjection:@"EPSG:4326" toProjection:@"jmap:local" inVenue:self.controller.activeVenue];

    // [self setMapUserPosition:convertPoint];

    [eventEmitter sendEvent:@"ODP_NAVIGATION_UPDATE" body:eventData];
}

- (void) onFacilityUpdateName:(NSString *)name onFacilityUpdateLocation:(Location *)location onFacilityUpdateRegion:(Region *)region onFacilityUpdateFloorPlans:(NSArray *)plans onFacilityUpdateCategories:(NSArray *)categories {
    
    NSDictionary *locationDict = @{
        @"latitude": ObjectOrNull(@(location.latLong.latitude)),
        @"longitude": ObjectOrNull(@(location.latLong.longitude)),
        @"x": ObjectOrNull(@(location.x)),
        @"y": ObjectOrNull(@(location.y)),
        @"z": ObjectOrNull(@(location.z)),
        @"altitude": ObjectOrNull(@(location.altitude)),
        @"isGeo": ObjectOrNull(@(location.isGeo)),
        
    };
    
    
    NSDictionary *regionDict = @{
        @"floor": ObjectOrNull(@(region.floor)),
        @"id": ObjectOrNull(@(region.uid)),
        @"name": ObjectOrNull(region.name),
        @"icon": ObjectOrNull(region.icon),
        @"type": ObjectOrNull(region.type),
        @"points": ObjectOrNull(region.locations),
        @"distanceToUser": ObjectOrNull(@(region.distanceToUser)),
        @"userOnSameFloor": ObjectOrNull(@(region.userOnSameFloor)),
        @"className": ObjectOrNull(region.type),
    };
    
    NSDictionary *eventData = @{
        @"name": name,
        @"location": locationDict,
        @"region": regionDict,
        @"floorPlans": ObjectOrNull(plans),
    };
    
       [eventEmitter sendEvent:@"ODP_FACILITY_UPDATE" body:eventData];
    
}

- (void) dataLoadEvent: (NSString *)data
{
    NSLog(@"On Region Change");
    NSLog(data);
    
}

- (void)setupUIKit {
    // Initialize UI Kit
    self.mapUIKit = [[JMapUI alloc] initWithController:self.controller andViewController:self andOptions:nil];
    self.mapUIKit.delegate = self;
    
    // Call to show map elements
    [self loadUIElements];
}

- (void)loadUIElements {
    
    // Call to load search bar
    [self loadSearchBar];
    
    // Call to load compass
    
    [self loadCompass];
}

- (void)loadSearchBar {
    // Load Search Bar
    NSDictionary * searchOption = @{@"placeholder": @"Search", @"maxResults": @3, @"position": @0.5};
    [self.mapUIKit renderSearchWithOptions:searchOption];
}

- (void)loadCompass {
    // Load compass
    NSLog(@"Showing Compass");
    NSDictionary *compassOption = @{@"resetRotationOnTap": @1, @"position": @[@0.3, @0.3], @"width": @75, @"height": @75};
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.mapUIKit renderCompassWithOptions:compassOption];
    });
    
}

- (void)canvas:(nonnull JMapCanvas *)canvas scrollViewDidRotateByDegrees:(float)degrees {
    [self.mapUIKit updateCompass:degrees];
}

- (void)actionButtonCallback:(nonnull Popup *)popup {
    NSLog(@"Action Button Callback");
}

- (void)onSelect:(id)item {
    NSLog(@"On Select");
}



@end






