# -*- coding: utf-8 -*-

import yaml
import yaml.representer
import datetime

yaml.Dumper.add_representer(unicode, yaml.representer.SafeRepresenter.represent_unicode)

class _YamlUnicodeConstruct(yaml.YAMLObject):

    @classmethod
    def from_yaml(cls, loader, node):
        return cls(**{
            k: unicode(v) if isinstance(v, str) else v
                for (k, v) in loader.construct_mapping(node, deep = True).items()
            })

class Figure(_YamlUnicodeConstruct):

    yaml_tag = u"!figure"

    def __init__(self, value, unit):
        self.value = value
        self.unit = unit

    def get_value(self):
        return self._value

    def set_value(self, value):
        self._value = value

    """ use property() instead of decorator to enable overriding """
    value = property(get_value, set_value)

    def get_unit(self):
        return self._unit

    def set_unit(self, unit):
        assert type(unit) is unicode
        self._unit = unit

    """ use property() instead of decorator to enable overriding """
    unit = property(get_unit, set_unit)

    def __eq__(self, other):
        return type(self) == type(other) and self.value == other.value and self.unit == other.unit

    def __ne__(self, other):
        return not (self == other)

    @classmethod
    def to_yaml(cls, dumper, figure):
        return dumper.represent_mapping(
                cls.yaml_tag,
                {'unit': figure.get_unit(), 'value': figure.get_value()},
                )

class ScalarFigure(Figure):

    yaml_tag = u'!scalar'

    def get_value(self):
        return super(ScalarFigure, self).get_value()

    def set_value(self, value):
        assert type(value) is float
        super(ScalarFigure, self).set_value(value)

    """ use property() instead of decorator to enable overriding """
    value = property(get_value, set_value)

    @classmethod
    def from_yaml(cls, loader, node):
        attr = loader.construct_scalar(node).split(' ')
        return cls(
                value = float(attr[0]),
                unit = attr[1],
                )

    @classmethod
    def to_yaml(cls, dumper, s):
        return dumper.represent_scalar(
                cls.yaml_tag,
                '%s %s' % (repr(s.value), s.unit),
                )

class Distance(ScalarFigure):

    yaml_tag = u'!distance'

    @property
    def metres(self):
        if self.unit == 'km':
            return self.value * 1000
        else:
            raise Exception()

class Sum(ScalarFigure):

    yaml_tag = u'!sum'

    def __init__(self, value, currency):
        super(Sum, self).__init__(value, currency)

    @property
    def currency(self):
        return self.unit

    def get_unit(self):
        return super(Sum, self).get_unit()

    def set_unit(self, currency):
        if currency == u'€':
            currency = u'EUR'
        assert type(currency) is unicode
        assert currency in [u'EUR']
        super(Sum, self).set_unit(currency)

    """ use property() instead of decorator to enable overriding """
    unit = property(get_unit, set_unit)

    @classmethod
    def from_yaml(cls, loader, node):
        attr = loader.construct_scalar(node).split(' ')
        return cls(
                value = float(attr[0]),
                currency = attr[1],
                )

class Discount(yaml.YAMLObject):

    yaml_tag = u'!discount'

    def __init__(
            self,
            name = None,
            amount = None,
            ):
        assert type(name) is unicode
        self.name = name
        assert type(amount) is Sum
        assert amount.value >= 0
        self.amount = amount

    def dict_repr(self):
        return {
            'name': self.name,
            'value': self.amount.value,
            'value_currency': self.amount.currency,
            }

    @staticmethod
    def from_dict(attr):
        return Discount(
                name = attr['name'],
                amount = Sum(attr['value'], attr['value_currency']),
                )

    def __eq__(self, other):
        return type(self) == type(other) and self.name == other.name and self.amount == other.amount

    def __ne__(self, other):
        return not (self == other)

yaml.SafeDumper.add_representer(Discount, lambda dumper, discount: dumper.represent_dict(discount.dict_repr()))

class Order(_YamlUnicodeConstruct):

    yaml_tag = u'!order'

    def __init__(self, platform, order_id, order_date,
            customer_id = None,
            items = None,
            discounts = None,
            ):
        assert type(platform) is unicode
        self.platform = platform
        assert type(order_id) is unicode
        self.order_id = order_id
        if type(order_date) is datetime.datetime:
            order_date = order_date.date()
        assert type(order_date) is datetime.date
        self.order_date = order_date
        assert customer_id is None or type(customer_id) is unicode
        self.customer_id = customer_id
        if items is None:
            items = []
        assert type(items) is list
        self.items = items
        if discounts is None:
            discounts = []
        assert type(discounts) is list
        self.discounts = discounts

    def dict_repr(self):
        return {k: v for (k, v) in {
            'articles': self.items,
            'customer_id': self.customer_id,
            'discounts': self.discounts,
            'order_date': self.order_date.strftime('%Y-%m-%d'),
            'order_id': self.order_id,
            'platform': self.platform,
            }.items() if v is not None}

    @staticmethod
    def from_dict(attr):
        order = Order(
                platform = attr['platform'],
                order_id = attr['order_id'],
                order_date = datetime.datetime.strptime(attr['order_date'], '%Y-%m-%d'),
                )

        if 'customer_id' in attr:
            order.customer_id = attr['customer_id']

        for item in attr['articles']:
            if type(item) is dict:
                item = Item.from_dict(item)
            assert isinstance(item, Item)
            order.items.append(item)

        for discount in attr['discounts']:
            if type(discount) is dict:
                discount = Discount.from_dict(discount)
            assert isinstance(discount, Discount)
            order.discounts.append(discount)

        return order

    def __eq__(self, other):
        return (type(self) == type(other)
                and vars(self) == vars(other))

    def __ne__(self, other):
        return not (self == other)

