#import <React/RCTLog.h>
#include <math.h>
#import <CoreGraphics/CoreGraphics.h>


#import "jibeStreamMapModule.h"
#import "JibestreamConverter.h"
#import "EventEmitter.h"





#import <JMapCoreKit.h>
#import <JMapControllerKit.h>
#import <JMapNavigationKit.h>
#import "Headers/JMapGeofenceKit.h"



//#import "Headers/JMapNavigationKit.h"
//#import "Headers/JMapCoreKit.h"
//#import "Headers/JMapControllerKit.h"
//#import "Headers/extend.h"




@interface jibeStreamMapModule() <JMapJCoreDelegate, JMapDelegate, JMapGeofenceDelegate, JMapRenderingKitDelegate>

@property (nonatomic) JMap *jMap;
@property (nonatomic) JMapJCore *jCore;
@property (nonatomic) JMapController *controller;
@property (strong, nonatomic) JMapCanvas *canvas;
@property (nonatomic) JMapNavigation *navKit;
@property (nonatomic) JMapGeofence *geofenceKit;


@end

@implementation jibeStreamMapModule

RCTPromiseResolveBlock initializeResolveCallbackId;
RCTPromiseRejectBlock initializeRejectCallbackId;

CTPEventEmitter *eventEmitter;
JMapOptions *options;

NSMutableDictionary * generatedPaths;

bool followBlueDot = false;
bool followBlueDotHeading = false;
NSNumber * followBlueDotInterval = 0;
long long lastFollowBlueDot = 0;
UIImage * arrowIcon;

bool isNavigating = false;
NSString * navigationPath;

NSDictionary * mapOptions;

NSDictionary * pathStyle;
NSDictionary * pathOriginIcon;
NSDictionary * pathDestinationIcon;

NSMutableArray *images;

NSMutableDictionary * amenityMapping;

NSNumber * mapOrientation = 0;
NSNumber * mapZoom;



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

- (UIView *)view
{
    generatedPaths = [NSMutableDictionary dictionary];
    images = [NSMutableArray array];
    amenityMapping = [NSMutableDictionary dictionary];
    
    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];
    
    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)
{
};

RCT_EXPORT_METHOD(create:(nonnull NSNumber *) reactTag)
{
};

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

RCT_EXPORT_METHOD(setMapVenue:(nonnull NSNumber *) reactTag
                  credentials:(NSDictionary *) credentials
                  mapOptions:(NSDictionary *) mapOptions)
{
    
    NSString * locale = [credentials objectForKey:@"locale"];
    
    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];
    
    if(locale){
        options.locale = locale;
    }
    
    
    //    if(locale.length > 0){
    //        options.locale = locale;
    //    }else{
    //        NSLocale *currentLocale = [NSLocale currentLocale];
    //        options.locale = [NSString stringWithFormat:@"%@-%@", currentLocale.languageCode, currentLocale.countryCode];
    
    //    }
    
    
    [self updateMapOptions:mapOptions];
    
    
    [self loadMap];
};

RCT_EXPORT_METHOD(centerMapOnPoint:(nonnull NSNumber *) reactTag
                  options:(NSDictionary *)options)
{
    if(options){
        NSNumber * x = [options objectForKey:@"point"][0];
        NSNumber * y = [options objectForKey:@"point"][1];
        NSNumber * zoom = [options objectForKey:@"zoom"];
        NSNumber * rotation = [options objectForKey:@"rotation"];
        NSNumber * animationDuration = [options objectForKey:@"animationDuration"];
        NSString * format = [options objectForKey:@"format"];
        
        CGPoint convertPoint = CGPointMake(x.floatValue, y.floatValue);
        if([format isEqualToString:@"LAT_LONG"]){
            convertPoint = CGPointMake(y.floatValue, x.floatValue);
            convertPoint = [JMapUtils convertPoint:convertPoint fromProjection:@"EPSG:4326" toProjection:@"jmap:local" inVenue:self.controller.activeVenue];
        }else if([format isEqualToString:@"X_Y"]){
            
        }else{
            return;
        }
        
        [self centerMapOnPosition:@(convertPoint.x) y:@(convertPoint.y) zoom:zoom orientation:@(-1*rotation.floatValue) animationDuration:animationDuration];
    }else{
        NSLog(@"Options is Null. Cannot center map on point");
    }
};

RCT_EXPORT_METHOD(setFollowBlueDot:(nonnull NSNumber *)
                  reactTag enabled:(BOOL) enabled
                  rotate:(BOOL *) rotate
                  interval:(nonnull NSNumber *) interval)
{
    followBlueDot = enabled;
    followBlueDotHeading = rotate;
    followBlueDotInterval = interval;
    

    
    if(followBlueDot){
        [self centerMapOnPosition:@(self.controller.userLocation.position.x) y:@(self.controller.userLocation.position.y) zoom:@5 orientation:@(-self.controller.userLocation.orientation) animationDuration:@1500];
    }
};


