from collections import OrderedDict
from itertools import chain

from staff.lib.models import smart_model
from staff.oebs.reports.fields import maybe_wrap_field

from .utils import is_primitive


class Report(object):
    model = None
    queryset = None
    fields = {}
    order = ()
    caches = ()

    def _init_caches(self):
        return {
            cache_class: cache_class(*fields)
            for cache_class, fields in self.caches
        }

    def __init__(self):
        self._caches = self._init_caches()

    def get_cache(self, cache_class):
        return self._caches[cache_class]

    def __iter__(self):
        yield (field.verbose_name for field in self.get_fields())

        queryset = (
            self.get_queryset()
            .values(*self.values)
            .order_by(*self.order)
        )
        for obj in queryset:
            yield (
                self.get_value(obj, field, transform)
                for field, transform in self.get_fields().items()
            )

    def get_model(self):
        return smart_model(self.model) or self.queryset.model

    def get_queryset(self):
        if self.queryset is not None:
            return self.queryset
        else:
            return self.get_model().objects.all()

    def filter_fields(self, primitive):
        return {
            k: v
            for k, v in self.get_fields().items()
            if is_primitive(k) == primitive
        }

    @property
    def complex_fields(self):
        return self.filter_fields(False)

    @property
    def values(self):
        return set(chain(*(f.required_values for f in self.complex_fields)))

    def apply_transform(self, raw_value, transform):
        return raw_value if transform is None else transform(raw_value)

    def get_value(self, obj, field, transform):
        raw = field(obj, self)
        return self.apply_transform(raw, transform)

    def get_fields(self):
        return OrderedDict(self._equalize_fields())

    def _equalize_fields(self):
        return (
            (maybe_wrap_field(self.get_model(), spec['name']),
             spec['transform'])
            for spec in self.fields
        )
