"""Resources that represent material spec data objects."""
from collections.abc import Iterator
from uuid import UUID

from citrine._rest.resource import GEMDResource
from citrine._serialization.properties import LinkOrElse, List, Object, Optional, String
from citrine.resources._default_labels import _inject_default_label_tags
from citrine.resources.object_specs import ObjectSpec, ObjectSpecCollection
from gemd.entity.attribute.property_and_conditions import PropertyAndConditions
from gemd.entity.file_link import FileLink
from gemd.entity.link_by_uid import LinkByUID
from gemd.entity.object.material_spec import MaterialSpec as GEMDMaterialSpec
from gemd.entity.object.process_spec import ProcessSpec as GEMDProcessSpec
from gemd.entity.template.material_template import MaterialTemplate as GEMDMaterialTemplate


class MaterialSpec(
    GEMDResource['MaterialSpec'],
    ObjectSpec,
    GEMDMaterialSpec,
    typ=GEMDMaterialSpec.typ
):
    """
    A material specification.

    Parameters
    ----------
    name: str
        Name of the material spec.
    uids: Map[str, str], optional
        A collection of
        `unique IDs <https://citrineinformatics.github.io/gemd-docs/
        specification/unique-identifiers/>`_.
    tags: list[str], optional
        `Tags <https://citrineinformatics.github.io/gemd-docs/specification/tags/>`_
        are hierarchical strings that store information about an entity. They can be used
        for filtering and discoverability.
    notes: str, optional
        Long-form notes about the material spec.
    process: ProcessSpec
        Process that produces this material.
    properties: list[PropertyAndConditions], optional
        Properties of the material, along with an optional list of conditions under which
        those properties are measured.
    template: MaterialTemplate, optional
        A template bounding the valid values for this material's properties.
    file_links: list[FileLink], optional
        Links to associated files, with resource paths into the files API.
    default_labels: list[str], optional
        An optional set of default labels to apply to this material spec.
        Default labels are used to populate labels on the ingredient spec, if none are explicitly
        specified, when the material spec is later used in an ingredient spec.

    """

    _response_key = GEMDMaterialSpec.typ  # 'material_spec'

    name = String('name', override=True, use_init=True)
    process = Optional(LinkOrElse(GEMDProcessSpec), 'process', override=True, use_init=True)
    properties = Optional(List(Object(PropertyAndConditions)), 'properties', override=True)
    template = Optional(LinkOrElse(GEMDMaterialTemplate), 'template', override=True, use_init=True)

    def __init__(self,
                 name: str,
                 *,
                 uids: dict[str, str] | None = None,
                 tags: list[str] | None = None,
                 notes: str | None = None,
                 process: GEMDProcessSpec | None = None,
                 properties: list[PropertyAndConditions] | None = None,
                 template: GEMDMaterialTemplate | None = None,
                 file_links: list[FileLink] | None = None,
                 default_labels: list[str] | None = None):
        if uids is None:
            uids = dict()
        all_tags = _inject_default_label_tags(tags, default_labels)
        super(ObjectSpec, self).__init__()
        GEMDMaterialSpec.__init__(self, name=name, uids=uids,
                                  tags=all_tags, process=process, properties=properties,
                                  template=template, file_links=file_links, notes=notes)

    def __str__(self):
        return '<Material spec {!r}>'.format(self.name)


class MaterialSpecCollection(ObjectSpecCollection[MaterialSpec]):
    """Represents the collection of all material specs associated with a dataset."""

    _individual_key = 'material_spec'
    _collection_key = 'material_specs'
    _resource = MaterialSpec

    @classmethod
    def get_type(cls) -> type[MaterialSpec]:
        """Return the resource type in the collection."""
        return MaterialSpec

    def list_by_template(self,
                         uid: UUID | str | LinkByUID | GEMDMaterialTemplate
                         ) -> Iterator[MaterialSpec]:
        """
        Get the material specs using the specified material template.

        Parameters
        ----------
        uid: UUID | str | LinkByUID | GEMDMaterialTemplate
            A representation of the material template whose material spec usages are to be located.

        Returns
        -------
        Iterator[MaterialSpec]
            The material specs using the specified material template.

        """
        return self._get_relation('material-templates', uid=uid)

    def get_by_process(self,
                       uid: UUID | str | LinkByUID | GEMDProcessSpec
                       ) -> MaterialSpec | None:
        """
        Get output material of a process.

        Parameters
        ----------
        uid: UUID | str | LinkByUID | GEMDProcessSpec
            A representation of the process whose output is to be located.

        Returns
        -------
        MaterialSpec
            The output material of the specified process, or None if no such material exists.

        """
        return next(self._get_relation(relation='process-specs', uid=uid, per_page=1), None)
