// TTTLocationFormatter.m
//
// Copyright (c) 2011 Mattt Thompson (http://mattt.me)
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#import "TTTLocationFormatter.h"

static double const kTTTMetersToKilometersCoefficient = 0.001;
static double const kTTTMetersToFeetCoefficient = 3.2808399;
static double const kTTTMetersToYardsCoefficient = 1.0936133;
static double const kTTTMetersToMilesCoefficient = 0.000621371192;

static inline double CLLocationDistanceToKilometers(CLLocationDistance distance) {
    return distance * kTTTMetersToKilometersCoefficient;
}

static inline double CLLocationDistanceToFeet(CLLocationDistance distance) {
    return distance * kTTTMetersToFeetCoefficient;
}

static inline double CLLocationDistanceToYards(CLLocationDistance distance) {
    return distance * kTTTMetersToYardsCoefficient;
}

static inline double CLLocationDistanceToMiles(CLLocationDistance distance) {
    return distance * kTTTMetersToMilesCoefficient;
}

#pragma mark -

static inline double DEG2RAD(double degrees) { 
    return degrees * M_PI / 180; 
}

static inline double RAD2DEG(double radians) { 
    return radians * 180 / M_PI; 
}

static inline CLLocationDegrees CLLocationDegreesBearingBetweenCoordinates(CLLocationCoordinate2D originCoordinate, CLLocationCoordinate2D destinationCoordinate) {
    double lat1 = DEG2RAD(originCoordinate.latitude);
	double lon1 = DEG2RAD(originCoordinate.longitude);
	double lat2 = DEG2RAD(destinationCoordinate.latitude);
	double lon2 = DEG2RAD(destinationCoordinate.longitude);
	
    double dLon = lon2 - lon1;
	double y = sin(dLon) * cos(lat2);
	double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
	double bearing = atan2(y, x) + (2 * M_PI);
	
    // `atan2` works on a range of -π to 0 to π, so add on 2π and perform a modulo check
	if (bearing > (2 * M_PI)) {
		bearing = bearing - (2 * M_PI);
	}
    
	return RAD2DEG(bearing);
}

TTTLocationCardinalDirection TTTLocationCardinalDirectionFromBearing(CLLocationDegrees bearing) {
    if(bearing > 337.5) {
        return TTTNorthDirection;
    } else if(bearing > 292.5) {
        return TTTNorthwestDirection;
    } else if(bearing > 247.5) {
        return TTTWestDirection;
    } else if(bearing > 202.5) {
        return TTTSouthwestDirection;
    } else if(bearing > 157.5) {
        return TTTSouthDirection;
    } else if(bearing > 112.5) {
        return TTTSoutheastDirection;
    } else if(bearing > 67.5) {
        return TTTEastDirection;
    } else if(bearing > 22.5) {
        return TTTNortheastDirection;
    } else {
        return TTTNorthDirection;
    }
}

#pragma mark -

static double const kTTTMetersPerSecondToKilometersPerHourCoefficient = 3.6;
static double const kTTTMetersPerSecondToFeetPerSecondCoefficient = 3.2808399;
static double const kTTTMetersPerSecondToMilesPerHourCoefficient = 2.23693629;

static inline double CLLocationSpeedToKilometersPerHour(CLLocationSpeed speed) {
    return speed * kTTTMetersPerSecondToKilometersPerHourCoefficient;
}

static inline double CLLocationSpeedToFeetPerSecond(CLLocationSpeed speed) {
    return speed * kTTTMetersPerSecondToFeetPerSecondCoefficient;
}

static inline double CLLocationSpeedToMilesPerHour(CLLocationSpeed speed) {
    return speed * kTTTMetersPerSecondToMilesPerHourCoefficient;
}


@interface TTTLocationFormatter ()
@property (readwrite, nonatomic, assign) TTTLocationFormatterCoordinateOrder coordinateOrder;
@property (readwrite, nonatomic, assign) TTTLocationFormatterBearingStyle bearingStyle;
@property (readwrite, nonatomic, assign) TTTLocationUnitSystem unitSystem;
@property (readwrite, nonatomic, retain) NSNumberFormatter *numberFormatter;
@end