RCT_EXPORT_METHOD(setUserPosition:(nonnull NSNumber *) reactTag
                  x:(nonnull NSNumber *) x
                  y:(nonnull NSNumber *) y
                  heading:(nonnull NSNumber *) heading
                  confidence:(nonnull NSNumber *) confidence
                  format:(NSString *) format)
{
    CGPoint convertPoint = CGPointMake(x.floatValue, y.floatValue);
    if(![format isEqualToString:@"xy"]){
        convertPoint = CGPointMake(y.floatValue, x.floatValue);
        convertPoint = [JMapUtils convertPoint:convertPoint fromProjection:@"EPSG:4326" toProjection:@"jmap:local" inVenue:self.controller.activeVenue];
    }
    
    float angle = [self getAngleBetweenPoints:self.controller.userLocation.position endPoint:convertPoint];
    float distance = [self getDistanceBetweenPoints:self.controller.userLocation.position endPoint:convertPoint];
    
    
    float animationDuration = 1000 * (distance / 50.0);
    
    
//    [self zoomMapToBounds:CGRectMake(convertPoint.x, convertPoint.y, 50, 50)];
    
    if(animationDuration < 200){
        animationDuration = 200;
    }
    
    
    if(followBlueDot){
        long long currentTime = [self getSystemTimeMillis];
        
        
        
        
        NSNumber * orientation = @(self.controller.userLocation.orientation);
        
        if(followBlueDotHeading){
            
            // Mathematically, this seems wrong to me.
            orientation = @(-1 * heading.floatValue); //@( -1.0 * self.controller.userLocation.orientation);
        }
        
        
        if(currentTime - lastFollowBlueDot > followBlueDotInterval.longLongValue){
            [self centerMapOnPosition:@(convertPoint.x) y:@(convertPoint.y) zoom:nil orientation:orientation animationDuration:@(animationDuration)];
        }
        
    }

    [self setMapUserPosition:convertPoint orientation:heading];
    
    if(isNavigating){
        
        bool autoReroute = false;
        if([[mapOptions objectForKey:@"navigation"] objectForKey:@"autoReroute"]){
            autoReroute = [[mapOptions objectForKey:@"navigation"] objectForKey:@"autoReroute"];
        }
        
        float autoRerouteThreshold = 0;
        if([[[mapOptions objectForKey:@"navigation"] objectForKey:@"autoRerouteThreshold"] floatValue]){
            autoRerouteThreshold = [[[mapOptions objectForKey:@"navigation"] objectForKey:@"autoRerouteThreshold"] floatValue];
        }
        
        NSArray<JMapPathPerFloor*>* paths = [generatedPaths objectForKey:navigationPath];
        
        
        if(autoReroute && [self.navKit hasUserVeeredOffRoute:paths threshold:autoRerouteThreshold]){
            
            long lastPathIndex = paths.count - 1;
            long lastPointIndex = paths[lastPathIndex].points.count -1;
            JMapASNode *jMapASNode = paths[lastPathIndex].points[lastPointIndex];
            JMapWaypoint *destinationWaypoint = [self.controller.currentMap.waypoints getById:[jMapASNode.nodeId intValue]];
            
            JMapPoint * userLocation = [[JMapPoint alloc] initWithX:convertPoint.x Y:convertPoint.y Z:[self.controller.currentMap.id floatValue]];
            
            
            NSArray * orderedWaypoints = sortWaypointsByDistance(userLocation, self.controller.currentMap.waypoints.getAll);
            
            int count = 0;
            for(JMapWaypoint * waypoint in orderedWaypoints){
                NSDictionary * pathParameters = [self generateWayfindingPath:waypoint destinationWaypoint:destinationWaypoint];
                
                if(pathParameters != NULL && [pathParameters[@"jibestreamPath"] count] > 0){
                    NSString *key = [@"" stringByAppendingFormat:@"%@_%@", [waypoint.id stringValue], [destinationWaypoint.id stringValue]];
                    [self drawNavigationPath:0 pathId:key shouldFocus:NO style:pathStyle originIconUrl:pathOriginIcon destinationIconUrl:pathDestinationIcon];
                    [eventEmitter sendEvent:@"REROUTE_NAVIGATION_PATH_GENERATED" body: pathParameters];
                    break;
                }
                if (count > 3){
                    NSDictionary * emptyParameters;
                    [eventEmitter sendEvent:@"REROUTE_NAVIGATION_PATH_GENERATED" body: emptyParameters];
                    break;
                }
                count +=1;
            }
        }
    }
}


-(long long) getSystemTimeMillis {
    NSDate *now = [NSDate date];
    NSTimeInterval timeInterval = [now timeIntervalSince1970];
    NSTimeInterval milliseconds = timeInterval * 1000.0;
    long long currentTimeMillis = (long long)milliseconds;

    return currentTimeMillis;
}


RCT_EXPORT_METHOD(clearWayfindingPath:(nonnull NSNumber *) reactTag)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.controller clearWayfindingPath];
        for(UIView * image in images){
            [self.controller removeComponent:image onMap:self.controller.currentMap];
        }
        images = [NSMutableArray array];
        isNavigating = false;
    });
};

RCT_EXPORT_METHOD(wayfindToWaypoint:(nonnull NSNumber *) reactTag
                  waypointId:(nonnull NSNumber *) waypointId)
{
    JMapPoint * userLocation = [[JMapPoint alloc] initWithX:self.controller.userLocation.center.x Y:self.controller.userLocation.center.y Z:[self.controller.currentMap.id floatValue]];
    
    JMapWaypoint * destinationWaypoint = [self.controller.currentMap.waypoints getById:waypointId.intValue];
    
    NSDictionary * pathParameters = [self generateBestWayfindingPathFromUser:destinationWaypoint userLocation:userLocation];
    if(pathParameters){
        [eventEmitter sendEvent:@"NAVIGATION_PATH_GENERATED" body: pathParameters];
    }else{
        NSDictionary * emptyParameters;
        [eventEmitter sendEvent:@"NAVIGATION_PATH_GENERATED" body: emptyParameters];
    }
    
    
};
    




RCT_EXPORT_METHOD(wayfindBetweenWaypoints:(nonnull NSNumber *) reactTag
                  originId:(nonnull NSNumber *) originId
                  destinationId:(nonnull NSNumber *) destinationId)
{
    
    JMapWaypoint * waypoint1 = [self.controller.currentMap.waypoints getById:originId.intValue];
    JMapWaypoint * waypoint2 = [self.controller.currentMap.waypoints getById:destinationId.intValue];
    
    
    NSDictionary * pathParameters = [self generateWayfindingPath:waypoint1 destinationWaypoint:waypoint2];
    if(pathParameters){
        [eventEmitter sendEvent:@"NAVIGATION_PATH_GENERATED" body: pathParameters];
    }else{
        NSDictionary * emptyParameters;
        [eventEmitter sendEvent:@"NAVIGATION_PATH_GENERATED" body: emptyParameters];
    }
};


