//
// ScheduleDriverLocationViewController.m
// Project
//
// Created by ziv on 2017/5/27.
//
//

#import "ScheduleDriverLocationViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#import "Lang.h"
#import "SDK.h"
#import "BeanUtils.h"
#import "CircularRegion.h"
#import "CocoaUtils.h"
#import "DrawUtils.h"
#import "PointAnnotation.h"
#import "TextViewController.h"
#import "UIToolkit.h"
#import "SMMapView.h"
#import <AFNetworking/AFNetworkReachabilityManager.h>
#import <BlocksKit/BlocksKit.h>
#import <BlocksKit/BlocksKit+UIKit.h>
#import <libextobjc/EXTScope.h>
#import <MZFormSheetPresentationController/MZFormSheetPresentationController.h>
#import <MZFormSheetPresentationController/MZFormSheetPresentationViewController.h>
#import <PureLayout/PureLayout.h>
#import <SocketRocket/SocketRocket.h>
#import "Utility.h"
#import "ScheduleStop.h"
#import "BusStatusApi.h"
#import "BusStatusDetailResponseJsonMapper.h"
#import "BusStatusDetailStopCell.h"
#import "UIViewController+Privilege.h"
#import "BusStatusDetailViewController.h"
#import "SchedulePassengerListViewController.h"
#import "BusStatusDetailStopView.h"
#import "Vars.h"

@interface ScheduleDriverLocationViewController () <MKMapViewDelegate, CLLocationManagerDelegate, SRWebSocketDelegate>

@property (nonatomic, weak) IBOutlet SMMapView *mapView;
@property (nonatomic, weak) IBOutlet BusStatusDetailStopView *cellFooterView;
@property (nonatomic, weak) IBOutlet UIView *mapToolbarView;
@property (nonatomic, weak) IBOutlet UIButton *refreshButton;
@property (nonatomic, weak) IBOutlet UIButton *userTrackingButton;

@property (nonatomic, weak) IBOutlet NSLayoutConstraint *separatorViewHeightConstraint;

