# -*- coding: UTF-8 -*-
# Copyright 2009-2014 by Luc Saffre.
# License: BSD, see file LICENSE for more details.

"""
Documented in :ref:`dpy`.

"""

import logging
logger = logging.getLogger(__name__)


from StringIO import StringIO
import os
import imp
from decimal import Decimal


from django.conf import settings
from django.db import models

from django.utils.module_loading import import_by_path

from django.db import IntegrityError
from django.db.models.fields import NOT_PROVIDED
from django.core.serializers import base
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.models import Session
from django.utils.encoding import smart_unicode, is_protected_type, force_unicode
from django.utils import translation

from djangosite.dbutils import obj2str, sorted_models_list, full_model_name
from north import dbutils

SUFFIX = '.py'


def create_mti_child(parent_model, pk_, child_model, **kw):
    """
    Similar to :func:`insert_child`, but very tricky.
    Used by :mod:`north.dumppy`

    The return value is an "almost normal" model instance,
    whose `save` and `full_clean` methods have been hacked.
    They are the only methods that will be
    called by :class:`north.dpy.Deserializer`.
    You should not use this instance for anything else
    and throw it away when the save() has been called.

    """
    parent_link_field = child_model._meta.parents.get(parent_model, None)
    if parent_link_field is None:
        raise ValidationError("A %s cannot be parent for a %s" % (
            parent_model.__name__, child_model.__name__))
    if True:
        ignored = {}
        for f in parent_model._meta.fields:
            if f.name in kw:
                ignored[f.name] = kw.pop(f.name)
        kw[parent_link_field.name + "_id"] = pk_
        if ignored:
            raise Exception(
                "create_mti_child() %s %s from %s : ignored non-local fields %s" % (
                    child_model.__name__,
                    pk_,
                    parent_model.__name__,
                    ignored))
        child_obj = child_model(**kw)
    else:
        attrs = {}
        attrs[parent_link_field.name + "_id"] = pk_
        #~ for lf in child_model._meta.local_fields:
        # backwards compat 20111211 : python fixtures created by Version 1.2.8 still
        # specify also field values of parent_model. Ignore these silently
        # otherwise Django would also try to create a parent_model record.
        for f, m in child_model._meta.get_fields_with_model():
            if m is None or not issubclass(m, child_model):
            #~ if m is None or m is child_model or not issubclass(m,parent_model):
                if f.name in kw:
                    attrs[f.name] = kw.pop(f.name)
        if kw:
            logging.warning(
                "create_mti_child() %s %s from %s : ignored non-local fields %s",
                child_model.__name__,
                pk_,
                parent_model.__name__,
                kw)

        child_obj = child_model(**attrs)

    def full_clean(*args, **kw):
        pass

    def save(*args, **kw):
        kw.update(raw=True, force_insert=True)
        child_obj.save_base(**kw)

    child_obj.save = save
    child_obj.full_clean = full_clean
    return child_obj


class Serializer(base.Serializer):
    """Serializes a QuerySet to a py stream.

    Usage: ``manage.py dumpdata --format py``

    DEPRECATED. The problem with this approach is that a serializer
   
 creates -by definition- one single file. And Python needs
    -understandabl
y- to load a module completely into memory before it
    can be executed.  Use :manage:`dump2py` instead.

    """

    internal_use_only = False

    write_preamble = True  # may be set to False e.g. by testcases
    models = None

    def serialize(self, queryset, **options):
        self.options = options

        self.stream = options.get("stream", StringIO())
        self.selected_fields = options.get("fields")
        self.use_natural_keys = options.get("use_natural_keys", False)
        if self.write_preamble:
            current_version = settings.SITE.version
            if '+' in current_version:
                logger.warning(
                    "Dumpdata from intermediate version %s" % current_version)

            self.stream.write('# -*- coding: UTF-8 -*-\n')
            self.stream.write('''\
"""
This is a `Python dump <http://north.lino-framework.org>`_
created using %s.
''' % settings.SITE.using_text())
            #~ self.stream.write(settings.SITE.welcome_text())
            self.stream.write('''
"""
from __future__ import unicode_literals
''')
            self.stream.write('SOURCE_VERSION = %r\n' % str(current_version))
            self.stream.write('from decimal import Decimal\n')
            self.stream.write('from datetime import datetime as dt\n')
            self.stream.write('from datetime import time,date\n')
            #~ self.stream.write('from north import dbutils\n')
            self.stream.write('from north.dpy import create_mti_child\n')
            self.stream.write('from north.dbutils import resolve_model\n')
            self.stream.write(
                'from django.contrib.contenttypes.models import ContentType\n')
            self.stream.write('from django.conf import settings\n')
            self.stream.write('''
            
def new_content_type_id(m):
    if m is None: return m
    # if not fmn: return None
    # m = resolve_model(fmn)
    ct = ContentType.objects.get_for_model(m)
    if ct is None: return None
    return ct.pk
    
''')
            #~ s = ','.join([
              #~ '%s=values[%d]' % (k,i)
                #~ for i,k in enumerate(settings.SITE.AVAILABLE_LANGUAGES)])
            s = ','.join([
                '%s=values[%d]' % (lng.name, lng.index)
                for lng in settings.SITE.languages])
            self.stream.write('''
def bv2kw(fieldname,values):
    """
    Needed if `Site.languages` changed between dumpdata and loaddata
    """
    return settings.SITE.babelkw(fieldname,%s)
    
''' % s)
        #~ model = queryset.model
        if self.models is None:
            self.models = sorted_models_list()  # models.get_models()
        if self.write_preamble:
            for model in self.models:
                self.stream.write('%s = resolve_model("%s")\n' % (
                    full_model_name(model, '_'), full_model_name(model)))
        self.stream.write('\n')
        for model in self.models:
            fields = [f for f,
                      m in model._meta.get_fields_with_model() if m is None]
            for f in fields:
                if getattr(f, 'auto_now_add', False):
                    raise Exception("%s.%s.auto_now_add is True : values will be lost!" % (
                        full_model_name(model), f.name))
            #~ fields = model._meta.local_fields
            #~ fields = [f for f in model._meta.fields if f.serialize]
            #~ fields = [f for f in model._meta.local_fields if f.serialize]
            self.stream.write('def create_%s(%s):\n' % (
                model._meta.db_table, ', '.join([f.attname
                                                 for f in fields if not getattr(f, '_lino_babel_field', False)])))
            if model._meta.parents:
                if len(model._meta.parents) != 1:
                    msg = "%s : model._meta.parents is %r" % (
                        model, model._meta.parents)
                    raise Exception(msg)
                pm, pf = model._meta.parents.items()[0]
                child_fields = [f for f in fields if f != pf]
                if child_fields:
                    attrs = ',' + ','.join([
                        '%s=%s' % (f.attname, f.attname)
                        for f in child_fields])
                else:
                    attrs = ''
                #~ self.stream.write('    return insert_child(%s.objects.get(pk=%s),%s%s)\n' % (
                    #~ full_model_name(pm,'_'),pf.attname,full_model_name(model,'_'),attrs))
                self.stream.write('    return create_mt