RCT_EXPORT_METHOD(drawNavigationPath:(nonnull NSNumber *)reactTag
                  pathId:(nonnull NSString *) pathId
                  shouldFocus:(BOOL) shouldFocus
                  style:(NSDictionary *) style
                  originIconUrl:(NSDictionary *) originImage
                  destinationIconUrl:(NSDictionary *) destinationImage
                  )
{
    
    [self.controller clearWayfindingPath];
    for(UIView * image in images){
        [self.controller removeComponent:image onMap:self.controller.currentMap];
    }
    images = [NSMutableArray array];

    if(pathId){
        pathStyle = style;
        pathOriginIcon = originImage;
        pathDestinationIcon = destinationImage;

        NSArray<JMapPathPerFloor*>* paths = [generatedPaths objectForKey:pathId];


        if(paths){
            int strokeWidth = 2;
            float strokeOpacity = 1;

            UIColor * fillColor = [UIColor clearColor];
            UIColor * strokeColor = [UIColor blackColor];

            JMapStyle * pathStyle = [JMapStyle alloc];
            if(style){

                if([style objectForKey:@"strokeWidth"]){
                    strokeWidth = [[style objectForKey:@"strokeWidth"] intValue];
                    [pathStyle setLineWidth:strokeWidth];

                }
                if([style objectForKey:@"strokeOpacity"]){
                    strokeOpacity = [[style objectForKey:@"strokeOpacity"] floatValue];
                    [pathStyle setOpacity:strokeOpacity];
                }
                if([style objectForKey:@"stroke"]){
                    strokeColor = [JibestreamConverter colorFromHexString:[style objectForKey:@"stroke"]];
                    [pathStyle setStrokeColor:strokeColor];
                }else{
                    strokeColor = [JibestreamConverter colorFromHexString:@"#000000"];
                    [pathStyle setStrokeColor:strokeColor];
                }
            } else{
                strokeColor = [JibestreamConverter colorFromHexString:@"#000000"];
                [pathStyle setStrokeColor:strokeColor];
                [pathStyle setOpacity:1];
            }
            [self.controller drawWayfindingPaths:paths withStyle:pathStyle];
            isNavigating = true;
            navigationPath = pathId;

            if(originImage){
                //                UIImage * image = [JibestreamConverter imageFromDict:originImage];
                CGPoint drawPoint = CGPointMake([paths.firstObject.points[0].x intValue], [paths.firstObject.points[0].y intValue]);
                [self drawImageAtPoint:originImage point:drawPoint];
            }
            if(destinationImage){
                //                UIImage * image = [JibestreamConverter imageFromDict:destinationImage];
                int index = paths.firstObject.points.count -1;
                CGPoint drawPoint = CGPointMake([paths.firstObject.points[index].x intValue], [paths.firstObject.points[index].y intValue]);
                [self drawImageAtPoint:destinationImage point:drawPoint];
            }

            if(shouldFocus){
                [self zoomMapToPath:0.2 rotation:0 animationDuration:1000];
            }

        }
        else{
            NSLog(@"Cannot Draw Path, No Path has been generated for key.");
        }
    } else{
        NSLog(@"Cannot Draw Paths, pathId is null");
    }
    
    
};

RCT_EXPORT_METHOD(styleUnitsForDestination:(nonnull NSNumber *) reactTag
                  destination:(nonnull NSNumber *) destinationId
                  style:(NSDictionary *) style)
{
    JMapDestination * destination = [self getDestinationById:destinationId];
    [self.controller getUnitsFromMap:self.controller.currentMap completionHandler:^(NSArray *shapes, JMapError *error) {
        //Iterate through each unit in shapes
        for (Shape * unit in shapes) {
            
            //Check if unit has destination association
            NSArray * dataUnit = [unit.meta objectForKey:@"data-unit"];
            if(dataUnit && dataUnit.count > 0) {
                NSString * destinationId = [[unit.meta objectForKey:@"data-unit"] firstObject];
                if (destinationId.intValue == destination.id.intValue) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        if(style){
                            if([style objectForKey:@"alpha"]){
                                [unit setStrokeOpacity:@([[style objectForKey:@"alpha"] doubleValue])];
                                [unit setOpacity:[[style objectForKey:@"alpha"] doubleValue]];
                                
                                
                            }
                            if([style objectForKey:@"color"]){
                                [unit setFillColor:[JibestreamConverter colorFromHexString:[style objectForKey:@"color"]].CGColor];
                            }
                        }
                    });
                    
                }
            }
        }
    }];
};

RCT_EXPORT_METHOD(styleShape:(nonnull NSNumber *) reactTag
                  destination:(nonnull NSNumber *) sid
                  style:(NSDictionary *) style)
{
};

RCT_EXPORT_METHOD(setMapOptions:(nonnull NSNumber *) reactTag
                  options:(NSDictionary *) options)
{
    [self updateMapOptions:options];
};

