"""
@package core.units

@brief Units management

.. todo::
    Probably will be replaced by Python ctypes fns in the near future(?)

Usage:

    from core.units import Units

Classes:
 - units::BaseUnits

(C) 2009, 2011 by the GRASS Development Team

This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.

@author Martin Landa <landa.martin gmail.com>
"""

import math

if __name__ == '__main__':
    import os
    import sys

from core.utils import _

class BaseUnits:
    def __init__(self):
        self._units = dict()
        self._units['length'] = { 0 : { 'key' : 'mu', 'label' : _('map units') },
                             1 : { 'key' : 'me', 'label' : _('meters') },
                             2 : { 'key' : 'km', 'label' : _('kilometers') },
                             3 : { 'key' : 'mi', 'label' : _('miles') },
                             4 : { 'key' : 'ft', 'label' : _('feet') } }
        
        self._units['area']   = { 0 : { 'key' : 'mu', 'label' : _('sq map units') },
                             1 : { 'key' : 'me', 'label' : _('sq meters') },
                             2 : { 'key' : 'km', 'label' : _('sq kilometers') },
                             3 : { 'key' : 'ar', 'label' : _('acres') },
                             4 : { 'key' : 'ht', 'label' : _('hectares') } }

    def GetUnitsList(self, type):
        """Get list of units (their labels)
        
        :param type: units type ('length' or 'area')
        
        :return: list of units labels
        """
        result = list()
        try:
            keys = self._units[type].keys()
            keys.sort()
            for idx in keys:
                result.append(self._units[type][idx]['label'])
        except KeyError:
            pass
        
        return result

    def GetUnitsKey(self, type, index):
        """Get units key based on index
        
        :param type: units type ('length' or 'area')
        :param index: units index
        """
        return self._units[type][index]['key']

    def GetUnitsIndex(self, type, key):
        """Get units index based on key
        
        :param type: units type ('length' or 'area')
        :param key: units key, e.g. 'me' for meters

        :return: index
        """
        for k, u in self._units[type].iteritems():
            if u['key'] == key:
                return k
        return 0

Units = BaseUnits()

def ConvertValue(value, type, units):
    """Convert value from map units to given units

    Inspired by vector/v.to.db/units.c

    :param value: value to be converted
    :param type: units type ('length', 'area')
    :param unit: destination units
    """
    # get map units
    # TODO
    
    f = 1
    if type == 'length':
        if units == 'me':
            f = 1.0
        elif units == 'km':
            f = 1.0e-3
        elif units == 'mi':
            f = 6.21371192237334e-4
        elif units == 'ft':
            f = 3.28083989501312
    else: # -> area
        if units == 'me':
            f = 1.0
        elif units == 'km':
            f = 1.0e-6
        elif units == 'mi':
            f = 3.86102158542446e-7
        elif units == 'ft':
            f = 10.7639104167097
        elif units == 'ar':
            f = 2.47105381467165e-4
        elif units == 'ht':
            f = 1.0e-4

    return f * value


def formatDist(distance, mapunits):
        """Formats length numbers and units in a nice way.

        Formats length numbers and units as a function of length.

        >>> formatDist(20.56915, 'metres')
        (20.57, 'm')
        >>> formatDist(6983.4591, 'metres')
        (6.983, 'km')
        >>> formatDist(0.59, 'feet')
        (0.59, 'ft')
        >>> formatDist(8562, 'feet')
        (1.622, 'miles')
        >>> formatDist(0.48963, 'degrees')
        (29.38, 'min')
        >>> formatDist(20.2546, 'degrees')
        (20.25, 'deg')
        >>> formatDist(82.146, 'unknown')
        (82.15, 'units')

        Accepted map units are 'meters', 'metres', 'feet', 'degree'.
        Returns 'units' instead of unrecognized units.

        :param distance: map units
        :param mapunits: map units

        From code by Hamish Bowman Grass Development Team 2006.
        """
        if mapunits == 'metres':
            mapunits = 'meters'
        outunits = mapunits
        distance = float(distance)
        divisor = 1.0

        # figure out which units to use
        if mapunits == 'meters':
            if distance > 2500.0:
                outunits = 'km'
                divisor = 1000.0
            else:
                outunits = 'm'
        elif mapunits == 'feet':
            # nano-bug: we match any "feet", but US Survey feet is really
            #  5279.9894 per statute mile, or 10.6' per 1000 miles. As >1000
            #  miles the tick markers are rounded to the nearest 10th of a
            #  mile (528'), the difference in foot flavours is ignored.
            if distance > 5280.0:
                outunits = 'miles'
                divisor = 5280.0
            else:
                outunits = 'ft'
        elif 'degree' in mapunits:
            # was: 'degree' in mapunits and not haveCtypes (for unknown reason)
            if distance < 1:
                outunits = 'min'
                divisor = (1 / 60.0)
            else:
                outunits = 'deg'
        else:
            return (distance, 'units')

        # format numbers in a nice way
        if (distance / divisor) >= 2500.0:
            outdistance = round(distance / divisor)
        elif (distance / divisor) >= 1000.0:
            outdistance = round(distance / divisor, 1)
        elif (distance / divisor) > 0.0:
            outdistance = round(distance / divisor,
                                int(math.ceil(3 - math.log10(distance / divisor))))
        else:
            outdistance = float(distance / divisor)

        return (outdistance, outunits)


def doc_test():
    """Tests the module using doctest

    :return: a number of failed tests
    """
    import doctest
    from core.utils import do_doctest_gettext_workaround
    do_doctest_gettext_workaround()
    return doctest.testmod().failed


if __name__ == '__main__':
    sys.exit(doc_test())