@implementation TTTLocationFormatter
@synthesize coordinateOrder = _coordinateOrder;
@synthesize bearingStyle = _bearingStyle;
@synthesize unitSystem = _unitSystem;
@synthesize numberFormatter = _numberFormatter;

- (id)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    
    self.coordinateOrder = TTTCoordinateLatLngOrder;
    self.bearingStyle = TTTBearingWordStyle;
    self.unitSystem = TTTMetricSystem;
    
    self.numberFormatter = [[[NSNumberFormatter alloc] init] autorelease];
    [self.numberFormatter setLocale:[NSLocale currentLocale]];
    [self.numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
    [self.numberFormatter setMaximumSignificantDigits:2];
    [self.numberFormatter setUsesSignificantDigits:YES];
    
    return self;
}

- (NSString *)stringFromCoordinate:(CLLocationCoordinate2D)coordinate {
    return [NSString stringWithFormat:NSLocalizedString(@"(%@, %@)", @"Coordinate format"), [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:coordinate.latitude]], [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:coordinate.longitude]], nil];
}

- (NSString *)stringFromLocation:(CLLocation *)location {
    return [self stringFromCoordinate:location.coordinate];
}

- (NSString *)stringFromDistance:(CLLocationDistance)distance {
    NSString *distanceString = nil;
    NSString *unitString = nil;
    
    switch (self.unitSystem) {
        case TTTMetricSystem: {
            double kilometerDistance = CLLocationDistanceToKilometers(distance);            
            if (kilometerDistance > 1) {
                distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:kilometerDistance]];
                unitString = NSLocalizedString(@"km", @"Kilometer Unit");
            } else {
                double meterDistance = distance;
                distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:meterDistance]];
                unitString = NSLocalizedString(@"m", @"Meter Unit");
            }
            break; 
        }
            
        case TTTImperialSystem: {
            double feetDistance = CLLocationDistanceToFeet(distance);
            if (feetDistance < 300) {
                distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:feetDistance]];
                unitString = NSLocalizedString(@"ft", @"Feet Unit");
            } else {
                double yardDistance = CLLocationDistanceToYards(distance);
                if (yardDistance < 500) {
                    distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:yardDistance]];
                    unitString = NSLocalizedString(@"yds", @"Yard Unit");
                } else {
                    double milesDistance = CLLocationDistanceToMiles(distance);
                    distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:milesDistance]];
                    unitString = (milesDistance > 1.0 && milesDistance < 1.1) ? NSLocalizedString(@"mile", @"Mile Unit (Singular)") : NSLocalizedString(@"miles", @"Mile Unit (Plural)");
                } 
            }
            break; 
        }
    }
    
    return [NSString stringWithFormat:NSLocalizedString(@"%@ %@", @"#{Distance} #{Unit}"), distanceString, unitString];
}