RCT_EXPORT_METHOD(addMapIcon:(nonnull NSNumber *) reactTag
                  coordinates:(NSArray *) coordinates
                  coordType:(NSString *) coordType
                  icon:(NSDictionary *) icon){
    
    CGPoint convertPoint = CGPointMake([coordinates[0] intValue], [coordinates[1] intValue]);
    
    if([coordType isEqualToString:@"LAT_LONG"]){
        convertPoint = [JMapUtils convertPoint:convertPoint fromProjection:@"EPSG:4326" toProjection:@"jmap:local" inVenue:self.controller.activeVenue];
    }
    
    UIImage * image = [JibestreamConverter imageFromDict:icon];
    
    if(image){
        [self drawImageAtPoint:icon point:convertPoint];
    }else{
        NSLog(@"Cannot Draw Empty Image");
    }
};

    
RCT_EXPORT_METHOD(setMapViewBounds:(nonnull NSNumber *) reactTag
                  options:(NSDictionary *) options){
    
    if(options){
        NSNumber * x1 = [options objectForKey:@"topLeft"][0];
        NSNumber * y1 = [options objectForKey:@"topLeft"][1];
        NSNumber * x2 = [options objectForKey:@"bottomRight"][0];
        NSNumber * y2 = [options objectForKey:@"bottomRight"][1];
        NSNumber * rotation = [options objectForKey:@"rotation"];
        NSNumber * animationDuration = [options objectForKey:@"animationDuration"];
        NSString * format = [options objectForKey:@"format"];
        
        CGPoint convertPoint1 = CGPointMake(x1.floatValue, y1.floatValue);
        CGPoint convertPoint2 = CGPointMake(x2.floatValue, y2.floatValue);
        if([format isEqualToString:@"LAT_LONG"]){
            convertPoint1 = CGPointMake(y1.floatValue, x1.floatValue);
            convertPoint1 = [JMapUtils convertPoint:convertPoint1 fromProjection:@"EPSG:4326" toProjection:@"jmap:local" inVenue:self.controller.activeVenue];
            
            convertPoint2 = CGPointMake(y2.floatValue, x2.floatValue);
            convertPoint2 = [JMapUtils convertPoint:convertPoint2 fromProjection:@"EPSG:4326" toProjection:@"jmap:local" inVenue:self.controller.activeVenue];
        }else if([format isEqualToString:@"X_Y"]){
            
        }else{
            return;
        }
        
        if(rotation == nil){
            rotation = @0;
        }
        
        rotation = @(-1 * rotation.floatValue);
        
        if(animationDuration == nil){
            animationDuration = @1000;
        }
        
        float centerX = (convertPoint1.x + convertPoint2.x) / 2.0;
        float centerY = (convertPoint1.y + convertPoint2.y) / 2.0;
        float width = fabs(convertPoint1.x - convertPoint2.x);
        float height = fabs(convertPoint1.y - convertPoint2.y);
        
        
        [self zoomMapToBounds:centerX centerY:centerY width:width height:height rotation:rotation.floatValue animationDuration:animationDuration.floatValue];
    }else{
        NSLog(@"Options is Null. Cannot center map on point");
    }
}

RCT_EXPORT_METHOD(setMapViewToPath:(nonnull NSNumber *) reactTag
                  options:(NSDictionary *) options){
    if(options){
        NSNumber * margin = [options objectForKey:@"margin"];
        NSNumber * rotation = [options objectForKey:@"rotation"];
        NSNumber * animationDuration = [options objectForKey:@"animationDuration"];
        
        if(rotation == nil){
            rotation = @0;
        }
        if(animationDuration == nil){
            animationDuration = @1000;
        }
        
        [self zoomMapToPath:margin.floatValue rotation:(-1 *rotation.floatValue) animationDuration: animationDuration.floatValue];
    } else{
        NSLog(@"Options was null, cannot set Map view to Path");
    }
}


double distanceBetweenPoints(JMapWaypoint * point1, JMapPoint * point2) {
    double deltaX = point2.x - point1.coordinates[0].floatValue;
    double deltaY = point2.y - point1.coordinates[1].floatValue;
    return sqrt(deltaX * deltaX + deltaY * deltaY);
}

// Comparator function for sorting waypoints based on distance from provided point
NSComparisonResult compareWaypoints(JMapWaypoint *waypoint1, JMapWaypoint *waypoint2, JMapPoint * targetPoint) {
    double distance1 = distanceBetweenPoints(waypoint1, targetPoint);
    double distance2 = distanceBetweenPoints(waypoint2, targetPoint);
    
    if (distance1 < distance2) {
        return NSOrderedAscending;
    } else if (distance1 > distance2) {
        return NSOrderedDescending;
    } else {
        return NSOrderedSame;
    }
}

// Function to sort waypoints by distance from provided point
NSArray *sortWaypointsByDistance(JMapPoint * targetPoint , NSArray *waypoints) {
    NSArray *sortedWaypoints = [waypoints sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        JMapWaypoint *waypoint1 = (JMapWaypoint *)obj1;
        JMapWaypoint *waypoint2 = (JMapWaypoint *)obj2;
        
        return compareWaypoints(waypoint1, waypoint2, targetPoint);
    }];
    
    return sortedWaypoints;
}

- (void) zoomMapToPath:(float) padding rotation:(float) rotation animationDuration:(float) animationDuration{
    // CGRect rect = [self.controller getBoundsOfPathOnMap:self.controller.currentMap];
    
    NSArray<JMapPathPerFloor*>* paths = [generatedPaths objectForKey:navigationPath];
    
    float top = self.controller.currentMap.height.floatValue;
    float right = 0;
    float bottom = 0;
    float left = self.controller.currentMap.width.floatValue;
    
    
    if([paths count] >0){
        for(NSUInteger i=0; i< [paths[0].mapPoints count]; i++){
            JMapPoint * jMapPoint = paths[0].mapPoints[i];
            
            if(jMapPoint.x > right){
                right = jMapPoint.x;
            }
            if(jMapPoint.x < left){
                left = jMapPoint.x;
            }
            if(jMapPoint.y > bottom){
                bottom = jMapPoint.y;
            }
            if(jMapPoint.y < top){
                top = jMapPoint.y;
            }
        }
    }else{
        printf("Cannot Zoom to Path, Path is not Drawn yet\n");
        return;
    }
    
    float width = fabs(right - left);
    float height = fabs(bottom - top);

    float centerX = left + width / 2.0;
    float centerY = top + height / 2.0;

    width = width * (1+padding);
    height = height * (1+padding);

    
    [self zoomMapToBounds:centerX centerY:centerY width:width height:height rotation:rotation animationDuration:animationDuration];
    
}
    
    
- (void) zoomMapToBounds:(float) centerX centerY:(float) centerY width:(float) width height:(float) height rotation:(float) rotation animationDuration:(float) animationDuration{
        
    float den = [self max:width b:height];
    float desiredZoom = [self min:self.canvas.frame.size.width b:self.canvas.frame.size.height] / den;
    
    
    JMapTransform *transform = [[JMapTransform alloc] initWithX:@(centerX) y:@(centerY) scale:@(desiredZoom) rotation:@(rotation) atCenter:nil duration:(animationDuration / 1000)];
    
    [self.controller setMapTransform:transform];
    
    
}
    