@property (nonatomic, strong) CLLocationManager *locationManager;
@property (nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
@property (nonatomic, strong) SRWebSocket *webSocket;
@property (nonatomic, assign) BOOL networkReachability;

@property (nonatomic, assign) BOOL shouldTerminated;
@property (nonatomic, assign) BOOL willDetermined;
@property (nonatomic, assign) BOOL didDetermined;
@property (nonatomic, assign) NSInteger atomicInteger;
@property (nonatomic, strong) CLLocation *uploadedLocation;
@property (nonatomic, strong) CLLocation *monitoredLocation;
@property (nonatomic, strong) BusStatusDetailResponse *busStatusDetailResponse;

@property (nonatomic, strong) NSArray<ScheduleStop *> *displayStops;
@property (nonatomic, strong) ScheduleStop *fromStop;
@property (nonatomic, strong) ScheduleStop *toStop;
@property (nonatomic, strong) NSArray<CircularRegion *> *hundredMetersRegions;
@property (nonatomic, strong) NSArray<CircularRegion *> *kilometerRegions;

@property (nonatomic, strong) NSMutableString *consoleLog;

@property (nonatomic, strong, readonly) NSArray<ScheduleStop *> *scheduleStops;

@end

@implementation ScheduleDriverLocationViewController

- (instancetype)init {
	if (self = [super init]) {
		self.edgesForExtendedLayout = UIRectEdgeNone;
		self.title = @"Map";
		self.hidesBottomBarWhenPushed = YES;
		[[Vars appDelegate] applyDefaultConfigurationForViewController:self];
	}
	return self;
}

- (void)viewDidLoad {
	[super viewDidLoad];

	[UIApplication sharedApplication].idleTimerDisabled = YES;

	UIBarButtonItem *listItem = [[UIBarButtonItem alloc] initWithImage:[DrawUtils imageWithFAKIcon:[FAKFontAwesome listIconWithSize:24] fillColor:[UIColor whiteColor]] style:UIBarButtonItemStylePlain target:self action:@selector(performSegueForDetails)];
#if DEBUG
	self.consoleLog = [NSMutableString string];
	UIBarButtonItem *logItem = [[UIBarButtonItem alloc] initWithTitle:@"Log" style:UIBarButtonItemStylePlain target:self action:@selector(performSegueForConsoleLog)];
	self.navigationItem.rightBarButtonItems = @[listItem, logItem];
#else
	self.navigationItem.rightBarButtonItem = listItem;
#endif

	@weakify(self)
	self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
	[self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
		@strongify(self)
		if (status > AFNetworkReachabilityStatusNotReachable) {
			if (!self.networkReachability) {
				[self tryOutWebSocket];
				self.networkReachability = YES;
			}
		} else {
			if (self.networkReachability) {
				// [self closeWebSocket];
				self.networkReachability = NO;
			}
		}
	}];
	[self.reachabilityManager startMonitoring];

	if ([CLLocationManager locationServicesEnabled]) {
		self.locationManager = [[CLLocationManager alloc] init];
		self.locationManager.delegate = self;
		self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
		self.locationManager.distanceFilter = kCLDistanceFilterNone;
		self.locationManager.activityType = CLActivityTypeOther;
		self.locationManager.pausesLocationUpdatesAutomatically = NO;
		if ([CocoaUtils allowsBackgroundLocationUpdates] && [self.locationManager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
			self.locationManager.allowsBackgroundLocationUpdates = YES;
		}
		CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
		if (status == kCLAuthorizationStatusNotDetermined) {
			if ([CocoaUtils allowsBackgroundLocationUpdates]) {
				[self.locationManager requestAlwaysAuthorization];
			} else {
				[self.locationManager requestWhenInUseAuthorization];
			}
		} else {
			[self locationManager:self.locationManager didChangeAuthorizationStatus:status];
		}
	} else {
		[CocoaUtils showAlertWithTitle:IMLocalizedString(@"Information") message:IMLocalizedString(@"Please turn on your location services")];
	}

	self.mapView.delegate = self;
	self.mapView.showsCompass = NO;
	self.mapView.showsUserLocation = YES;
	self.mapView.userTrackingMode = MKUserTrackingModeFollow;

	if (YES) {
		self.cellFooterView.backgroundColor = [UIColor whiteColor];
		[Utility addRoundedBorderToButton:self.cellFooterView];
		[DrawUtils addShadowToRect:self.cellFooterView];
		self.cellFooterView.hidesStatusTime = YES;
		self.cellFooterView.showsToolbar = YES;
		self.cellFooterView.showBottomToolbar = NO;
		
        if ([CocoaUtils isIPhoneX]) {
            if (@available(iOS 11.0, *)) {
                [self.cellFooterView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor].active = YES;
            }
        }else{
            [self.cellFooterView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:10];
        }
	}
	if (YES) {
		self.separatorViewHeightConstraint.constant = 1.0 / [UIScreen mainScreen].scale;
		[Utility addRoundedBorderToButton:self.mapToolbarView];
		[self.refreshButton setImage:[DrawUtils imageWithFAKIcon:[FAKIonIcons iosRefreshEmptyIconWithSize:CGRectGetWidth(self.refreshButton.frame)] fillColor:[Vars defaultTintColor]] forState:UIControlStateNormal];
		[self.refreshButton bk_addEventHandler:^(id sender) {
			@strongify(self)
			[self getBusStatusDetailInBackground: NO completion:^{
				if (self.toStop) {
					[self reloadFooterViewWithObject:[self getBusStatusDetailBeanWithScheduleStopId:self.toStop.id]];
				}
			}];
		} forControlEvents:UIControlEventTouchUpInside];
	}
}

- (void)viewWillAppear:(BOOL)animated {
	[super viewWillAppear:animated];

	[CocoaUtils proxyTopMostViewController:self];
	[CocoaUtils proxyAlertControllerAnimated:NO];
}

- (void)viewDidAppear:(BOOL)animated {
	[super viewDidAppear:animated];
	// ajax
	if (!self.busStatusDetailResponse) {
		[self getBusStatusDetailInBackground:NO completion:^{
			if (self.mapView.userLocation) {
				[self determineDataWithLocation:self.mapView.userLocation.location];
			}
		}];
	}
}

- (void)viewDidDisappear:(BOOL)animated {
	[super viewDidDisappear:animated];

	[CocoaUtils proxyTopMostViewController:nil];
	[CocoaUtils proxyAlertControllerAnimated:YES];
}

#pragma mark - Property

- (NSArray *)scheduleStops {
	return [self.busStatusDetailResponse.resultList valueForKeyPath:@"scheduleStop"];
}

#pragma mark - Segue

- (void)performSegueForDetails {
	BusStatusDetailViewController *viewController = [[BusStatusDetailViewController alloc] init];
	viewController.departureDate = self.departureDate;
	viewController.schedule = self.schedule;
	viewController.allowsEditing = YES;
	viewController.isScheduleDriver = YES;

	[self.navigationController pushViewController:viewController animated:YES];
}

- (void)performSegueForPassengerListWithObject:(BusStatusDetailBean *)bean isPickUp:(BOOL)isPickUp isDropOff:(BOOL)isDropOff {
	SchedulePassengerListViewController *viewController = [[SchedulePassengerListViewController alloc] init];
	viewController.service_date = self.departureDate;
	viewController.scheduleStopId = bean.scheduleStop.id;
    viewController.scheduleId = self.schedule.id;
	viewController.isPickUp = isPickUp;
	viewController.isDropOff = isDropOff;
	viewController.allowsCheckIn = YES;

	[self.navigationController pushViewController:viewController animated:YES];
}

- (void)performSegueForConsoleLog {
	TextViewController *viewController = [[TextViewController alloc] init];
	[[Vars appDelegate] applyDefaultConfigurationForViewController:viewController];
	viewController.textView.text = self.consoleLog;
	[self.navigationController pushViewController:viewController animated:YES];
}

#pragma mark - Action

- (IBAction)changeUserTrackingMode:(UIButton *)sender {
	switch (self.mapView.userTrackingMode) {
		case MKUserTrackingModeNone:
			[self.mapView setUserTrackingMode:MKUserTrackingModeFollow animated:YES];
			break;

		case MKUserTrackingModeFollow:
			[self.mapView setUserTrackingMode:MKUserTrackingModeNone animated:YES];
			[self showAnnotationsByDisplayStops];
			break;

		default:
			break;
	}
}

#pragma mark - Network

- (void)getBusStatusDetailInBackground:(BOOL)background completion:(void (^) (void))completion {
	BusStatusDetail *busStatusDetail = [[BusStatusDetail alloc] init];
	busStatusDetail.departureDate = self.departureDate;
	busStatusDetail.scheduleId = self.schedule.id;
	busStatusDetail.editing = YES;

	NSDictionary *requestDict = [BeanUtils describeAccessible:busStatusDetail];

	[Utility performRESTRequestWithMethod:@"POST" url:busStatusDetail.address parameters:requestDict beforeSend:^{
		self.atomicInteger++;
		if (background) {
			[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
		} else {
			[CocoaUtils showBusyIndicator:self.view];
		}
	} success:^(id jsonObject) {
		// NSLog(@"%@", jsonObject);
		BusStatusDetailResponseJsonMapper *mapper = [[BusStatusDetailResponseJsonMapper alloc] init];
		if (![mapper parseWithJSONObject:jsonObject]) {
			BusStatusDetailResponse *response = mapper.response;
			self.busStatusDetailResponse = response;
			if (completion) {
				completion();
			}
		}
	} complete:^{
		if (background) {
			[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
		} else {
			[CocoaUtils hideBusyIndicator:self.view];
		}
		self.atomicInteger--;
	}];
}

- (void)updateBusStatusTimeWithScheduleStopId:(NSInteger)scheduleStopId isArrival:(BOOL)isArrival {
	if (self.shouldTerminated) {
		return;
	}
	BusStatusDetailBean *bean = [self getBusStatusDetailBeanWithScheduleStopId:scheduleStopId];
	if ((isArrival && !bean.scheduleStop.isDropOff) || (!isArrival && !bean.scheduleStop.isPickUp)) {
		return;
	}
	[self.consoleLog appendFormat:@"update time of stop: %@\n\n", bean.scheduleStop.busstation.name];
	UpdateBusStatusTime *updateBusStatusTime = [[UpdateBusStatusTime alloc] init];
	updateBusStatusTime.scheduleId = self.schedule.id;
	updateBusStatusTime.serviceDate = self.departureDate;
	updateBusStatusTime.stopId = scheduleStopId;
	updateBusStatusTime.isArrival = isArrival;
	updateBusStatusTime.dateTime = [[DateFormatUtils ISO_DATETIME_FORMAT] format:[NSDate date]];

	NSDictionary *requestDict = [BeanUtils describeAccessible:updateBusStatusTime];

	[Utility performRESTRequestWithMethod:@"POST" url:updateBusStatusTime.address parameters:requestDict beforeSend:^{
		self.atomicInteger++;
		[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
	} success:^(id jsonObject) {
		// NSLog(@"%@", jsonObject);
	} complete:^{
		[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
		self.atomicInteger--;
	}];
}

- (void)uploadBusStatusLocation:(CLLocation *)location {
	if (!self.fromStop) {
		return;
	}
	[self.consoleLog appendFormat:@"upload location of stop: %@\n\n", self.fromStop.busstation.name];

	NSMutableDictionary *locationDict = [NSMutableDictionary dictionary];
	locationDict[@"latitude"] = @(location.coordinate.latitude);
	locationDict[@"longitude"] = @(location.coordinate.longitude);
	locationDict[@"timestamp"] = [DateFormatUtils format:location.timestamp pattern:@"yyyy-MM-dd'T'HH:mm:ssZZ"];
	NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];
	dataDict[@"scheduleId"] = @(self.schedule.id);
	dataDict[@"departureDate"] = self.departureDate;
	dataDict[@"userId"] = @([Vars appDelegate].loginUser.id);
	dataDict[@"stopId"] = @(self.fromStop.id);
	dataDict[@"location"] = locationDict;
	NSMutableDictionary *dict = [NSMutableDictionary dictionary];
	dict[@"action"] = @"UploadBusStatusLocation";
	dict[@"data"] = dataDict;

	NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:NULL];
#if DEBUG
	NSLog(@"%@ %@", self.webSocket.url, [CocoaUtils jsonStringWithDictionary:dict]);
#endif
	[self sendWebSocketMessage:jsonData];
}

#pragma mark - Private

- (void)determineDataWithLocation:(CLLocation *)location {
	if (!CLLocationCoordinate2DIsValid(location.coordinate)) {
		return;
	}
	if (!self.busStatusDetailResponse) {
		return;
	}
	if (self.shouldTerminated) {
		return;
	}
	if (self.didDetermined) {
		return;
	}
	if (self.willDetermined) {
		return;
	}
	[self.consoleLog appendString:@"determineDataWithLocation\n"];
	self.willDetermined = YES;
	[self addAnnotationsByScheduleStops];
	[self drawDirectionsByScheduleStops];
	NSArray<BusStatusDetailBean *> *assignedBeans = [self.busStatusDetailResponse.resultList bk_select:^BOOL (BusStatusDetailBean *detail) {
		return detail.driverAssigned;
	}];
	if (YES) {
		NSMutableArray *array1 = [NSMutableArray array];
		NSMutableArray *array2 = [NSMutableArray array];
		for (BusStatusDetailBean *bean in assignedBeans) {
			CircularRegion *region1 = [[CircularRegion alloc] initWithCenter:CLLocationCoordinate2DMake(bean.scheduleStop.busstation.latitude, bean.scheduleStop.busstation.longitude) radius:100 identifier:[NSString stringWithFormat:@"%d", bean.scheduleStop.id]];
			region1.notifyOnEntry = NO;
			region1.notifyOnExit = NO;
			region1.tag = bean.scheduleStop.id;
			[array1 addObject:region1];
			CircularRegion *region2 = [[CircularRegion alloc] initWithCenter:CLLocationCoordinate2DMake(bean.scheduleStop.busstation.latitude, bean.scheduleStop.busstation.longitude) radius:200 identifier:[NSString stringWithFormat:@"%d", bean.scheduleStop.id]];
			region2.notifyOnEntry = NO;
			region2.notifyOnExit = NO;
			region2.tag = bean.scheduleStop.id;
			[array2 addObject:region2];
		}
		self.hundredMetersRegions = [NSArray arrayWithArray:array1];
		self.kilometerRegions = [NSArray arrayWithArray:array2];
	}
	NSArray *sortedBeans = [assignedBeans sortedArrayUsingComparator:^NSComparisonResult (BusStatusDetailBean *bean1, BusStatusDetailBean *bean2) {
		CLLocationDistance distance1 = [CocoaUtils distanceFromCoordinate:location.coordinate toCoordinate:CLLocationCoordinate2DMake(bean1.scheduleStop.busstation.latitude, bean1.scheduleStop.busstation.longitude)];
		CLLocationDistance distance2 = [CocoaUtils distanceFromCoordinate:location.coordinate toCoordinate:CLLocationCoordinate2DMake(bean2.scheduleStop.busstation.latitude, bean2.scheduleStop.busstation.longitude)];
		return distance1 - distance2;
	}];
	BusStatusDetailBean *bean = sortedBeans.firstObject;
	[self.consoleLog appendFormat:@"nearest stop: %@\n", bean.scheduleStop.busstation.name];
	if (bean) {
		if (bean.arrivalStopStatus.isPassed || bean.departureStopStatus.isPassed) {
			[self reloadDataToExitWithScheduleStopId:bean.scheduleStop.id];
		} else {
			[self reloadDataToEnterWithScheduleStopId:bean.scheduleStop.id];
		}
	}
	self.didDetermined = YES;
	[self.consoleLog appendString:@"\n"];
}

- (void)reloadFooterViewWithObject:(BusStatusDetailBean *)bean {
	self.cellFooterView.hidden = !bean;

	BusStatusDetailStopView *view = self.cellFooterView;
	view.backgroundColor = bean.isCancelled ? [UIColor lightGrayColor] : [UIColor whiteColor];
	if (YES) {
		UIFont *font1 = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
		UIFont *font2 = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1];
		NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
		[attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:@"Next Stop" attributes:@{NSForegroundColorAttributeName : [UIColor lightGrayColor], NSFontAttributeName : font1}]];
		[attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:@" "]];
		[attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:bean.scheduleStop.busstation.destination.show_name attributes:@{NSFontAttributeName : font1}]];
		[attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:@"\n"]];
		[attributedString appendAttributedString:[[NSAttributedString alloc] im_initWithString:bean.scheduleStop.busstation.name attributes:@{NSFontAttributeName : font2}]];
		view.stopNameLabel.attributedText = attributedString;
	}
	view.scheduledTimeLabel.attributedText = bean.attributedScheduledTimeText;

	view.isPickUp = bean.scheduleStop.isPickUp;
	view.isDropOff = bean.scheduleStop.isDropOff;
	if (bean.scheduleStop.isPickUp) {
		[view.pickUpButton setTitle:[NSString stringWithFormat:@"%d", bean.scheduleStop.pickUpPassengerNum] forState:UIControlStateNormal];
		[view.checkInButton setTitle:[NSString stringWithFormat:@"%d", bean.scheduleStop.checkInPassengerNum] forState:UIControlStateNormal];
		[view.uncheckedButton setTitle:[NSString stringWithFormat:@"%d", bean.scheduleStop.uncheckedPassengerNum] forState:UIControlStateNormal];
	} else {
		[view.pickUpButton setTitle:@"N/A" forState:UIControlStateNormal];
		[view.checkInButton setTitle:@"N/A" forState:UIControlStateNormal];
		[view.uncheckedButton setTitle:@"N/A" forState:UIControlStateNormal];
	}
	if (bean.scheduleStop.isDropOff) {
		[view.dropOffButton setTitle:[NSString stringWithFormat:@"%d", bean.scheduleStop.dropOffPassengerNum] forState:UIControlStateNormal];
	} else {
		[view.dropOffButton setTitle:@"N/A" forState:UIControlStateNormal];
	}
	[view.pickUpButton bk_removeEventHandlersForControlEvents:UIControlEventTouchUpInside];
	[view.checkInButton bk_removeEventHandlersForControlEvents:UIControlEventTouchUpInside];
	[view.uncheckedButton bk_removeEventHandlersForControlEvents:UIControlEventTouchUpInside];
	[view.dropOffButton bk_removeEventHandlersForControlEvents:UIControlEventTouchUpInside];
	@weakify(self)
	if (bean.scheduleStop.isPickUp && bean.scheduleStop.pickUpPassengerNum > 0) {
		[view.pickUpButton bk_addEventHandler:^(id sender) {
			@strongify(self)
			[self performSegueForPassengerListWithObject: bean isPickUp:YES isDropOff: NO];
		} forControlEvents:UIControlEventTouchUpInside];
	}
	if (bean.scheduleStop.isPickUp && bean.scheduleStop.checkInPassengerNum > 0) {
		[view.checkInButton bk_addEventHandler:^(id sender) {
			@strongify(self)
			[self performSegueForPassengerListWithObject: bean isPickUp:YES isDropOff: NO];
		} forControlEvents:UIControlEventTouchUpInside];
	}
	if (bean.scheduleStop.isPickUp && bean.scheduleStop.uncheckedPassengerNum > 0) {
		[view.uncheckedButton bk_addEventHandler:^(id sender) {
			@strongify(self)
			[self performSegueForPassengerListWithObject: bean isPickUp:YES isDropOff: NO];
		} forControlEvents:UIControlEventTouchUpInside];
	}
	if (bean.scheduleStop.isDropOff && bean.scheduleStop.dropOffPassengerNum > 0) {
		[view.dropOffButton bk_addEventHandler:^(id sender) {
			@strongify(self)
			[self performSegueForPassengerListWithObject: bean isPickUp:NO isDropOff: YES];
		} forControlEvents:UIControlEventTouchUpInside];
	}
	
	[view setNeedsLayout];
}

