from django.db import models
import operator
from django.db.models import Q
from ox.text import smartSplit
from ox.django.fields import DictField
from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.template.loader import render_to_string
from django.contrib.contenttypes.models import ContentType
from insidepages.models import ModuleTab, ModelExtra
from os.path import exists
from sorl.thumbnail import get_thumbnail

def splitSearch(string):
  ret = []
  for s in smartSplit(string):
    word = s.replace("'", "").replace('"', '')
    ret.append(word)
  return ret

#i see the point of this function, but please clean it up: refer to 'except MultipleObjectsReturned' to see why this is here.
def get_real_ctype(module_name):
  for c in ContentType.objects.filter(model=module_name):
    if c.model_class().is_itf_model == True:
      return c 
  return None

class ItfModel(models.Model):
  fts_fields = []
  fk_filters = []
#  related_models = []
  sort_fields = []
  hasComments = True
  title_field = "title"
  is_itf_model = True

  class Meta:
    abstract = True

  def list_dict(self):
    return {}

  def info_dict(self):
    return self.get_dict()

  def get_title(self):
    return self.get(self.title_field)

  def get_tab(self):
    modelextra = self.get_modelextra()
    tab = ModuleTab.objects.filter(model=modelextra)[0]
    return tab

  @classmethod
  def get_add_form(cls):
    try:
      formStr = cls.add_form
#      cls = default_tab.model_class()        
      app_label = cls._meta.app_label
      module = __import__(app_label + ".forms")
      add_form = module.forms.__getattribute__(formStr)
      return add_form
    except:
      return None


  def get_modelextra(self):
    try:
      ctype = ContentType.objects.get(model=self.__class__._meta.module_name)
    except:#FIXME: ideally catch only MultipleObjectsReturned (figure out where to import that from :/ ) #FUCKING way ugly hack to get clashing model ctype names with django internal models working (get_real_ctypes simply checks, if there are multiple content objects with the same 'module_name', which one is_itf_model, and returns that).
      ctype = get_real_ctype(self.__class__._meta.module_name)
#    modelextra = ModelExtra.objects.filter(model=ctype)[0]
    modelextra = ctype.modelextra_set.all()[0]
    return modelextra

  def get_absolute_url(self):
    return "%s/?tab=%s&object_id=%d" % (self.get_module().get_absolute_url(), self.get_tab().slug, self.id)

  def get_module(self):
    tab = self.get_tab()
    if tab:
      return tab.module
    else:
      return None
          


  def get_main_image(self):
    main_image = self.main_image()
    if main_image is not None:
      imgfield = main_image
    elif self.get_modelextra().default_image:
      imgfield = self.get_modelextra().default_image
    else:
      imgfield = None
    if imgfield is not None:
      try:
        thumb =  get_thumbnail(imgfield, "142x150", crop="center").url
      except:
        thumb = ''
    else:
      thumb = ''  # Add default image for site
    return {
        'thumb': thumb
    }


  def main_image(self):
    return None

  def get_template_path(self):
    kls = self.__class__
    return  "modules/%s/%s.html" % (kls._meta.app_label, kls._meta.module_name)

  def get_dict(self):
    return self.get(self._get_fields().keys())

  def insidepage_dict(self):
    try:
        html = render_to_string(self.get_template_path(), self.info_dict())
    except:
        html = "Template path not found at : " + self.get_template_path()
    return {
        'url': self.get_absolute_url(),
        'title': self.get_title(),
        'main_image': self.get_main_image(),
        'html': html
    }



  def get(self, props):
    typ = type(props)
    if typ == list:
      ret = {}
      for p in props:
        ret[p] = self._get_property(p)
      return ret
    elif typ == str:
      return self._get_property(props)
    else:
      return False

  def _get_property(self, prop):
    fields = self._get_fields()
    if prop in fields.keys():
      field_type = fields[prop]
      if field_type in ["TextField", "CharField", "IntegerField"]:
        return self.__getattribute__(prop)
      elif field_type == "ImageField":
        imagefield = self.__getattribute__(prop)
        return imagefield.url if imagefield.name != '' else ''
      elif field_type == "ForeignKey":
        return self._get_fk(prop)
      elif field_type == "ManyToMany":
        return self._get_m2m(prop)
    elif prop.lower() in self.related_models:
      return self._get_related(prop)
    else:
      try:
        val = self.__getattribute__(prop)
        if type(val).__name__ == 'instancemethod' and prop in self.getters:
          return val()
        else:
          return val
      except:
        return False #FIXME

  def _get_fk(self, prop):
    prop = prop.replace("_id", "")
    return self.__getattribute__(prop).get_dict()