- (float) min:(float) a b:(float) b {
    if(a > b){
        return b;
    } else{
        return a;
    }
}
    
- (float) max:(float) a b:(float) b {
    if(a > b){
        return a;
    } else{
        return b;
    }
}

- (void) updateMapOptions:(NSDictionary * ) options{
    
    if(options){
        mapOptions = options;
        NSDictionary * userOptions = [options objectForKey:@"user"];
        NSDictionary * userCircle = [options objectForKey:@"userCircle"];
        NSDictionary * snap = [options objectForKey:@"snap"];
        NSDictionary * navOptions = [options objectForKey:@"navigation"];
        
        [self setUserCircleOptions:userCircle];
        [self setSnapOptions:snap];
        [self setUserOptions:userOptions];
        [self setNavigationOptions:navOptions];
        
        
    }
}

- (void) setUserOptions:(NSDictionary *) userOptions{
    NSString * userImageString = [userOptions objectForKey:@"iconUrl"];
    if(userImageString.length >0){
        NSData * imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:userImageString]];
        dispatch_async(dispatch_get_main_queue(), ^{
            
            UIImage * newImage = [UIImage imageWithData: imageData];
            arrowIcon = newImage;
            [self.controller.userLocation setImage:arrowIcon];
        });
    }
    
    float userSpeed = [[userOptions objectForKey:@"maxSpeed"]floatValue];
    [self.controller.userLocation setSpeed:userSpeed];
    [self.controller.userLocation setRotateWithMap:TRUE];
}

- (void) setUserCircleOptions:(NSDictionary *) userCircle{
    
    //    NSDictionary * userCircle = [options objectForKey:@"userCircle"];
    
    NSNumber * delayBetweenPulses = [userCircle objectForKey:@"delayBetweenPulses"];
    NSNumber * pulseAnimationDuration = [userCircle objectForKey:@"pulseAnimationDuration"];
    NSString * confidenceColor = [userCircle objectForKey:@"confidenceColor"];
    NSNumber * confidenceOpacity = [userCircle objectForKey:@"confidenceOpacity"];
    NSNumber * confidenceRadius = [userCircle objectForKey:@"confidenceRadius"];
    
    if(delayBetweenPulses){
        [self.controller.userLocation setDelayBetweenPulseCycles:[delayBetweenPulses doubleValue]];
    }
    if(pulseAnimationDuration){
        [self.controller.userLocation setPulseAnimationDuration:[pulseAnimationDuration doubleValue]];
    }
    if(confidenceColor){
        [self.controller.userLocation setConfidenceColor:[JibestreamConverter colorFromHexString:confidenceColor]];
        [self.controller.userLocation setPulseColor:[JibestreamConverter colorFromHexString:confidenceColor]];
        
    }
    if(confidenceOpacity){
        [self.controller.userLocation setConfidenceAlpha:[confidenceOpacity doubleValue]];
    }
    if(confidenceRadius){
        [self.controller.userLocation setConfidenceRadius:[confidenceRadius doubleValue]];
    }
}

- (void) setSnapOptions:(NSDictionary *) snapOptions{
    
    bool pathShouldSnap = [snapOptions objectForKey:@"path"];
    bool waypointShouldSnap = [snapOptions objectForKey:@"waypoint"];
    bool gridShouldSnap = [snapOptions objectForKey:@"gridShouldSnap"];
    
    
    if(pathShouldSnap){
        double pathThreshold = [[snapOptions objectForKey:@"pathThreshold"] doubleValue];
        [self.controller.userLocation snapToWayfindingPathWithThreshold:pathThreshold];
    }else if(waypointShouldSnap){
        [self.controller.userLocation setSnapToWaypoint:true];
    }else if(gridShouldSnap){
        [self.controller.userLocation setSnapToGrid:true];
    }else{
        [self.controller.userLocation setSnapToGrid:false];
        [self.controller.userLocation setSnapToWaypoint:false];
    }
}

- (void) setNavigationOptions:(NSDictionary *) navOptions{
    bool autoReroute = [navOptions objectForKey:@"autoReroute"];
    
    if(autoReroute){
        float autoRerouteThreshold = [[navOptions objectForKey:@"autoRerouteThreshold"] floatValue];
        //        [self.controller.userLocation shouldAutoRerouteWayfindingPath];
        //        [self.controller.userLocation autoRerouteWayfindingPathWithThreshold:autoRerouteThreshold];
    }
}


- (void) centerMapOnPosition:(NSNumber *) x y:(NSNumber *) y zoom:(NSNumber *) zoom orientation:(NSNumber *)orientation animationDuration:(NSNumber *) animationDuration {
    
    dispatch_async(dispatch_get_main_queue(), ^{
        
        JMapTransform *transform = [[JMapTransform alloc] initWithX:x y:y scale:zoom rotation:orientation atCenter:nil duration:(animationDuration.floatValue / 1000.0)];
        [self.controller setMapTransform:transform];
    });
}

- (NSDictionary *) generateBestWayfindingPathFromUser:(JMapWaypoint *) destinationWaypoint userLocation:(JMapPoint *) userLocation{
    NSArray * orderedWaypoints = sortWaypointsByDistance(userLocation, self.controller.currentMap.waypoints.getAll);
    
    int count = 0;
    for(JMapWaypoint * waypoint in orderedWaypoints){
        NSDictionary * path = [self generateWayfindingPath:waypoint destinationWaypoint:destinationWaypoint];
        
        if(path != NULL && [path[@"jibestreamPath"] count] > 0){
            return path;
        }
        if (count > 3){
            break;
        }
        count +=1;
    }
    return NULL;
    
}