- (NSString *)stringFromBearing:(CLLocationDegrees)bearing {
    switch (self.bearingStyle) {
        case TTTBearingWordStyle:
            switch (TTTLocationCardinalDirectionFromBearing(bearing)) {
                case TTTNorthDirection:
                    return NSLocalizedString(@"North", @"North Direction");
                case TTTNortheastDirection:
                    return NSLocalizedString(@"Northeast", @"Northeast Direction");
                case TTTEastDirection:
                    return NSLocalizedString(@"East", @"East Direction");
                case TTTSoutheastDirection:
                    return NSLocalizedString(@"Southeast", @"Southeast Direction");
                case TTTSouthDirection:
                    return NSLocalizedString(@"South", @"South Direction");
                case TTTSouthwestDirection:
                    return NSLocalizedString(@"Southwest", @"Southwest Direction");
                case TTTWestDirection:
                    return NSLocalizedString(@"West", @"West Direction");
                case TTTNorthwestDirection:
                    return NSLocalizedString(@"Northwest", @"Northwest Direction");
            }
            break;
        case TTTBearingAbbreviationWordStyle:
            switch (TTTLocationCardinalDirectionFromBearing(bearing)) {
                case TTTNorthDirection:
                    return NSLocalizedString(@"N", @"North Direction Abbreviation");
                case TTTNortheastDirection:
                    return NSLocalizedString(@"NE", @"Northeast Direction Abbreviation");
                case TTTEastDirection:
                    return NSLocalizedString(@"E", @"East Direction Abbreviation");
                case TTTSoutheastDirection:
                    return NSLocalizedString(@"SE", @"Southeast Direction Abbreviation");
                case TTTSouthDirection:
                    return NSLocalizedString(@"S", @"South Direction Abbreviation");
                case TTTSouthwestDirection:
                    return NSLocalizedString(@"SW", @"Southwest Direction Abbreviation");
                case TTTWestDirection:
                    return NSLocalizedString(@"W", @"West Direction Abbreviation");
                case TTTNorthwestDirection:
                    return NSLocalizedString(@"NW", @"Northwest Direction Abbreviation");;
            }
            break;
        case TTTBearingNumericStyle:
            return [[self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:bearing]] stringByAppendingString:NSLocalizedString(@"°", @"Degrees Symbol")];
    }
    
    return nil;
}

- (NSString *)stringFromSpeed:(CLLocationSpeed)speed {
    NSString *speedString = nil;
    NSString *unitString = nil;
    
    switch (self.unitSystem) {
        case TTTMetricSystem: {
            double metersPerSecondSpeed = speed;
            double kilometersPerHourSpeed = CLLocationSpeedToKilometersPerHour(speed);
            
            if (kilometersPerHourSpeed > 1) {
                speedString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:kilometersPerHourSpeed]];
                unitString = NSLocalizedString(@"km/h", @"Kilometers Per Hour Unit");
            } else {
                speedString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:metersPerSecondSpeed]];
                unitString = NSLocalizedString(@"m/s", @"Meters Per Second Unit");
            }
            break; 
        }
            
        case TTTImperialSystem: {
            double feetPerSecondSpeed = CLLocationSpeedToFeetPerSecond(speed);
            double milesPerHourSpeed = CLLocationSpeedToMilesPerHour(speed);
            
            if (milesPerHourSpeed > 1) {
                speedString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:milesPerHourSpeed]];
                unitString = NSLocalizedString(@"mph", @"Miles Per Hour Unit");
            } else {
                speedString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:feetPerSecondSpeed]];
                unitString = NSLocalizedString(@"ft/s", @"Feet Per Second Unit");
            }
            break; 
        }
    }
    
    return [NSString stringWithFormat:NSLocalizedString(@"%@ %@", @"#{Speed} #{Unit}"), speedString, unitString];
}

- (NSString *)stringFromDistanceFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation {    
    return [self stringFromDistance:[destinationLocation distanceFromLocation:originLocation]];
}

- (NSString *)stringFromBearingFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation {
    return [self stringFromBearing:CLLocationDegreesBearingBetweenCoordinates(originLocation.coordinate, destinationLocation.coordinate)];
}

- (NSString *)stringFromDistanceAndBearingFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation {
    return [NSString stringWithFormat:NSLocalizedString(@"%@ %@", @"#{Dimensional Quantity} #{Direction}"), [self stringFromDistanceFromLocation:originLocation toLocation:destinationLocation], [self stringFromBearingFromLocation:originLocation toLocation:destinationLocation]];
}

- (NSString *)stringFromVelocityFromLocation:(CLLocation *)originLocation toLocation:(CLLocation *)destinationLocation atSpeed:(CLLocationSpeed)speed {
    return [NSString stringWithFormat:NSLocalizedString(@"%@ %@", @"#{Dimensional Quantity} #{Direction}"), [self stringFromSpeed:speed], [self stringFromBearingFromLocation:originLocation toLocation:destinationLocation]];
}

@end
