#!/usr/bin/env python3
#
# Copyright (c) 2020 Arm Limited
# All rights reserved
#
# The license below extends only to copyright in the software and shall
# not be construed as granting a license to any other intellectual
# property including but not limited to intellectual property relating
# to a hardware implementation of the functionality of the software
# licensed hereunder.  You may use the software subject to the license
# terms below provided that you ensure that this notice is replicated
# unmodified and in its entirety in all distributions of the software,
# modified or unmodified, in source code or in binary form.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import email.utils
import enum
import os
from typing import Any, Dict, Iterator, List, Mapping, Optional, Sequence, \
    TextIO, Tuple, Union

import yaml

PathOrFile = Union[TextIO, str]

class FileFormatException(Exception):
    pass

class MissingFieldException(FileFormatException):
    pass

class IllegalValueException(FileFormatException):
    pass

class Status(enum.Enum):
    MAINTAINED = enum.auto()
    ORPHANED = enum.auto()

    @classmethod
    def from_str(cls, key: str) -> 'Status':
        _status_dict = {
            'maintained': cls.MAINTAINED,
            'orphaned': cls.ORPHANED,
        }
        return _status_dict[key]

    def __str__(self) -> str:
        return {
            Status.MAINTAINED: 'maintained',
            Status.ORPHANED: 'orphaned',
        }[self]

class Subsystem(object):
    tag: str
    status: Status
    maintainers: List[Tuple[str, str]] # Name, email
    description: str

    def __init__(self, tag: str,
                 maintainers: Optional[Sequence[Tuple[str, str]]],
                 description: str = '',
                 status: Status = Status.ORPHANED):
        self.tag = tag
        self.status = status
        self.maintainers = list(maintainers) if maintainers is not None else []
        self.description = description if description is not None else ''

class Maintainers(object):
    DEFAULT_MAINTAINERS = os.path.join(os.path.dirname(__file__),
                                       '../../../MAINTAINERS.yaml')

    _subsystems: Dict[str, Subsystem] # tag -> Subsystem

    def __init__(self, ydict: Mapping[str, Any]):
        self._subsystems = {}
        for tag, maint in list(ydict.items()):
            self._subsystems[tag] = Maintainers._parse_subsystem(tag, maint)

    @classmethod
    def from_file(cls, path_or_file: Optional[PathOrFile] = None) \
        -> "Maintainers":

        return cls(Maintainers._load_maintainers_file(path_or_file))

    @classmethod
    def from_yaml(cls, yaml_str: str) -> "Maintainers":
        return cls(yaml.load(yaml_str, Loader=yaml.SafeLoader))

    @classmethod
    def _load_maintainers_file(cls,
                               path_or_file: Optional[PathOrFile] = None) \
                               -> Mapping[str, Any]:
        if path_or_file is None:
            path_or_file = cls.DEFAULT_MAINTAINERS

        if isinstance(path_or_file, str):
            with open(path_or_file, 'r') as fin:
                return yaml.load(fin, Loader=yaml.SafeLoader)
        else:
            return yaml.load(path_or_file, Loader=yaml.SafeLoader)

    @classmethod
    def _parse_subsystem(cls, tag: str, ydict: Mapping[str, Any]) -> Subsystem:
        def required_field(name):
            try:
                return ydict[name]
            except KeyError:
                raise MissingFieldException(
                    f"{tag}: Required field '{name}' is missing")

        maintainers: List[Tuple[str, str]] = []
        raw_maintainers = ydict.get('maintainers', [])
        if not isinstance(raw_maintainers, Sequence):
            raise IllegalValueException(
                f"{tag}: Illegal field 'maintainers' isn't a list.")
        for maintainer in raw_maintainers:
            name, address = email.utils.parseaddr(maintainer)
            if name == '' and address == '':
                raise IllegalValueException(
                    f"{tag}: Illegal maintainer field: '{maintainer}'")
            maintainers.append((name, address))

        try:
            status = Status.from_str(required_field('status'))
        except KeyError:
            raise IllegalValueException(
                f"{tag}: Invalid status '{ydict['status']}'")

        return Subsystem(tag, maintainers=maintainers, status=status,
                         description=ydict.get('desc', ''))

    def __iter__(self) -> Iterator[Tuple[str, Subsystem]]:
        return iter(list(self._subsystems.items()))

    def __getitem__(self, key: str) -> Subsystem:
        return self._subsystems[key]

def _main():
    maintainers = Maintainers.from_file()
    for tag, subsys in maintainers:
        print(f'{tag}: {subsys.description}')
        print(f'  Status: {subsys.status}')
        print(f'  Maintainers:')
        for maint in subsys.maintainers:
            print(f'    - {maint[0]} <{maint[1]}>')
        print()

if __name__ == '__main__':
    _main()

__all__ = [
    "FileFormatException",
    "MissingFieldException",
    "IllegalValueException",
    "Status",
    "Subsystem",
    "Maintainers",
]