- (NSDictionary *) generateWayfindingPath:(JMapWaypoint *) startWaypoint destinationWaypoint:(JMapWaypoint *) destinationWaypoint{
    
    if (startWaypoint && destinationWaypoint) {
        NSArray<JMapPathPerFloor*>* paths = [self.controller wayfindBetweenWaypoint:startWaypoint andWaypoint:destinationWaypoint withAccessibility:100 withObstacle:nil];
        
        NSString *key = [@"" stringByAppendingFormat:@"%@_%@", [startWaypoint.id stringValue], [destinationWaypoint.id stringValue]];
        
        NSArray <JMapInstruction *>* instructions = [self.navKit createInstructionsFromPaths:paths];
        
        NSMutableDictionary * processedInstructions = [self processInstructions:instructions paths:paths];
        
        NSMutableArray *pathArray = [NSMutableArray array];
        
        
        for (JMapPathPerFloor *pathPerFloor in paths){
            NSDictionary *pathPerFloorDict = [JibestreamConverter jMapPathPerFloorToDict:pathPerFloor jMapController:self.controller];
            [pathArray addObject:pathPerFloorDict];
        }
        
        
        NSDictionary *pathParameters = @{
            @"pathId": key,
            @"instructions": ObjectOrNull(processedInstructions),
            @"start": [JibestreamConverter jMapWaypointToDict:startWaypoint],
            @"end": [JibestreamConverter jMapWaypointToDict:destinationWaypoint],
            @"jibestreamPath": pathArray,
            @"path": ObjectOrNull(processedInstructions)
        };
        
        generatedPaths[key] = paths;
        return pathParameters;
    }
    
    return NULL;
}



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


- (void)jmapInitialized:(JMapError *)error {
    if (error) {
        NSLog(@"Error: %@", error.message);
    } else {

        
        self.controller = _jMap.controller;
        self.jCore = _jMap.core;
        
        
        _navKit = [[JMapNavigation alloc] initWithController:self.controller andOptions:@{@"layersOfInterest" : @[@"Units"]}];
        [self sendDestinations];
        [self createAmenityMapping];
        
        NSNumber * confidenceRadius = [[mapOptions objectForKey:@"userCircle"] objectForKey:@"confidenceRadius"];
        
        [self.controller updateUserLocation:CGPointMake(self.controller.currentMap.width.floatValue/2.0, self.controller.currentMap.height.floatValue/2.0) floorMap:self.controller.currentMap orientation:@(0) confidenceRadius:confidenceRadius];
        [self.controller.userLocation setImage:arrowIcon];
        
        
        [self setUserOptions:[mapOptions objectForKey:@"user"]];
        [self setUserCircleOptions:[mapOptions objectForKey:@"userCircle"]];
        [self setSnapOptions:[mapOptions objectForKey:@"snap"]];
        [self setNavigationOptions:[mapOptions objectForKey:@"navigation"]];
        
        [self setLayersInteractive];
        
        self.geofenceKit = [[JMapGeofence alloc] initWithCore:self.jCore andController:self.controller];
        self.geofenceKit.delegate = self;
        [self showGeofences];
        
        
    }
}

- (void) wayfindDataLoaded{
    [eventEmitter sendEvent:@"VENUE_LOADED" body:self.controller.activeVenue.id ];
}

- (void) showGeofences {
    [self.geofenceKit getGeofences:^(JMapError * _Nullable error) {
                // NSLog(@"\n\n\n\n\nIterating over GeoFences\n\n\n\n");
                // dispatch_async(dispatch_get_main_queue(), ^{
                //     //Loop through each geofence
        
        
        
                //     for (JMapGeofenceObject *geofence in self.geofenceKit.geofences.getAll) {
                //         NSLog(@"Drawing Geofences");
                //         //Draw polygon of geofence
                //         //Note: passing nil as style will use geofence's defined colour on CMS, if none is defined on CMS black colour will be assigned
                //         [self.geofenceKit drawPolygonsOfGeofence:geofence onMap:nil withStyle:nil];
                //     }
                // });

    }];
}

- (void) createAmenityMapping{
    NSArray <JMapAmenity*> *amenities = [self.controller.activeVenue.amenities getAll];
    for (JMapAmenity *amenity in amenities){
        [amenityMapping setValue:amenity.id forKey:amenity.name];
    }
}

-(void) sendDestinations{
    NSArray *destinationsAll = [self.controller.activeVenue.destinations getAll];
    
    NSMutableArray *encodedDestinations = [NSMutableArray array];
    for (JMapDestination *dest in destinationsAll){
        
        NSDictionary *destinationData = [JibestreamConverter jMapDestinationToDict:dest];
        [encodedDestinations addObject:destinationData];
    }
    
    [eventEmitter sendEvent:@"GENERATED_SEARCH_DESTINATIONS" body: encodedDestinations];
}

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

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