#  def _get_related_objects(self, fk_field):
  def _get_m2m(self, prop):
    ret = []
    for o in self.__getattribute__(prop).all():
      ret.append(o.get_dict())
    return ret
    
  def _get_related(self, prop):
    attr = prop.lower() + "_set"
    ret = []
    for o in self.__getattribute__(attr).all():
      ret.append(o.get_dict())
    return ret

  @classmethod
  def _get_fields(kls):
    ret = {}
    for f in kls._meta.fields:
      ret[f.get_attname()] = type(f).__name__
    return ret    


  
  @classmethod
  def fts(kls, qset, search):
    terms = splitSearch(search)
    qobjects = []
    for t in terms:  
      for f in kls.fts_fields:
        qstring = f + '__icontains'
        qobject = Q(**{qstring:t})
        qobjects.append(qobject)
    return qset.filter(reduce(operator.or_, qobjects))

  '''
  eg. fks = {
      'somefield': [1, 5, 7],
      'someotherfield': [3]
      }
  '''
  @classmethod
  def filter_by_fks(kls, qset, fks):
    qobjects = []
    for key in fks.keys():
      field = getField(kls._meta.fields, key)
      if field:
#        rel_class = field.related.parent_model
        for i in fks[key]:
          qobject = Q(**{field.name: i})
          qobjects.append(qobject)
    return qset.filter(reduce(operator.or_, qobjects))               


  @classmethod
  def get_fk_objects(kls):
    ret = {}
    for f in kls.fk_filters:
      ret[f] = []
      field = getField(kls._meta.fields, f)
      rel_class = field.related.parent_model
      for o in rel_class.objects.all():
        ret[f].append({
          'id': o.id,
          'title': unicode(o)
        })  
    return ret

  
  @classmethod
  def get_list(kls, data):
    options = {
      'page': 1,
      'count': 20,
      'search': '',
      'sort': [],
      'range': [0,50],
      'object_id': False
    }
    options.update(data)
    ret = []
    page_no = options['page']
    list_size = options['count']
    try:
      qset = kls.get_qset()
    except:
      qset = kls.objects.all()

    if options['object_id'] != False and options['object_id'] != '':
        object_id = options['object_id']
        qset = qset.exclude(pk=object_id)
        try:
            obj = kls.objects.get(pk=object_id)
            ret.append(obj.list_dict())
        except:
            pass

    search = options['search']
    if search != '':
      qset = kls.fts(qset, search) 
   
    sort = options['sort']

    if sort != []:
      for s in sort:
        if s['operator'] == '-':
          operator = '-'
        else:
          operator = ''
        sort = operator + s['key']
        qset = qset.order_by(sort)
    '''
    r0 = options['range'][0]
    r1 = options['range'][1]
    results = qset[r0:r1]
    '''
    paginator = Paginator(qset, list_size)


    try:
      results = paginator.page(page_no)
    except (EmptyPage, InvalidPage):
      results = paginator.page(paginator.num_pages)
    
    for r in results.object_list:
      ret.append(r.list_dict())

    return {
        'page_no': page_no,
        'num_pages': paginator.num_pages,
        'items': ret
    }

  @classmethod
  def get_qset(kls):
    '''
    Override this method in your model class to define a custom queryset instead of objects.all(), for instance, to always exclude unpublished items.
    '''
    raise NotImplementedError

def getField(fields, name):
  for f in fields:
    if f.name == name:
      return f
  return False



def site_config():
    with open(settings.SITE_CONFIG) as f:
        site_config = json.load(f)
        site_config['keys'] = {}
        for key in site_config['itemKeys']:
            site_config['keys'][key['id']] = key
        site_config['_findKeys'] = {}
        for key in site_config['findKeys']:
            site_config['_findKeys'][key['id']] = key
    return site_config