- (void)reloadDataToEnterWithScheduleStopId:(NSInteger)scheduleStopId {
	[self.consoleLog appendString:@"reloadDataToEnterWithScheduleStopId\n"];
	NSArray<BusStatusDetailBean *> *assignedBeans = [self.busStatusDetailResponse.resultList bk_select:^BOOL (BusStatusDetailBean *detail) {
		return detail.driverAssigned;
	}];
	BusStatusDetailBean *bean = [assignedBeans bk_match:^BOOL (BusStatusDetailBean *obj) {
		return obj.scheduleStop.id == scheduleStopId;
	}];
	if (bean) {
		[self.consoleLog appendFormat:@"enter stop: %@\n", bean.scheduleStop.busstation.name];
		NSInteger index = [assignedBeans indexOfObject:bean];
		if (index > 0) {
			BusStatusDetailBean *prevBean = assignedBeans[index - 1];
			self.fromStop = prevBean.scheduleStop;
			self.toStop = bean.scheduleStop;
			[self reloadFooterViewWithObject:bean];
			[self.consoleLog appendFormat:@"prev %@ => %@\n", prevBean.scheduleStop.busstation.name, bean.scheduleStop.busstation.name];
		} else {
			// self.fromStop = nil;
			// self.toStop = bean.scheduleStop;
			// force 0 => 1
			[self.consoleLog appendFormat:@"first stop %@\n", bean.scheduleStop.busstation.name, bean.scheduleStop.busstation.name];
			BusStatusDetailBean *nextBean = assignedBeans[index + 1];
			self.fromStop = bean.scheduleStop;
			self.toStop = nextBean.scheduleStop;
			[self reloadFooterViewWithObject:nextBean];
			[self.consoleLog appendFormat:@"%@ => next %@\n", bean.scheduleStop.busstation.name, nextBean.scheduleStop.busstation.name];
		}
		self.displayStops = @[self.fromStop, self.toStop];
		[self showAnnotationsByDisplayStops];
	}
	[self.consoleLog appendString:@"\n"];
}

