import posixpath
from typing import Union, Any, List, Optional, Dict

import rdflib

from . import *
from .utils import parse_class_name


class OwnedObjectPropertyMixin:

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.type_constraint = None

    def from_user(self, value: Any) -> rdflib.Literal:
        desired_type = SBOLObject
        if self.type_constraint:
            desired_type = self.type_constraint
        if not isinstance(value, desired_type):
            raise TypeError(f'Expecting SBOLObject, got {type(value)}')
        return value

    def to_user(self, value: Any) -> str:
        return value

    def _storage(self) -> Dict[str, list]:
        return self.property_owner._owned_objects

    def item_added(self, item: Any) -> None:
        if not self.property_owner.identity_is_url():
            return
        # if not item.display_id:
        #     raise ValueError(f'Item {item} does not have a display_id')
        type_name = parse_class_name(item.type_uri)
        counter_value = self.property_owner.counter_value(type_name)
        new_display_id = f'{type_name}{counter_value}'
        new_url = posixpath.join(self.property_owner.identity, new_display_id)
        for sibling in self._storage()[self.property_uri]:
            if sibling == item:
                continue
            if sibling.identity == new_url:
                raise ValidationError(f'Duplicate URI: {new_url}')
        item._update_identity(new_url, new_display_id)


class OwnedObjectSingletonProperty(OwnedObjectPropertyMixin, SingletonProperty):

    def __init__(self, property_owner: Any, property_uri: str,
                 lower_bound: int, upper_bound: int,
                 validation_rules: Optional[List] = None,
                 initial_value: Optional[str] = None,
                 type_constraint: Optional[Any] = None):
        super().__init__(property_owner=property_owner,
                         property_uri=property_uri,
                         lower_bound=lower_bound,
                         upper_bound=upper_bound,
                         validation_rules=validation_rules)
        # Better to pass this up to super, but the super().__init__
        # methods are not set up properly for that. They would need
        # to accept unknown keyword arguments.
        self.type_constraint = type_constraint
        if initial_value:
            self.set(initial_value)


class OwnedObjectListProperty(OwnedObjectPropertyMixin, ListProperty):

    def __init__(self, property_owner: Any, property_uri: str,
                 lower_bound: int, upper_bound: int,
                 validation_rules: Optional[List] = None,
                 initial_value: Optional[str] = None,
                 type_constraint: Optional[Any] = None):
        super().__init__(property_owner=property_owner,
                         property_uri=property_uri,
                         lower_bound=lower_bound,
                         upper_bound=upper_bound,
                         validation_rules=validation_rules)
        # Better to pass this up to super, but the super().__init__
        # methods are not set up properly for that. They would need
        # to accept unknown keyword arguments.
        self.type_constraint = type_constraint
        if initial_value:
            self.set(initial_value)


def OwnedObject(property_owner: Any, property_uri: str,
                lower_bound: int, upper_bound: Union[int, float],
                validation_rules: Optional[List] = None,
                initial_value: Optional[Union['Identified', List['Identified']]] = None,
                type_constraint: Optional[Any] = None
                ) -> Property:
    if upper_bound == 1:
        return OwnedObjectSingletonProperty(property_owner, property_uri,
                                            lower_bound, upper_bound,
                                            validation_rules, initial_value,
                                            type_constraint=type_constraint)
    return OwnedObjectListProperty(property_owner, property_uri,
                                   lower_bound, upper_bound,
                                   validation_rules, initial_value,
                                   type_constraint=type_constraint)
