from config import settings

class _UNIT(str): pass

# for now, we can't use _UNIT() yet; there is some code to be modified first
UNIT_CM = "cm" #_UNIT("cm")
UNIT_IN = "in" #_UNIT("in")
UNIT_PERCENT = "%" #_UNIT("%")
UNIT_PT = "pt" #_UNIT("pt")
UNIT_PX = "px" #_UNIT("px")


def _round(v): return (v < 0) and int(v - 0.5) or int(v + 0.5)


class Unit(object):
    """
      Class for coordinate and size values with units. This class abstracts from
      actual lengths with concrete units.
    """

    __slots__ = ('__is_unset', '__unit', '__cache_px', '__cache_pt',
                 '__cache_cm', '__cache_in', '__cache_pcnt', '__in2pcnt',
                 '__original_value', '__value',
                 '__IN2PT_SCALE', '__PT2IN_SCALE', '__IN2PX_SCALE',
                 '__PX2IN_SCALE', '__IN2CM_SCALE', '__CM2IN_SCALE')


    def __init__(self, value = None, unit = None, string = None):

        # scaling factors
        self.__IN2PT_SCALE = 72.0
        self.__PT2IN_SCALE = 1 / self.__IN2PT_SCALE
        self.__IN2PX_SCALE = float(settings.dpi)
        self.__PX2IN_SCALE = 1 / self.__IN2PX_SCALE
        self.__IN2CM_SCALE = 2.54
        self.__CM2IN_SCALE = 1 / self.__IN2CM_SCALE

        if (not string): string = None
        if (value == unit == string == None):
            value, unit = 0, UNIT_PX
            self.__is_unset = True
        else:
            self.__is_unset = False

        if (string):
            if (string[-1] == "%"):
                value = float(string[:-1])
                unit = UNIT_PERCENT
            elif (string[-2:] == "cm"):
                value = float(string[:-2])
                unit = UNIT_CM
            elif (string[-2:] == "in"):
                value = float(string[:-2])
                unit = UNIT_IN
            elif (string[-2:] == "pt"):
                value = float(string[:-2])
                unit = UNIT_PT
            else:
                value = int(float(string))
                unit = UNIT_PX


        # the unit type
        self.__unit = unit

        # cache for computed values
        self.__cache_px = (unit != UNIT_PX) and value or None
        self.__cache_pt = (unit == UNIT_PT) and value or None
        self.__cache_cm = (unit == UNIT_CM) and value or None
        self.__cache_in = (unit == UNIT_IN) and value or None
        self.__cache_pcnt = (unit == UNIT_PERCENT) and value or None

        # the scaling factor from inches to percents
        self.__in2pcnt = 0.1

        self.__validate(self.__unit)

        # store value in inches
        self.__original_value = value
        self.__value = self.__unit_to_inch(value, unit)
        self.set_100_percent(100)
        


    #
    # Validates the unit type.
    #
    def __validate(self, unit):

        if (unit not in (UNIT_PX, UNIT_CM, UNIT_IN, UNIT_PT, UNIT_PERCENT)):
            raise TypeError("%s is not a valid UNIT." % (unit,))



    def __unit_to_inch(self, value, unit):

        # trust no one
        self.__validate(unit)

        if (unit == UNIT_IN): return value
        elif (unit == UNIT_CM): return value * self.__CM2IN_SCALE
        elif (unit == UNIT_PX): return value * self.__PX2IN_SCALE
        elif (unit == UNIT_PT): return value * self.__PT2IN_SCALE
        # percentual values get converted at a later time
        elif (unit == UNIT_PERCENT): return value


    def __inch_to_unit(self, value, unit):

        # trust no one
        self.__validate(unit)

        if (unit == UNIT_IN):
            v = value
            self.__cache_in = v
        elif (unit == UNIT_CM):
            v = value * self.__IN2CM_SCALE
            self.__cache_cm = v
        elif (unit == UNIT_PX):
            v = _round(value * self.__IN2PX_SCALE)
            self.__cache_px = v
        elif (unit == UNIT_PT):
            v = value * self.__IN2PT_SCALE
            self.__cache_pt = v
        elif (unit == UNIT_PERCENT):
            v = value * self.__in2pcnt
            self.__cache_pcnt = v

        return v


    def set_100_percent(self, size):
        #assert (size > 0)
        if (size <= 0): size = 1

        # size is a value in pixels, therefore convert it to inches
        size = self.__unit_to_inch(size, UNIT_PX)

        if (self.__unit == UNIT_PERCENT):
            self.__value = (self.__original_value) * (size / 100.0)

        self.__in2pcnt = 100.0 / size

        # invalidate cached values
        self.__cache_pcnt = None
        self.__cache_px = None
        self.__cache_pt = None
        self.__cache_cm = None
        self.__cache_in = None



    def as_px(self):
        return self.__cache_px or self.__inch_to_unit(self.__value, UNIT_PX)

    def as_pt(self):
        return self.__cache_pt or self.__inch_to_unit(self.__value, UNIT_PT)

    def as_cm(self):
        return self.__cache_cm or self.__inch_to_unit(self.__value, UNIT_CM)

    def as_in(self):
        return self.__cache_in or self.__inch_to_unit(self.__value, UNIT_IN)

    def as_percent(self):
        return self.__cache_pcnt or self.__inch_to_unit(self.__value,
                                                        UNIT_PERCENT)


    def get_unit(self): return self.__unit
    def is_unset(self): return self.__is_unset



    #
    # Creates a new Unit object as a copy of this one.
    # TODO: copy the set_100_percent value as well
    #
    def copy(self):

        new = Unit(self.as_pt(), UNIT_PT)
        return new


    def __getitem__(self, unit):

        log("Deprecation:\n"
            "Accessing the Unit object as a dictionary is deprecated. "
            "Please use the appropriate accessor methods instead.", True)

        return self.__inch_to_unit(self.__value, unit)


    def __add__(self, other):

        return Unit(self.as_pt() + other.as_pt(), UNIT_PT)


    def __sub__(self, other):

        return Unit(self.as_pt() - other.as_pt(), UNIT_PT)


    def __mul__(self, other):

        if (isinstance(other, Unit)):
            return Unit(self.as_pt() * other.as_pt(), UNIT_PT)
        else:
            return Unit(self.as_pt() * other, UNIT_PT)


    def __div__(self, other):

        if (isinstance(other, Unit)):
            return Unit(self.as_pt() / other.as_pt(), UNIT_PT)
        else:
            return Unit(self.as_pt() / other, UNIT_PT)


    def __cmp__(self, other):
        assert (other == None or isinstance(other, Unit)), \
               "Cannot compare with non-Unit object"

        if (not other): return 1
        else: return cmp(int(self.as_pt()), int(other.as_pt()))


    def __repr__(self):
        unit = self.get_unit()
        if (self.is_unset()): return "unset"
        if (unit == UNIT_CM): return "%fcm" % self.as_cm()
        elif (unit == UNIT_IN): return "%fin" % self.as_in()
        elif (unit == UNIT_PT): return "%fpt" % self.as_pt()
        elif (unit == UNIT_PX): return "%f" % self.as_px()
        elif (unit == UNIT_PERCENT): return "%f%%" % self.as_percent()


    def __hash__(self):

        return hash(repr(self))

import sys
# for convenience
ZERO = Unit(0, UNIT_PT)
ONE = Unit(1, UNIT_PT)
MAXIMUM = Unit(sys.maxint, UNIT_PT)
UNSET = Unit()


if (__name__ == "__main__"):

    u = Unit(10, UNIT_PX)
    u.set_100_percent(11)
    print u.as_px()
    print u.as_in()
    print u.as_percent()