- (NSMutableDictionary *) processInstructions:(NSArray *) instructions paths:(NSArray<JMapPathPerFloor*>*)paths{
    NSMutableArray *processedInstructions = [NSMutableArray array];

    
    float totalDistance = 0;
    float totalDuration = 0;
    
    

    NSMutableArray *steps = [NSMutableArray array];
    NSMutableArray *longLatPoses = [NSMutableArray array];
    
    bool firstInstruction = true;
    
    if(paths.count >0){
        if(paths[0].points.count > 0){
            CGPoint longLat;
            longLat = [self xyToLongLatWithX:paths[0].points[0].x.floatValue y:paths[0].points[0].y.floatValue];
            NSArray * longLatArray = @[@(longLat.x), @(longLat.y)];
            [longLatPoses addObject:longLatArray];
        }
    }
    
    NSNumber * doorAmenityId = 0;
    if([amenityMapping objectForKey:@"Door"]){
        doorAmenityId = [amenityMapping objectForKey:@"Door"];
    }else{

    }
    
    
    for (JMapInstruction * instruction in instructions) {
        
        NSMutableDictionary * pInstruction = [NSMutableDictionary dictionary];
        
        NSMutableDictionary * step = [NSMutableDictionary dictionary];
        
        
        CGPoint longLat;
        
        
        if (instruction.decisionPoint != nil) {
            pInstruction[@"wp"] = instruction.decisionPoint.id;
            longLat = [self xyToLongLatWithX:instruction.decisionPoint.coordinates[0].floatValue y:instruction.decisionPoint.coordinates[1].floatValue];
            
        } else{
            pInstruction[@"wp"]  = instruction.completionPoint.id;
            longLat = [self xyToLongLatWithX:instruction.completionPoint.coordinates[0].floatValue y:instruction.completionPoint.coordinates[1].floatValue];
        }
        
        step[@"lon"] = @(longLat.x);
        step[@"lat"] = @(longLat.y);
        NSArray * longLatArray = @[@(longLat.x), @(longLat.y)];
//        [steps addObject:longLatArray];
        [longLatPoses addObject:longLatArray];
        
        float distance = instruction.pixelDistance * self.controller.currentMap.mmPerPixel.floatValue / 1000.0;
        float distanceFeet = distance * 3.28084;
        
        pInstruction[@"distance"] = @(distance);
        pInstruction[@"remainingDistance"] = @(distance);
        step[@"distance"] = @(distance);
        totalDistance += distance;
        
        
        NSString *units = @"meters";
        
        NSString *freedomUnits = @"feet";
        
        if(ceil(distance) ==1){
            units = @"meter";
        }
        
        if(ceil(distanceFeet) == 1){
            freedomUnits = @"foot";
        }
        
        pInstruction[@"direction"] = instruction.direction;
        step[@"direction"] = instruction.direction;
        
        NSString * prepend = @"";
        NSString * amenityPrepend = @"";
        
        pInstruction[@"door"] = false;
        if(instruction.decisionPoint.associations.amenities.count >0){
            for(NSNumber * amenityId in instruction.decisionPoint.associations.amenities){
                if(amenityId == doorAmenityId){
                    pInstruction[@"door"] = @(TRUE);
                    prepend = @"Open the Door and ";
                    amenityPrepend = @"Open the Door and ";
                }
            }
        }
        
        if([instruction.direction isEqualToString:@"Left"] || [instruction.direction isEqualToString:@"Right"]){
            prepend = [prepend stringByAppendingString:@"Turn "];
        }
        pInstruction[@"prepend"] = prepend;
        step[@"prepend"] = prepend;
        
        
        
        if (instruction.surroundingElements &&
            instruction.surroundingElements.shapes &&
            instruction.surroundingElements.shapes.count > 0){
            
            NSArray * dataUnits = instruction.surroundingElements.shapes[0].shape.meta[@"data-unit"];
            
            
            if(dataUnits && dataUnits.count >0){
                NSNumber * destinationId = dataUnits[0];
                
                JMapDestination * dest = [self.controller.activeVenue.destinations getById:destinationId.intValue];
                
                NSString * destinationName = dest.name;
                
                if(destinationName){
                    pInstruction[@"destinationName"] = destinationName;
                    pInstruction[@"dest"] = destinationId;
                    pInstruction[@"fullText"] = [NSString stringWithFormat:@"%@ %@ at %@ in %@ %@",amenityPrepend, instruction.text, destinationName, [@(ceil(distance)) stringValue], units];
                    
                    step[@"directions"] = [NSString stringWithFormat:@"%@%@ at %@ in %@ %@",amenityPrepend, instruction.text, destinationName, [@(ceil(distanceFeet)) stringValue], freedomUnits];
                }else{
                    pInstruction[@"dest"] = destinationId;
                    pInstruction[@"fullText"] = [NSString stringWithFormat:@"%@ %@ in %@ %@",amenityPrepend, instruction.text, [@(ceil(distance)) stringValue], units];
                    step[@"directions"] = [NSString stringWithFormat:@"%@%@ in %@ %@",amenityPrepend, instruction.text, [@(ceil(distanceFeet)) stringValue], freedomUnits];
                }
            }else{
                pInstruction[@"destinationName"] = @"";
                pInstruction[@"dest"] = ObjectOrNull(nil);
                pInstruction[@"fullText"] = [NSString stringWithFormat:@"%@ %@ in %@ %@",amenityPrepend, instruction.text, [@(ceil(distance)) stringValue], units];
                step[@"directions"] = [NSString stringWithFormat:@"%@%@ in %@ %@",amenityPrepend, instruction.text, [@(ceil(distanceFeet)) stringValue], freedomUnits];
            }
            
            
        }
        
        NSArray *points = @[];//@[@0, @0];
        
        pInstruction[@"startPoint"] = points;
        pInstruction[@"endPoint"] = points;
        
        [processedInstructions addObject:pInstruction];
        [steps addObject:step];
    }
    
    NSDictionary *geometry = @{
        @"type": @"LineString",
        @"coordinates": longLatPoses
    };
    
    NSDictionary *properties = @{
        @"name": @"wayfind-path-geojson"
    };
        
        
        
    NSDictionary *lineString = @{
        @"type": @"Feature",
        @"geometry": geometry,
        @"properties": properties
    };
        
    
    
    // geojson[@"distance"] = @(totalDistance);
    // geojson[@"duration"] = @(totalDistance / 1.18);
    // geojson[@"startTime"] = @([self getSystemTimeMillis]);
    // geojson[@"endTime"] = @([self getSystemTimeMillis]);
    // geojson[@"legGeometry"] = lineString;
    // geojson[@"steps"] = steps;
    
    //NSArray * result = @[processedInstructions;
    
    return processedInstructions;
}

