from projex.lazymodule import lazy_import
from .collector import Collector

orb = lazy_import('orb')

class ReverseLookup(Collector):
    def __json__(self):
        output = super(ReverseLookup, self).__json__()
        output['model'] = self.__reference
        output['target'] = self.targetColumn().field()
        return output

    def __init__(self, from_column='', reference='', target='', removeAction='unset', **options):
        if from_column:
            reference, _, target = from_column.partition('.')

        options['model'] = reference
        super(ReverseLookup, self).__init__(**options)

        if removeAction not in ('unset', 'delete'):
            raise orb.errors.ValidationError('The remove action must be either "unset" or "delete".')

        # custom options
        self.__reference = reference
        self.__removeAction = removeAction
        self.__target = target

    def collect(self, record, **context):
        if not record.isRecord():
            return orb.Collection()
        else:
            model = self.referenceModel()

            # create the pipe query
            q  = orb.Query(model, self.__target) == record

            context['where'] = q & context.get('where')
            return model.select(record=record, collector=self, **context)

    def collectExpand(self, query, parts, **context):
        rmodel = self.referenceModel()
        sub_q = query.copy()
        sub_q._Query__column = '.'.join(parts[1:])
        sub_q._Query__model = rmodel
        return rmodel.select(columns=[self.targetColumn()], where=sub_q)

    def copy(self):
        out = super(ReverseLookup, self).copy()
        out._ReverseLookup__reference = self.__reference
        out._ReverseLookup__target = self.__target
        return out

    def removeAction(self):
        """
        Defines the action that should be taken when a model is removed from the collection generated
        by this reverse lookup.  The default action is 'unset', which will simply de-reference the source
        model from the target.  If you set the action to 'delete', then the target model will be fully removed
        from the database when being removed.

        :return:  <str>
        """
        return self.__removeAction

    def referenceModel(self):
        schema = orb.system.schema(self.__reference)
        if schema is not None:
            return schema.model()
        else:
            raise orb.errors.ModelNotFound(schema=self.__reference)

    def setRemoveAction(self, action):
        """
        Sets the remove action that should be taken when a model is removed from the collection generated by
        this reverse lookup.  Valid actions are "unset" or "delete", any other values will raise an exception.

        :param action: <str>
        """
        if action not in ('unset', 'delete'):
            raise orb.errors.ValidationError('The remove action must be either "unset" or "delete"')
        else:
            self.__removeAction = action

    def targetColumn(self):
        schema = orb.system.schema(self.__reference)
        try:
            return schema.column(self.__target)
        except AttributeError:
            raise orb.errors.ModelNotFound(schema=self.__reference)