yaml.SafeDumper.add_representer(Order, lambda dumper, order: dumper.represent_dict(order.dict_repr()))

class Item(_YamlUnicodeConstruct):

    yaml_tag = u'!item'

    def __init__(
            self,
            name = None,
            price_brutto = None,
            ):
        assert type(name) is unicode
        self.name = name
        assert type(price_brutto) is Sum
        self.price_brutto = price_brutto

    def dict_repr(self):
        return {
            'name': self.name,
            'price_brutto': self.price_brutto.value,
            'price_brutto_currency': self.price_brutto.currency,
            }

    @staticmethod
    def from_dict(attr):
        return Item(
                name = attr['name'],
                price_brutto = Sum(attr['price_brutto'], attr['price_brutto_currency']),
                )

    def __eq__(self, other):
        return (type(self) == type(other)
                and vars(self) == vars(other))

    def __ne__(self, other):
        return not (self == other)

yaml.SafeDumper.add_representer(Item, lambda dumper, item: dumper.represent_dict(item.dict_repr()))

class Article(Item):

    yaml_tag = u'!article'

    def __init__(
            self,
            authors = [],
            delivery_date = None,
            quantity = None,
            reseller = None,
            shipper = None,
            state = None,
            **kwargs
            ):
        super(Article, self).__init__(**kwargs)
        assert type(quantity) is int
        self.quantity = quantity
        assert type(authors) is list
        self.authors = authors
        assert state is None or type(state) is unicode
        self.state = state
        assert reseller is None or type(reseller) is unicode
        self.reseller = reseller
        assert shipper is None or type(shipper) is unicode
        self.shipper = shipper
        assert delivery_date is None or type(delivery_date) is datetime.date
        self.delivery_date = delivery_date

    def dict_repr(self):
        attr = super(Article, self).dict_repr()
        attr.update({
            'delivery_date': self.delivery_date,
            'quantity': self.quantity,
            'reseller': self.reseller,
            'shipper': self.shipper,
            'state': self.state,
            })
        if len(self.authors) > 0:
            attr['authors'] = self.authors
        return attr

yaml.SafeDumper.add_representer(Article, lambda dumper, article: dumper.represent_dict(article.dict_repr()))

class Transportation(Item):

    yaml_tag = u'!transportation'

    def __init__(
            self,
            departure_point = None,
            destination_point = None,
            distance = None,
            route_map = None,
            **kwargs
            ):
        super(Transportation, self).__init__(**kwargs)
        assert type(departure_point) is unicode
        self.departure_point = departure_point
        assert type(destination_point) is unicode
        self.destination_point = destination_point
        assert distance is None or type(distance) is Distance
        self.distance = distance
        assert route_map is None or type(route_map) is str
        self.route_map = route_map

    def dict_repr(self):
        attr = super(Transportation, self).dict_repr()
        attr.update({
            'departure_point': self.departure_point,
            'destination_point': self.destination_point,
            'distance_metres': self.distance.metres if self.distance else None,
            'route_map': self.route_map,
            })
        return attr

yaml.SafeDumper.add_representer(Transportation, lambda dumper, transportation: dumper.represent_dict(transportation.dict_repr()))

class TaxiRide(Transportation):

    yaml_tag = u'!taxi-ride'

    def __init__(
            self,
            arrival_time = None,
            departure_time = None,
            driver = None,
            name = None,
            **kwargs
            ):
        if name is None:
            name = u'Taxi Ride'
        super(TaxiRide, self).__init__(name = name, **kwargs)
        assert type(driver) is unicode
        self.driver = driver
        assert arrival_time is None or type(arrival_time) is datetime.datetime
        self.arrival_time = arrival_time
        assert departure_time is None or type(departure_time) is datetime.datetime
        self.departure_time = departure_time

    def dict_repr(self):
        attr = super(TaxiRide, self).dict_repr()
        attr.update({
            'arrival_time': self.arrival_time.strftime('%Y-%m-%d %H:%M') if self.arrival_time else None,
            'departure_time': self.departure_time.strftime('%Y-%m-%d %H:%M') if self.departure_time else None,
            'driver': self.driver,
            })
        return attr

yaml.SafeDumper.add_representer(TaxiRide, lambda dumper, taxi_ride: dumper.represent_dict(taxi_ride.dict_repr()))