- (void)reloadDataToExitWithScheduleStopId:(NSInteger)scheduleStopId {
	[self.consoleLog appendString:@"reloadDataToExitWithScheduleStopId\n"];
	NSArray<BusStatusDetailBean *> *assignedBeans = [self.busStatusDetailResponse.resultList bk_select:^BOOL (BusStatusDetailBean *detail) {
		return detail.driverAssigned;
	}];
	BusStatusDetailBean *bean = [assignedBeans bk_match:^BOOL (BusStatusDetailBean *obj) {
		return obj.scheduleStop.id == scheduleStopId;
	}];
	if (bean) {
		[self.consoleLog appendFormat:@"exit stop: %@\n", bean.scheduleStop.busstation.name];
		NSInteger index = [assignedBeans indexOfObject:bean];
		if (index < assignedBeans.count - 1) {
			BusStatusDetailBean *nextBean = assignedBeans[index + 1];
			self.fromStop = bean.scheduleStop;
			self.toStop = nextBean.scheduleStop;
			self.displayStops = @[self.fromStop, self.toStop];
			[self reloadFooterViewWithObject:nextBean];
			[self showAnnotationsByDisplayStops];
			[self.consoleLog appendFormat:@"%@ => next %@\n", bean.scheduleStop.busstation.name, nextBean.scheduleStop.busstation.name];
		} else {
			[self.consoleLog appendFormat:@"last stop %@\n", bean.scheduleStop.busstation.name, bean.scheduleStop.busstation.name];
			self.shouldTerminated = YES;
			[self terminateAll];
		}
	}
	[self.consoleLog appendString:@"\n"];
}