- (void)canvas:(nonnull JMapCanvas *)canvas detectedTapOnElement:(nullable id)element atPoint:(CGPoint)point{
    if ([element isKindOfClass:[Shape class]]) {
        Shape * unit = element;
        
        if ([unit.meta objectForKey:@"data-unit"]) {
            
            
            // retrieve the destination ID of the shape from the data-unit meta data
            NSArray * data = [unit.meta objectForKey:@"data-unit"];
            for(NSNumber * destId in data){
                JMapDestination * destination = [self getDestinationById:destId];
                
                if(destination){
                    NSDictionary *tapLocation = @{
                        @"x": ObjectOrNull(@(point.x)),
                        @"y": ObjectOrNull(@(point.y)),
                        @"destination": [JibestreamConverter jMapDestinationToDict:destination],
                    };
                    
                    [eventEmitter sendEvent:@"MAP_TAPPED_GENERIC" body: tapLocation];
                    break;
                }
            }
            
            NSDictionary *tapLocation = @{
                @"x": ObjectOrNull(@(point.x)),
                @"y": ObjectOrNull(@(point.y)),
            };
            
            
        }
    }else{
        NSDictionary *tapLocation = @{
            @"x": ObjectOrNull(@(point.x)),
            @"y": ObjectOrNull(@(point.y))
        };
        [eventEmitter sendEvent:@"MAP_TAPPED_GENERIC" body: tapLocation];
    }
}

- (void)setLayersInteractive {
    
    // array of layer names
    NSArray * layerNames = @[@"units", @"iconsLayer"];
    
    // Iterate through all namees in the layerNames array
    for (NSString * layerName in layerNames) {
        
        // Enable interactivity for layer with name
        [self.controller enableLayerInteractivityForLayerName:layerName];
    }
}

- (void)canvas:(nonnull JMapCanvas *)canvas scrollViewDidRotateByDegrees:(float)degrees {
    NSDictionary *rotationAngle = @{
        @"degrees": ObjectOrNull(@(degrees)),
    };
    mapOrientation = @(degrees);
    [eventEmitter sendEvent:@"SCREEN_ROTATED" body: rotationAngle];
}

- (void)canvas:(nonnull JMapCanvas *)canvas scrollViewDidZoom:(float)zoomScale {
    mapZoom = @(zoomScale);
}


- (float) getAngleBetweenPoints:(CGPoint) startPoint endPoint:(CGPoint) endPoint{
    
    float theta = atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x) + M_PI / 2.0;
    
    double angle = theta / M_PI * 180;
    
    if (angle < 0){
        angle +=360;
    }
    return angle;
    
}

- (float) getDistanceBetweenPoints:(CGPoint) startPoint endPoint:(CGPoint) endPoint{
    
    return sqrtf(pow(startPoint.x - endPoint.x,2) + pow(startPoint.y - endPoint.y,2));
}

- (JMapDestination *) getDestinationById:(NSNumber * ) destId{
    NSArray *destinationsAll = [self.controller.activeVenue.destinations getAll];
    
    NSMutableArray *encodedDestinations = [NSMutableArray array];
    for (JMapDestination *dest in destinationsAll){
        if([dest.id intValue] == [destId intValue]){
            return dest;
        }
    }
    return nil;
}

- (void)geofencesEnteredByMovingObject:(JMapMovingObject *)movingObject geofences:(NSArray<JMapGeofenceInstance *> *)geofences {
    if (movingObject == self.controller.userLocation) {
        NSMutableDictionary * geofenceDict = [JibestreamConverter jMapGeofenceArrayToDict:geofences];
        
        geofenceDict[@"entered"] = @(YES);
        [eventEmitter sendEvent:@"GEOFENCES_CHANGED" body: geofenceDict];
        
    }
}

- (void)geofencesExitedByMovingObject:(JMapMovingObject *)movingObject geofences:(NSArray<JMapGeofenceInstance *> *)geofences {
    if (movingObject == self.controller.userLocation) {
        NSMutableDictionary * geofenceDict = [JibestreamConverter jMapGeofenceArrayToDict:geofences];
        
        geofenceDict[@"entered"] = @(NO);
        [eventEmitter sendEvent:@"GEOFENCES_CHANGED" body: geofenceDict];
    }
}
//
//- (UIImage *)imageWithOrientation:(UIImageOrientation)orientation {
//    CGImageRef imageRef = self.CGImage;
//
//    return [UIImage imageWithCGImage:imageRef
//                               scale:self.scale
//                         orientation:orientation];
//}

- (void)drawImageAtPoint:(NSDictionary *)originImage point:(CGPoint) point{
    UIImage * image = [JibestreamConverter imageFromDict:originImage];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        
        int width = [[originImage objectForKey:@"iconWidth"] intValue];
        int height = [[originImage objectForKey:@"iconHeight"] intValue];
        
        int offsetX = [[originImage objectForKey:@"iconOffsetX"] intValue];
        int offsetY = [[originImage objectForKey:@"iconOffsetY"] intValue];
        
        CGSize newSize = CGSizeMake(width, height); // Specify the new width and height
        
        // Create a new graphics context with the new size
        UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
        
        // Draw the original image into the new context
        [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
        
        // Get the resized image from the context
        UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
        
        // End the graphics context
        UIGraphicsEndImageContext();
        
        
        
        UIImageView * imageView = [[UIImageView alloc] initWithImage:resizedImage];
        [imageView setBounds:CGRectMake(0, 0, width, height)];
        
//        imageView.contentMode = UIViewContentModeScaleAspectFit; // You can choose an appropriate content mode
//        imageView.contentMode = UIViewContentModeTopLeft;
//        imageView.contentMode = UIViewContentModeRedraw;
        imageView.contentMode = UIViewContentModeCenter;
        
        CGRect frame = CGRectMake(offsetX, offsetY, newSize.width + abs(offsetX)*2, newSize.height + abs(offsetY)*2); // Replace xOffset and yOffset with your desired values
        imageView.frame = frame;
        
        [self.controller addComponent:imageView toMap:self.controller.currentMap atPoint:point rotateWithMap:NO scaleWithMap:NO];
        [images addObject:imageView];
    });
}


- (CGPoint)xyToLongLatWithX:(float)x y:(float)y {
    CGPoint convertPoint = CGPointMake(x, y);
    convertPoint = [JMapUtils convertPoint:convertPoint fromProjection:@"jmap:local" toProjection:@"EPSG:4326" inVenue:self.controller.activeVenue];
    return convertPoint;
}

@end






