--[[----------------------------------------------------------------------------

WFCatGPSFormatUtils.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2015 Adobe Systems IncorporatedAll Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

----------------------------------------------------------------------------]]--

	-- WF.core.string
local KSStringUtils = require 'KSStringUtils'
local KSTableUtils = require 'KSTableUtils'

	-- WF.core.utils
local WFArguments = require 'WFArguments'

local WFCatFormatterUtils = require 'WFCatFormatterUtils'

local lowercase = KSStringUtils.lower
local check = WFArguments.check

--==============================================================================

local kCardinalDirection = {
	"$$$/AgLocation/CardinalDirection/NorthEast=North-East",
	"$$$/AgLocation/CardinalDirection/East=East",
	"$$$/AgLocation/CardinalDirection/SouthEast=South-East",
	"$$$/AgLocation/CardinalDirection/South=South",
	"$$$/AgLocation/CardinalDirection/SouthWest=South-West",
	"$$$/AgLocation/CardinalDirection/West=West",
	"$$$/AgLocation/CardinalDirection/NorthWest=North-West",
	"$$$/AgLocation/CardinalDirection/North=North",
}

--------------------------------------------------------------------------------

local WFCatGPSFormatUtils = {}

--------------------------------------------------------------------------------

local kMetersPerFoot = 0.3048
WFCatGPSFormatUtils.MetersPerFoot = kMetersPerFoot

--------------------------------------------------------------------------------

local kDirectionRefToOzRef = { C = 'M', T = 'T' }
local kOzRefTODirectionRef = KSTableUtils.invert( kDirectionRefToOzRef )
--------------------------------------------------------------------------------

function WFCatGPSFormatUtils.floatLatLonCoord( input )

	if not input then

		return

	end

	local pat = "[0-9.]+"
	local value = 0
	local divisor = 1
	for x in string.gfind( input, pat ) do
		value = value + tonumber( x ) / divisor
		divisor = divisor * 60

	end

	local ref = string.gfind( input, "[NSWE]" )()

	if ref == "S" or ref == "W" then

		value = value * -1

	end

	return value
end

------------------------------------------------------------------------------

function WFCatGPSFormatUtils.normalizeGPSCoord( input, maxValue )

	-- convert the input value to be within [-maxValue, maxValue]

	local period = 2 * maxValue;

	if input < -period or input > period then

		input = math.fmod( input, period )

	end

	if input < -maxValue then

		input = input + period

	elseif input > maxValue then
	
		input = input - period

	end

	return input
end

------------------------------------------------------------------------------

local function latLonCoordFromFloatInternal( input, latOrLon, forUIDisplay )

	local compass

	if latOrLon == "lat" then

		input = WFCatGPSFormatUtils.normalizeGPSCoord( input, 90 )

		if input < 0 then
			input = input * -1
			compass = "S"
		else
			compass = "N"
		end

	else
	
		input = WFCatGPSFormatUtils.normalizeGPSCoord( input, 180 )

		if input < 0 then
			input = input * -1
			compass = "W"
		else
			compass = "E"
		end

	end

	local degrees
	degrees, input = math.modf( input )
	input = input * 60
	local minutesWithFractionalAmount = input
	local minutes
	minutes, input = math.modf( input )
	input = input * 60
	local seconds
	seconds = string.format( "%.2f", input )
	local result

	if forUIDisplay == true then

		result = { 'ZSTR', "$$$/AgLibrary/Exif/LatitudeLongitude=^1^D^2\'^3\" ^4",
			degrees, minutes, seconds, compass }
			
	else
	
		minutesWithFractionalAmount = string.format("%.10f", minutesWithFractionalAmount):gsub('%.?0+$', '')
		result = { 'ZSTR', "$$$/AgLibrary/Exif/LatitudeLongitudeXMP=^1,^2^3",
			degrees, minutesWithFractionalAmount, compass }
			-- XMP spec don't allow fractional numbers in the second field. This representation gives more
			-- precision in specifying the GPS coordinates

	end

	return result
	
end

--------------------------------------------------------------------------------

