"""
interface: user interface for sourdough project construction and iteration
Corey Rayburn Yung <coreyrayburnyung@gmail.com>
Copyright 2020-2021, Corey Rayburn Yung
License: Apache-2.0 (https://www.apache.org/licenses/LICENSE-2.0)

Contents: 
    Manager (Element, Validator):
    Project (Workflow, Element): access point and interface for creating and 
        implementing sourdough projects.

"""
from __future__ import annotations
import dataclasses
import inspect
import pathlib
from typing import (Any, Callable, ClassVar, Dict, Iterable, List, Mapping, 
                    Optional, Sequence, Set, Tuple, Type, Union)
import warnings

import sourdough
from . import base


@dataclasses.dataclass
class Project(sourdough.quirks.Base, sourdough.Validator, 
              sourdough.types.Lexicon):
    """Directs construction of a sourdough project.
    
    Args:
        name (str): designates the name of a class instance that is used for 
            internal referencing throughout sourdough. For example, if a 
            sourdough instance needs settings from a Configuration 
            instance, 'name' should match the appropriate section name in a 
            Configuration instance. Defaults to None. 
        identification (str): a unique identification name for a sourdough
            Project. The name is used for creating file folders related to the 
            project. If it is None, a str will be created from 'name' and the 
            date and time. Defaults to None.   
        settings (Union[sourdough.base.Settings, str, pathlib.Path]]): a
            Settings-compatible subclass or instance, a str or pathlib.Path 
            containing the file path where a file of a supported file type with 
            settings for a Configuration instance is located. Defaults to 
            the default Configuration instance.
        filer (Union[Type, str, pathlib.Path]]): a Clerk-compatible class or a 
            str or pathlib.Path containing the full path of where the root 
            folder should be located for file input and output. A 'filer' must 
            contain all file path and import/export methods for use throughout 
            sourdough. Defaults to the default Clerk instance. 
        automatic (bool): whether to automatically advance 'worker' (True) or 
            whether the worker must be advanced manually (False). Defaults to 
            True.
        data (Any): any data object for the project to be applied. If it is
            None, an instance will still execute its workflow, but it won't
            apply it to any external data. Defaults to None.  
        results (Union[base.Results, Type[base.Results], str]): a Results 
            container object, a Results subclass, or a str matching a key in 
            'bases.results' for any project results to be stored. Defaults to 
            an empty Results instance.
        validations (List[str]): a list of attributes that need validating.
            Each item in 'validations' should have a corresponding method named 
            f'_validate_{name}' or match a key in 'validators'. Defaults to a 
            list of attributes listed in the dataclass field.
    
    Attributes:
        project
        bases
        library
        validators
        
    """
    name: str = None
    settings: Union[base.Settings, Type[base.Settings], pathlib.Path, str, 
                    Mapping[str, Mapping[str, Any]]] = None
    filer: Union[base.Filer, Type[base.Filer], pathlib.Path, str] = None
    identification: str = None
    outline: base.Stage = None
    workflow: base.Stage = None
    summary: base.Stage = None
    automatic: bool = True
    data: Any = None
    stages: ClassVar[Sequence[Union[str, base.Stage]]] = [
        'outline', 'workflow', 'summary']
    validations: ClassVar[Sequence[str]] = [
        'settings', 'name', 'identification', 'filer']
    
    """ Initialization Methods """

    def __post_init__(self) -> None:
        """Initializes class instance attributes."""
        # Calls parent and/or mixin initialization method(s).
        try:
            super().__post_init__()
        except AttributeError:
            pass
        # Removes various python warnings from console output.
        warnings.filterwarnings('ignore')
        # Calls validation methods.
        self.validate(validations = self.validations)
        # Adds 'general' section attributes from 'settings'.
        self.settings.inject(instance = self)
        # Calls 'create' and 'execute' if 'automatic' is True.
        if self.automatic:
            self.create()

    """ Public Methods """

    def create(self) -> None:
        """[summary]

        Raises:
            TypeError: [description]

        Returns:
            [type]: [description]
            
        """
        for item in self.stages:
            if isinstance(item, str):
                stage = self.bases.stage.library.borrow(names = item)
            elif inspect.isclass(item) and issubclass(item, self.bases.stage):
                stage = item
            else:
                raise TypeError(
                    f'Items in stages must be str or {self.bases.stage} '
                    f'subclasses (not instances)')
            kwargs = stage.needify(instance = self)
            setattr(self, item, stage.create(**kwargs))
            print('test product', getattr(self, item))
        return self 

    def execute(self, **kwargs) -> None:
        """ """
        pass
                        
    """ Private Methods """

    def _validate_name(self, name: str) -> str:
        """Creates 'name' if one doesn't exist.
        
        If 'name' was not passed, this method first tries to infer 'name' as the 
        first appropriate section name in 'settings'. If that doesn't work, it 
        uses the snakecase name of the class.
        
        Args:
            name (str): name of the project, if passed.
            
        Returns:
            str: a default name, if none was passed. But if the passed 'name'
                exists, it is returned unchanged. 
            
        """
        if not name:
            sections = self.settings.excludify(subset = self.settings.skip)
            try:
                name = sections.keys()[0]
            except IndexError:
                name = sourdough.tools.snakify(self.__class__)
        return name

    def _validate_identification(self, identification: str) -> str:
        """Creates unique 'identification' if one doesn't exist.
        
        By default, 'identification' is set to the 'name' attribute followed by
        an underscore and the date and time.
        
        Args:
            identification (str): unique identification string for a project.
            
        Returns:
            str: a default identification, derived from the project's 'name'
                attribute and the date and time.
        
        """
        if not identification:
            identification = sourdough.tools.datetime_string(prefix = self.name)
        return identification