- (void)showAlertForNextWithScheduleStopId:(NSInteger)scheduleStopId {
	if (YES) {
		MZFormSheetPresentationViewController *viewController = self.mz_formSheetPresentedPresentationController;
		if (viewController) {
			[viewController dismissViewControllerAnimated:NO completion:nil];
		}
	}
	AudioServicesPlaySystemSound(1000);
	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

	NSArray<BusStatusDetailBean *> *assignedBeans = [self.busStatusDetailResponse.resultList bk_select:^BOOL (BusStatusDetailBean *detail) {
		return detail.driverAssigned;
	}];
	BusStatusDetailBean *bean = [assignedBeans bk_match:^BOOL (BusStatusDetailBean *obj) {
		return obj.scheduleStop.id == scheduleStopId;
	}];

	NSMutableAttributedString *content = [[NSMutableAttributedString alloc] init];
	[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:@"You are about to arrive next stop:\n"]];
	[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:[NSString stringWithFormat:@"%@", bean.scheduleStop.busstation.name] attributes:@{NSFontAttributeName : [CocoaUtils renderedFont:[UIFont preferredFontForTextStyle:UIFontTextStyleBody] withSymbolicTraits:UIFontDescriptorTraitBold]}]];
	if (bean.scheduleStop.isDropOff) {
		[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:@"\n"]];
		[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:[NSString stringWithFormat:@"%d", bean.scheduleStop.dropOffPassengerNum] attributes:@{NSForegroundColorAttributeName : [Vars defaultTintColor]}]];
		[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:@" passengers will "]];
		[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:@"drop off" attributes:@{NSForegroundColorAttributeName : [Vars defaultTintColor]}]];
	}
	if (bean.scheduleStop.isPickUp) {
		[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:@"\n"]];
		[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:[NSString stringWithFormat:@"%d", bean.scheduleStop.pickUpPassengerNum] attributes:@{NSForegroundColorAttributeName : [Vars defaultTintColor]}]];
		[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:@" passengers will "]];
		[content appendAttributedString:[[NSAttributedString alloc] im_initWithString:@"pick up" attributes:@{NSForegroundColorAttributeName : [Vars defaultTintColor]}]];
	}

	UIView *contentView = [[UIView alloc] init];
	contentView.backgroundColor = [UIColor whiteColor];
	UILabel *contentLabel = [[UILabel alloc] init];
	contentLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
	contentLabel.numberOfLines = 0;
	contentLabel.attributedText = content;
	[contentView addSubview:contentLabel];
	[contentLabel autoPinEdgesToSuperviewEdgesWithInsets:UIEdgeInsetsMake(15, 15, 15, 15)];
	[NSLayoutConstraint autoSetPriority:UILayoutPriorityRequired - 1 forConstraints:^{
		[contentView autoSetDimension:ALDimensionWidth toSize:CGRectGetWidth([UIScreen mainScreen].bounds) - 15 * 2];
	}];
	
	MZFormSheetPresentationViewController *viewController = [[MZFormSheetPresentationViewController alloc] initWithContentView:contentView];
	viewController.presentationController.shouldDismissOnBackgroundViewTap = YES;
	viewController.presentationController.shouldCenterVertically = YES;
	[self presentViewController:viewController animated:NO completion:nil];
}