-- The Latitude/Longitude coordinate string returned by this function is good
-- for UI display.

function WFCatGPSFormatUtils.latLonCoordFromFloat( input, latOrLon )
	return latLonCoordFromFloatInternal( input, latOrLon, true )
end

--------------------------------------------------------------------------------

-- The Latitude/Longitude coordinate string returned by this function is good
-- for persistent storage in XMP.

function WFCatGPSFormatUtils.xmpLatLonCoordFromFloat( input, latOrLon )
	return latLonCoordFromFloatInternal( input, latOrLon, false )
end

--------------------------------------------------------------------------------

-- The Latitude/Longitude coordinate string returned by this function is good
-- for persistent storage in XMP.

function WFCatGPSFormatUtils.normalizeXmpLatLonCoord( input, latOrLon )
	local latLonFloat = WFCatGPSFormatUtils.floatLatLonCoord( input )
	return latLonCoordFromFloatInternal( latLonFloat, latOrLon, false )
end

--------------------------------------------------------------------------------

-- The Latitude/Longitude coordinate string returned by this function is good
-- for UI display.

function WFCatGPSFormatUtils.normalizeLatLonCoord( input, latOrLon )
	local latLonFloat = WFCatGPSFormatUtils.floatLatLonCoord( input )
	return latLonCoordFromFloatInternal( latLonFloat, latOrLon, true )
end

--------------------------------------------------------------------------------

function WFCatGPSFormatUtils.getElevationOverlay( elevation )

	if elevation then
		return {
			location = {
				altitude = math.floor( elevation * 1000000 ) / 1000000
			}
		}
	else
		return {
			location = {
				altitude = { _delete = true }
			}
		}
	end

end

--------------------------------------------------------------------------------

function WFCatGPSFormatUtils.parseElevation( value )

	local altitude = value

	if type( value ) == 'string' then

		value = KSStringUtils.trimWhitespace( value )

		if string.len( value ) > 0 then

			value = lowercase( value )
			local metric = false -- kUseMetric -- TODO [arahn 2015-01-30]: Daemon shouldn't just "know" to use metric.
			local unitFeet, unitMeters
			
			-- TODO [arahn 2015-01-30]: Doing string matching using a ZSTR as an input is bad juju.
			-- For a daemon to remain blissfully ignorant of the current language this has to be reconsidered.
			for _, pattern in ipairs { "$$$/AgLibrary/Exif/Feet/Abbrev=ft", "$$$/AgLibrary/Exif/Feet=Feet" } do
				unitFeet = string.find( value, lowercase( pattern ) )

				if unitFeet then
					break
				end
			end

			-- TODO [arahn 2015-01-30]: Doing string matching using a ZSTR as an input is bad juju.
			-- For a daemon to remain blissfully ignorant of the current language this has to be reconsidered.
			for _, pattern in ipairs { "$$$/AgLibrary/Exif/Meters/Abbrev=m", "$$$/AgLibrary/Exif/Meters=Meters" } do
				unitMeters = string.find( value, lowercase( pattern ) )

				if unitMeters then
					break
				end
			end

			if type( unitFeet ) == "number" and unitFeet > 1 then
				value = string.sub( value, 1, unitFeet - 1 )
				metric = false

			elseif type( unitMeters ) == "number" and unitMeters > 1 then
				value = string.sub( value, 1, unitMeters - 1 )
				metric = true
			end
			altitude = tonumber( value )

			if altitude then

				if not metric then
					altitude = altitude * kMetersPerFoot
				end
			end

		end

	end

	return altitude
end

--------------------------------------------------------------------------------
--[[!
	\brief Parse an input string into GPS coordinates (latitude and longitude)

	\param value (string) input string to be parsed
	
	\return (number, number) latitude and longitude

	NOTE: Make a best-effort to extract GPS coordinates from the user-input
	string. Supports both the DMS and decimal formats. Coordinates can be exchanged
	if you specify NS/EW. Case is ignored.
]]--

