"""
Annalist record field description
"""

from __future__ import unicode_literals
from __future__ import absolute_import, division, print_function

__author__      = "Graham Klyne (GK@ACM.ORG)"
__copyright__   = "Copyright 2014, G. Klyne"
__license__     = "MIT (http://opensource.org/licenses/MIT)"

import logging
log = logging.getLogger(__name__)

import os
import os.path
import shutil

from django.conf import settings

from annalist                       import layout
from annalist.identifiers           import ANNAL, RDFS
from annalist.models.entity         import Entity
from annalist.models.entitydata     import EntityData
from annalist.models.recordgroup    import RecordGroup, RecordGroup_migration
from annalist.util                  import (
    split_type_entity_id, extract_entity_id, make_type_entity_id
    )
from annalist.exceptions            import Annalist_Error

class RecordField(EntityData):

    _entitytype     = ANNAL.CURIE.Field
    _entitytypeid   = layout.FIELD_TYPEID
    _entityroot     = layout.COLL_FIELD_PATH
    _entityview     = layout.COLL_FIELD_VIEW
    _entityfile     = layout.FIELD_META_FILE

    def __init__(self, parent, field_id):
        """
        Initialize a new RecordField object, without metadta (yet).

        parent      is the parent collection to which the field belongs.
        field_id    the local identifier for the record field
        """
        # assert altparent, "RecordField instantiated with no altparent"
        super(RecordField, self).__init__(parent, field_id)
        self._parent = parent
        # log.debug("RecordField %s"%(field_id))
        return

    def get_property_uri(self):
        """
        Return field's property URI
        """
        return self.get(ANNAL.CURIE.property_uri, "@@undefined_property_uri@@")

    def _migrate_filenames(self):
        """
        Override EntityData method
        """
        return None

    def _map_entity_field_enum_val(self, entitydata, key, type_id, old_enum_val, new_enum_val):
        """
        Map enumerated value of specified type
        """
        if key in entitydata:
            type_id_here, enum_val_here = split_type_entity_id(entitydata[key])
            if type_id_here == type_id and enum_val_here == old_enum_val:
                entitydata[key] = make_type_entity_id(type_id, new_enum_val)
        return entitydata

    def _migrate_values(self, entitydata):
        """
        Field description entity format migration method.

        The specification for this method is that it returns an entitydata value
        which is a copy of the supplied entitydata with format migrations applied.

        NOTE:  implementations are free to apply migrations in-place.  The resulting 
        entitydata should be exactly as the supplied data *should* appear in storage
        to conform to the current format of the data.  The migration function should 
        be idempotent; i.e.
            x._migrate_values(x._migrate_values(e)) == x._migrate_values(e)
        """
        field_id = entitydata[ANNAL.CURIE.id]
        migration_map = (
            [ (ANNAL.CURIE.options_typeref,     ANNAL.CURIE.field_ref_type       )
            , (ANNAL.CURIE.restrict_values,     ANNAL.CURIE.field_ref_restriction)
            , (ANNAL.CURIE.target_field,        ANNAL.CURIE.field_ref_field      )
            , (ANNAL.CURIE.field_target_type,   ANNAL.CURIE.field_value_type     )
            ])
        entitydata = self._migrate_values_map_field_names(migration_map, entitydata)
        # Fix up enumerated values to use new enumeration type names
        field_enum_types = (
            [ (ANNAL.CURIE.field_render_type, "_enum_render_type")
            , (ANNAL.CURIE.field_value_mode,  "_enum_value_mode")
            ])
        for fkey, ftype in field_enum_types:
            if fkey in entitydata and entitydata[fkey]:
                entitydata[fkey] = make_type_entity_id(
                    ftype, extract_entity_id(entitydata[fkey])
                    )
        # If comment and no tooltip, create tooltip and update comment
        if (RDFS.CURIE.comment in entitydata) and (ANNAL.CURIE.tooltip not in entitydata):
            label   = entitydata.get(RDFS.CURIE.label, "Field '%s'"%field_id)
            comment = entitydata[RDFS.CURIE.comment]
            entitydata[ANNAL.CURIE.tooltip] = comment
            entitydata[RDFS.CURIE.comment]  = "# %s\r\n\r\n%s"%(label, comment)
        # If reference to field group, copy group field list inline
        if ANNAL.CURIE.group_ref in entitydata:
            group_type_id, group_id = split_type_entity_id(
                entitydata[ANNAL.CURIE.group_ref], default_type_id=layout.GROUP_TYPEID
                )
            if group_id != "":
                log.info("Migrating group reference %s in field %s"%(group_id, field_id))
                group_obj = RecordGroup_migration.load(self._parent, group_id)
                if not group_obj:
                    msg = (
                        "Failed to load group '%s' for field '%s' in collection '%s'"%
                        (group_id, field_id, self._parent.get_id())
                        )
                    log.warning(msg)
                    self.set_error(msg)
                    # raise Annalist_Error(msg)
                else:
                    field_value_type = entitydata[ANNAL.CURIE.field_value_type]
                    group_entity_type = group_obj[ANNAL.CURIE.group_entity_type]
                    if field_value_type and group_entity_type and field_value_type != group_entity_type:
                        log.warning(
                            "Group %s entity type %s differs from field %s value type %s"%
                            (group_id, group_entity_type, field_id, field_value_type)
                            )
                    entitydata[ANNAL.CURIE.field_fields] = group_obj[ANNAL.CURIE.group_fields]
            del entitydata[ANNAL.CURIE.group_ref]
        # Default render type to "Text"
        if ANNAL.CURIE.field_render_type not in entitydata:
            entitydata[ANNAL.CURIE.field_render_type] = "_enum_render_type/Text"
        # Migrate changed render type names
        entitydata = self._map_entity_field_enum_val(
            entitydata, ANNAL.CURIE.field_render_type, "_enum_render_type", 
            "RepeatGroup", "Group_Seq"
            )
        entitydata = self._map_entity_field_enum_val(
            entitydata, ANNAL.CURIE.field_render_type, "_enum_render_type", 
            "RepeatGroupRow", "Group_Seq_Row"
            )
        entitydata = self._map_entity_field_enum_val(
            entitydata, ANNAL.CURIE.field_render_type, "_enum_render_type", 
            "Slug", "EntityRef"
            )
        # Calculate mode from other fields if not defined
        val_render = entitydata[ANNAL.CURIE.field_render_type]
        ref_type  = entitydata.get(ANNAL.CURIE.field_ref_type, None)
        ref_field = entitydata.get(ANNAL.CURIE.field_ref_field, None)
        if ANNAL.CURIE.field_value_mode in entitydata:
            val_mode = entitydata[ANNAL.CURIE.field_value_mode]
        else:
            val_mode  = "Value_direct"
            if ref_type and ref_field:
                val_mode = "Value_field"
            elif val_render == "RefMultifield":
                val_mode = "Value_entity"
            elif val_render == "URIImport":
                val_mode = "Value_import"
            elif val_render == "FileUpload":
                val_mode = "Value_upload"
            entitydata[ANNAL.CURIE.field_value_mode] = val_mode
        # Consistency checks
        if val_mode == "Value_field":
            if ( not (ref_type and ref_field) ):
               log.warning(
                    "RecordField %s: val_mode 'Value_field' requires values for %s and %s"%
                        (field_id, ANNAL.CURIE.field_ref_type, ANNAL.CURIE.field_ref_field)
                    )
        elif val_mode == "Value_entity":
            if not ref_type:
               log.warning(
                    "RecordField %s: val_mode 'Value_entity' requires value for %s"%
                        (field_id, ANNAL.CURIE.field_ref_type)
                    )
            if ref_field:
               log.warning(
                    "RecordField %s: val_mode 'Value_entity' should not define value for %s"%
                        (field_id, ANNAL.CURIE.field_ref_field)
                    )
        # Return result
        return entitydata

    def _pre_save_processing(self, entitydata):
        """
        Pre-save value processing.

        This method is called just before a value is saved to fill in or update
        any values that were not specified in the form input.

        The specification for this method is that it returns an entitydata value
        which is a copy of the supplied entitydata with any data updates applied.

        NOTE:  implementations are free to apply updates in-place.  The resulting 
        entitydata should be exactly as the supplied data *should* appear in storage.
        The update function should be idempotent; i.e.
            x._pre_save_processing(x._pre_save_processing(e)) == x._pre_save_processing(e)
        """
        if not entitydata.get(ANNAL.CURIE.property_uri, None):
            entitydata[ANNAL.CURIE.property_uri] = entitydata[ANNAL.CURIE.id]
        return entitydata

    def _post_update_processing(self, entitydata, post_update_flags):
        """
        Post-update processing.

        This method is called when a RecordField entity has been created or updated.  

        It invokes the containing collection method to regenerate the JSON LD context 
        for the collection to which the field belongs.
        """
        self._parent.cache_add_field(self)
        self._parent.generate_coll_jsonld_context(flags=post_update_flags)
        return entitydata

    def _post_remove_processing(self, post_update_flags):
        """
        Post-remove processing.

        This method is called when a RecordField entity has been removed.  
        """
        self._parent.cache_remove_field(self.get_id())
        return




# End.