- (void)updateDataWithLocation:(CLLocation *)location {
	if (!CLLocationCoordinate2DIsValid(location.coordinate)) {
		return;
	}
	if (!self.busStatusDetailResponse) {
		return;
	}
	if (!self.didDetermined) {
		return;
	}
	if (self.shouldTerminated) {
		return;
	}
	if (self.atomicInteger != 0) {
		return;
	}
	self.atomicInteger++;
	if (!self.monitoredLocation || ([CocoaUtils dateIntervalForComponent:NSCalendarUnitSecond fromDate:self.monitoredLocation.timestamp toDate:location.timestamp] > 1 && [CocoaUtils distanceFromCoordinate:self.monitoredLocation.coordinate toCoordinate:location.coordinate] > 10)) {
		self.monitoredLocation = location;
		[self monitoringHundredMetersRegionsWithLocation:location];
		if (self.shouldTerminated) {
			self.atomicInteger--;
			return;
		}
		[self monitoringKilometerRegionsWithLocation:location];
	}
	if (!self.uploadedLocation || [CocoaUtils dateIntervalForComponent:NSCalendarUnitSecond fromDate:self.uploadedLocation.timestamp toDate:location.timestamp] > 10) {
		self.uploadedLocation = location;
		[self uploadBusStatusLocation:location];
	}
	self.atomicInteger--;
}

- (void)monitoringHundredMetersRegionsWithLocation:(CLLocation *)location {
	for (CircularRegion *region in self.hundredMetersRegions) {
		if (region.state == CLRegionStateUnknown) {
			if ([region containsCoordinate:location.coordinate]) {
				region.state = CLRegionStateInside;
			} else {
				region.state = CLRegionStateOutside;
			}
		} else if (region.state == CLRegionStateInside) {
			if (![region containsCoordinate:location.coordinate]) {
				region.state = CLRegionStateOutside;
				region.notifyOnExit = YES;
			}
		} else if (region.state == CLRegionStateOutside) {
			if ([region containsCoordinate:location.coordinate]) {
				region.state = CLRegionStateInside;
				region.notifyOnEntry = YES;
			}
		}
		if (region.notifyOnExit) {
			region.notifyOnExit = NO;
			[self.consoleLog appendString:@"notify on exit\n\n"];
			[self reloadDataToExitWithScheduleStopId:region.tag];
			[self updateBusStatusTimeWithScheduleStopId:region.tag isArrival:NO];
		}
		if (self.shouldTerminated) {
			break;
		}
		if (region.notifyOnEntry) {
			region.notifyOnEntry = NO;
			[self.consoleLog appendString:@"notify on entry\n\n"];
			[self reloadDataToEnterWithScheduleStopId:region.tag];
			[self updateBusStatusTimeWithScheduleStopId:region.tag isArrival:YES];
		}
	}
}

- (void)monitoringKilometerRegionsWithLocation:(CLLocation *)location {
	for (CircularRegion *region in self.kilometerRegions) {
		if (region.state == CLRegionStateUnknown) {
			if ([region containsCoordinate:location.coordinate]) {
				region.state = CLRegionStateInside;
			} else {
				region.state = CLRegionStateOutside;
			}
		} else if (region.state == CLRegionStateInside) {
			if (![region containsCoordinate:location.coordinate]) {
				region.state = CLRegionStateOutside;
			}
		} else if (region.state == CLRegionStateOutside) {
			if ([region containsCoordinate:location.coordinate]) {
				region.state = CLRegionStateInside;
				region.notifyOnEntry = YES;
			}
		}
		if (region.notifyOnEntry) {
			region.notifyOnEntry = NO;
			[self.consoleLog appendString:@"notify on alert\n\n"];
			[self getBusStatusDetailInBackground:YES completion:^{
				[self reloadDataToEnterWithScheduleStopId:region.tag];
				[self showAlertForNextWithScheduleStopId:region.tag];
			}];
		}
	}
}

- (void)addAnnotationsByScheduleStops {
	NSMutableArray *newAnnotations = [NSMutableArray array];
	[self.scheduleStops enumerateObjectsUsingBlock:^(ScheduleStop *scheduleStop, NSUInteger idx, BOOL *stop) {
		PointAnnotation *annotation = [[PointAnnotation alloc] init];
		annotation.coordinate = CLLocationCoordinate2DMake(scheduleStop.busstation.latitude, scheduleStop.busstation.longitude);
		annotation.title = scheduleStop.busstation.destination.show_name;
		annotation.subtitle = scheduleStop.busstation.name;
		if (idx == self.scheduleStops.count - 1) {
			annotation.image = [UIImage imageNamed:@"icon_bus_station_arrival"];
		} else {
			annotation.image = [UIImage imageNamed:@"icon_bus_station_departure"];
		}
		annotation.weaklyAssociatedObject = scheduleStop;
		[newAnnotations addObject:annotation];
	}];
	[self.mapView addAnnotations:newAnnotations];
}

