# Copyright (c) 2022 Project CHIP Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import fnmatch
import logging
import os
from dataclasses import dataclass, field
from typing import Iterator, List, Optional

from .types import IdlFileType, InputIdlFile
from .using_codegen import (CodegenCppAppPregenerator, CodegenCppClustersTLVMetaPregenerator,
                            CodegenCppProtocolsTLVMetaPregenerator, CodegenJavaClassPregenerator, CodegenJavaJNIPregenerator)
from .using_zap import ZapApplicationPregenerator


def _IdlsInDirectory(top_directory_name: str, truncate_length: int):
    for root, dirs, files in os.walk(top_directory_name):
        for file in files:
            if file.endswith('.zap'):
                yield InputIdlFile(file_type=IdlFileType.ZAP,
                                   full_path=os.path.join(root, file),
                                   relative_path=os.path.join(root[truncate_length:], file))
            if file.endswith('.matter'):
                yield InputIdlFile(file_type=IdlFileType.MATTER,
                                   full_path=os.path.join(root, file),
                                   relative_path=os.path.join(root[truncate_length:], file))


def _FindAllIdls(sdk_root: str, external_roots: Optional[List[str]]) -> Iterator[InputIdlFile]:
    relevant_subdirs = [
        'examples',  # all example apps
        'src',      # realistically only controller/data_model
    ]

    while sdk_root.endswith('/'):
        sdk_root = sdk_root[:-1]
    sdk_root_length = len(sdk_root)

    # first go over SDK items
    for subdir_name in relevant_subdirs:
        top_directory_name = os.path.join(sdk_root, subdir_name)
        logging.debug(f"Searching {top_directory_name}")
        for idl in _IdlsInDirectory(top_directory_name, sdk_root_length+1):
            yield idl

    # next external roots
    if external_roots:
        for root in external_roots:
            root = os.path.normpath(root)
            for idl in _IdlsInDirectory(root, len(root) + 1):
                yield idl


@dataclass
class TargetFilter:
    # If set, only the specified files are accepted for codegen
    file_type: Optional[IdlFileType] = None

    # If non-empty only the given paths will be code-generated
    path_glob: List[str] = field(default_factory=list)


# TODO: the build GlobMatcher is more complete by supporting `{}` grouping
#       For now this limited glob seems sufficient.
class GlobMatcher:
    def __init__(self, pattern: str):
        self.pattern = pattern

    def matches(self, s: str):
        return fnmatch.fnmatch(s, self.pattern)


def FindPregenerationTargets(sdk_root: str, external_roots: Optional[List[str]], filter: TargetFilter, runner):
    """Finds all relevand pre-generation targets in the given
       SDK root.

       Pre-generation targets are based on zap and matter files with options
       on what rules to pregenerate and how.
    """

    generators = [
        # Jinja-based codegen
        CodegenJavaJNIPregenerator(sdk_root),
        CodegenJavaClassPregenerator(sdk_root),
        CodegenCppAppPregenerator(sdk_root),
        CodegenCppClustersTLVMetaPregenerator(sdk_root),
        CodegenCppProtocolsTLVMetaPregenerator(sdk_root),

        # ZAP codegen
        ZapApplicationPregenerator(sdk_root),
    ]

    path_matchers = [GlobMatcher(pattern) for pattern in filter.path_glob]

    for idl in _FindAllIdls(sdk_root, external_roots):
        if filter.file_type is not None:
            if idl.file_type != filter.file_type:
                logging.debug(f"Will not process file of type {idl.file_type}: {idl.relative_path}")
                continue

        if path_matchers:
            if all([not matcher.matches(idl.relative_path) for matcher in path_matchers]):
                logging.debug(f"Glob not matched for {idl.relative_path}")
                continue

        for generator in generators:
            if generator.Accept(idl):
                yield generator.CreateTarget(idl, runner=runner)