function WFCatGPSFormatUtils.parseLatLong( value )

	check( 'string' )

	value = KSStringUtils.trimWhitespace( value )
	value = KSStringUtils.upper( value )

	if string.len( value ) > 0 then
		local ns = string.find( value, "[NS]" )
		local ew = string.find( value, "[EW]" )
		local sep = string.find( value, "[,;]" )

		if type( ns ) == "number" and type( ew ) == "number" and ns > 1 and ew > 1 then
			local lat, lng

			if ns < ew then

				-- latitude specified first

				lat = string.sub( value, 1, ns )
				lng = string.sub( value, ns + 1 )

			else

				-- longitude specified first

				lng = string.sub( value, 1, ew)
				lat = string.sub( value, ew + 1 )

			end

			if string.find( lat, "[0-9.]+" ) and string.find( lng, "[0-9.]+" ) then
				local floatLat = WFCatGPSFormatUtils.floatLatLonCoord( KSStringUtils.trimWhitespace( lat ), "lat" )
				local floatLng = WFCatGPSFormatUtils.floatLatLonCoord( KSStringUtils.trimWhitespace( lng ), "lon" )

				return floatLat, floatLng

		-- This unusual formatting of end followed by elseif is a way to hide a coverage
		-- idiosyncrasy (lua coverage never reports the end line hit).

		end elseif ns == nil and ew == nil and type( sep ) == "number" and sep > 1 then

			-- eg. "-24.755449, 15.292478"

			local lat, lng

			-- assume that latitude is specified first, longitude is specified the second

			lat = string.sub( value, 1, sep - 1)
			lng = string.sub( value, sep + 1 )
			local latFloat = tonumber( lat )
			local lngFloat = tonumber( lng )

			if latFloat and lngFloat then
				return latFloat, lngFloat
			end

		end

	end

	return nil, nil
end

--------------------------------------------------------------------------------

function WFCatGPSFormatUtils.getLocationOverlay( lat, lng )

	if lat and lng then

		return {
			location = {
				longitude = lng,
				latitude = lat,
				
				-- Reset fields that Oz may have set by reverse-geocoding
				city = { _delete = true },
				state = { _delete = true },
				country = { _delete = true },
				isoCountryCode = { _delete = true },
				sublocation = { _delete = true },
			},
		}

	else

		return {
			location = {
				longitude = { _delete = true },
				latitude = { _delete = true },
			}
		}

	end

end

--------------------------------------------------------------------------------

function WFCatGPSFormatUtils.getDirectionOverlay( direction )

	if direction then

		return {
			location = {
				direction = direction,
			}
		}
		
	else

		return {
			location = {
				direction = { _delete = true },
			}
		}
		
	end

end

--------------------------------------------------------------------------------

function WFCatGPSFormatUtils.formatDirection( direction, format )

	if type( direction ) == "number" then

		if format == "cardinal" then
			local index = math.floor( direction / 45 + 0.5 )

			if index == 0 then index = 8 end
			local name = kCardinalDirection[ index ]
			return 'ZSTR', "$$$/AgLibrary/Exif/Direction/DisplayName=^1", { 'ZSTR', name }
		else
			return 'ZSTR', "$$$/AgLibrary/Exif/Direction/DisplayDegrees=^1^D", math.floor( direction + 0.5 )
		end

	end

end

--------------------------------------------------------------------------------

function WFCatGPSFormatUtils.convertGPSDirectionRefToOzRef( directionRef )

	return directionRef and kDirectionRefToOzRef[ string.upper( directionRef ) ]
	
end

--------------------------------------------------------------------------------

function WFCatGPSFormatUtils.convertOzRefToGPSDirectionRef( ozRef )
	
	return ozRef and kOzRefTODirectionRef[ string.upper( ozRef ) ]

end

--------------------------------------------------------------------------------

function WFCatGPSFormatUtils.convertGPSImgDirectionToNumber( direction )
	
	local numerator, denominator = WFCatFormatterUtils.extractNumeratorDenominator( direction )
	
	local ratio = {}
	ratio[ 1 ] = numerator
	ratio[ 2 ] = denominator
	
	return WFCatFormatterUtils.convertToNumericValue( ratio )

end

--==============================================================================

return WFCatGPSFormatUtils