- (void)showAnnotationsByDisplayStops {
	NSMutableArray *annotations = [NSMutableArray array];
	for (id<MKAnnotation> annotation in self.mapView.annotations) {
		if ([NSStringFromClass([annotation class]) isEqualToString:@"MKUserLocation"]) {
			[annotations addObject:annotation];
		} else if ([annotation isKindOfClass:[PointAnnotation class]]) {
			PointAnnotation *marker = annotation;
			if ([self.displayStops bk_any:^BOOL (ScheduleStop *obj) {
				return marker.weaklyAssociatedObject == obj;
			}]) {
				[annotations addObject:annotation];
			}
		}
	}
	[self.mapView showAnnotations:annotations animated:NO];
}

- (void)drawDirectionsByScheduleStops {
	// [self.mapView removeOverlays:self.mapView.overlays];
	NSMutableArray *locations = [NSMutableArray array];
	for (ScheduleStop *scheduleStop in self.scheduleStops) {
		CLLocation *location = [[CLLocation alloc] initWithLatitude:scheduleStop.busstation.latitude longitude:scheduleStop.busstation.longitude];
		[locations addObject:location];
	}
// if (self.mapView.userLocation.location && !self.shouldTerminated) {
// NSInteger fromIndex = [self.scheduleStops indexOfObject:self.fromStop];
// if (fromIndex >= 0 && fromIndex < self.scheduleStops.count - 1) {
// [locations insertObject:[[CLLocation alloc] initWithLatitude:self.mapView.userLocation.location.coordinate.latitude longitude:self.mapView.userLocation.location.coordinate.longitude] atIndex:fromIndex + 1];
// }
// }
	[locations enumerateObjectsUsingBlock:^(CLLocation *location, NSUInteger idx, BOOL *stop) {
		if (idx < locations.count - 1) {
			CLLocation *nextLocation = locations[idx + 1];
			[self drawDirectionFromCoordinate:location.coordinate toCoordinate:nextLocation.coordinate completionHandler:nil];
		}
	}];
}

- (void)drawDirectionFromCoordinate:(CLLocationCoordinate2D)sourceCoordinate toCoordinate:(CLLocationCoordinate2D)destinationCoordinate completionHandler:(void (^) (MKRoute *route))completionHandler {
	MKPlacemark *sourcePlacemark = [[MKPlacemark alloc] initWithCoordinate:sourceCoordinate addressDictionary:nil];
	MKPlacemark *destinationPlacemark = [[MKPlacemark alloc] initWithCoordinate:destinationCoordinate addressDictionary:nil];
	MKMapItem *sourceMapItem = [[MKMapItem alloc] initWithPlacemark:sourcePlacemark];
	MKMapItem *destinationMapItem = [[MKMapItem alloc] initWithPlacemark:destinationPlacemark];
	MKDirectionsRequest *request = [[MKDirectionsRequest alloc] init];
	request.source = sourceMapItem;
	request.destination = destinationMapItem;
	request.requestsAlternateRoutes = YES;
	MKDirections *directions = [[MKDirections alloc] initWithRequest:request];
	[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
		if (response) {
			NSArray<MKRoute *> *routes = nil;
			if ([SystemUtils isOperatingSystemAtLeastVersion:NSOperatingSystemVersionMake(9, 0, 0)]) {
				routes = [response.routes bk_select:^BOOL (MKRoute *route) {
					return route.transportType == MKDirectionsTransportTypeTransit;
				}];
			}
			if (routes.count == 0) {
				routes = [response.routes bk_select:^BOOL (MKRoute *route) {
					return route.transportType == MKDirectionsTransportTypeAutomobile;
				}];
			}
			if (routes.count > 1) {
				routes = [routes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"expectedTravelTime" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:@"distance" ascending:YES]]];
			}
			MKRoute *route = routes.count > 0 ? routes[0] : nil;
			if (route) {
				[self.mapView addOverlay:route.polyline];
				if (completionHandler) {
					completionHandler(route);
				}
			}
		}
	}];
}

- (void)openWebSocket {
	if (self.shouldTerminated) {
		return;
	}
	[self closeWebSocket];
	NSString *urlString = @"ws://awsuranus.gotobus.com:8080/app";
#if DEBUG
	if ([Vars isJavaAppLocalhost]) {
		NSURLComponents *components = [NSURLComponents componentsWithString:[Vars javaAppLocalhost]];
		components.scheme = @"ws";
		urlString = components.string;
	}
#endif
	urlString = [urlString stringByAppendingFormat:@"/websocket/bus-status?userId=%d", [Vars appDelegate].loginUser.id];
	self.webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:urlString]];
	self.webSocket.delegate = self;
	[self.webSocket open];
}

- (void)closeWebSocket {
	self.webSocket.delegate = nil;
	[self.webSocket close];
}

- (void)tryOutWebSocket {
	if (self.webSocket.readyState != SR_OPEN) {
		[self openWebSocket];
	}
}

- (void)reconnectWebSocket {
	if (self.shouldTerminated) {
		return;
	}
	@weakify(self)
	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
		@strongify(self)
		if (self.reachabilityManager.reachable) {
			[self tryOutWebSocket];
		}
	});
}

- (void)sendWebSocketMessage:(id)data {
	if (self.webSocket.readyState == SR_OPEN) {
		[self.webSocket send:data];
	}
}

- (void)terminateAll {
	self.hundredMetersRegions = nil;
	self.kilometerRegions = nil;

	self.fromStop = nil;
	self.toStop = nil;
	self.displayStops = nil;
	[self reloadFooterViewWithObject:nil];

	[self closeWebSocket];
	[self.reachabilityManager stopMonitoring];
	[self.locationManager stopUpdatingLocation];

	[CocoaUtils showAlertWithTitle:IMLocalizedString(@"Warning") message:@"You has reached last stop. App will suspend your check-in. Please contact us if you have any question."];
}

- (BusStatusDetailBean *)getBusStatusDetailBeanWithScheduleStopId:(NSInteger)scheduleStopId {
	NSArray<BusStatusDetailBean *> *assignedBeans = [self.busStatusDetailResponse.resultList bk_select:^BOOL (BusStatusDetailBean *detail) {
		return detail.driverAssigned;
	}];
	BusStatusDetailBean *bean = [assignedBeans bk_match:^BOOL (BusStatusDetailBean *obj) {
		return obj.scheduleStop.id == scheduleStopId;
	}];
	return bean;
}

#pragma mark - Map view delegate

- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
	if (![annotation isKindOfClass:[PointAnnotation class]]) {
		return nil;
	}
	PointAnnotation *marker = annotation;
	static NSString *PinIdentifier = @"Pin";
	MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:PinIdentifier];
	if (!annotationView) {
		annotationView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:PinIdentifier];
	}
	annotationView.canShowCallout = NO;
	annotationView.image = marker.image;
	if (annotationView.image) {
		annotationView.centerOffset = CGPointMake(0, -annotationView.image.size.height / 2);
	} else {
		annotationView.centerOffset = CGPointZero;
	}
	return annotationView;
}

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
	if ([NSStringFromClass(view.class) isEqualToString:@"MKModernUserLocationView"]) {
		return;
	}
	if (![view.annotation isKindOfClass:[PointAnnotation class]]) {
		return;
	}
	PointAnnotation *marker = view.annotation;
	SMModernCalloutView *calloutView = ((SMMapView *) mapView).calloutView;
	[CocoaUtils configureCalloutView:calloutView annotation:marker];
	[calloutView presentCalloutFromRect:view.bounds inView:view constrainedToView:mapView animated:YES];
}

- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view {
	if ([NSStringFromClass(view.class) isEqualToString:@"MKModernUserLocationView"]) {
		return;
	}
	if (![view.annotation isKindOfClass:[PointAnnotation class]]) {
		return;
	}
	SMModernCalloutView *calloutView = ((SMMapView *) mapView).calloutView;
	[calloutView dismissCalloutAnimated:YES];
}

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray<MKAnnotationView *> *)views {
	for (MKAnnotationView *view in views) {
		if ([NSStringFromClass(view.class) isEqualToString:@"MKModernUserLocationView"]) {
			view.canShowCallout = NO;
		}
	}
}

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay {
	MKPolylineRenderer *line = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
	line.lineWidth = 5;
	line.strokeColor = [UIColor aquaColor];
	return line;
}

- (void)mapView:(MKMapView *)mapView didChangeUserTrackingMode:(MKUserTrackingMode)mode animated:(BOOL)animated {
	if (mode == MKUserTrackingModeFollowWithHeading) {
		[self.userTrackingButton setImage:[UIImage imageNamed:@"icon_user_tracking_follow_heading"] forState:UIControlStateNormal];
	} else if (mode == MKUserTrackingModeFollow) {
		[self.userTrackingButton setImage:[UIImage imageNamed:@"icon_user_tracking_follow"] forState:UIControlStateNormal];
	} else if (mode == MKUserTrackingModeNone) {
		[self.userTrackingButton setImage:[UIImage imageNamed:@"icon_user_tracking"] forState:UIControlStateNormal];
	}
}

- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
	if (userLocation) {
		[self determineDataWithLocation:userLocation.location];
		// [self updateDataWithLocation:userLocation.location];
	}
}

#pragma mark - Location manager delegate

- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
	if ([CocoaUtils allowsBackgroundLocationUpdates] && status == kCLAuthorizationStatusAuthorizedWhenInUse) {
		[CocoaUtils showAlertWithTitle:IMLocalizedString(@"Information") message:[NSString stringWithFormat:IMLocalizedString(@"Please allow \"%@\" to access your location always"), [CocoaUtils bundleName]]];
	} else if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusAuthorizedAlways) {
		[manager startUpdatingLocation];
	} else if (status == kCLAuthorizationStatusDenied || status == kCLAuthorizationStatusRestricted) {
		[CocoaUtils showAlertWithTitle:IMLocalizedString(@"Information") message:[NSString stringWithFormat:[CocoaUtils allowsBackgroundLocationUpdates] ? IMLocalizedString(@"Please allow \"%@\" to access your location always") : IMLocalizedString(@"Please allow \"%@\" to access your location"), [CocoaUtils bundleName]]];
	}
}

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
	if (locations.count > 0) {
		[self updateDataWithLocation:locations.lastObject];
	}
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
	[CocoaUtils showNotificationWithMessage:error.localizedDescription];
}

#pragma mark - <SRWebSocketDelegate>

- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
	if (reason) {
		[CocoaUtils showNotificationWithMessage:reason];
	}
	[self reconnectWebSocket];
}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
	if (error.localizedDescription) {
		[CocoaUtils showNotificationWithMessage:error.localizedDescription];
	}
	[self reconnectWebSocket];
}

#pragma mark

- (void)dealloc {
	[self closeWebSocket];
	[self.reachabilityManager stopMonitoring];
	[self.locationManager stopUpdatingLocation];
	[UIApplication sharedApplication].idleTimerDisabled = NO;
}

@end
