# (C) Copyright 2018- ECMWF.
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.

from pathlib import Path
import re


class RapsDependencyFile:
    """
    Wrapper class for GNUmake-style dependency files generated by RAPS.
    """

    def __init__(self, content=None, path=None):
        self.path = path
        self.content = content or []

    @classmethod
    def from_file(cls, path):
        re_deps = re.compile(r'(?P<target>.*):\s(?P<deps>[\s\S]*)', re.MULTILINE)
        re_assign = re.compile(r'(?P<target>.*)\s=\s(?P<objs>[\s\S]*)', re.MULTILINE)

        path = Path(path)
        with path.open('r') as f:
            source = f.read()

        content = []
        for block in source.split('\n\n'):
            deps = re_deps.search(block)
            if deps is not None:
                groups = deps.groupdict()
                deps = groups['deps'].split(' \\\n\t')
                deps = [d.strip() for d in deps]
                content.append(Dependency(target=groups['target'], deps=deps))

            assign = re_assign.search(block)
            if assign is not None:
                groups = assign.groupdict()
                objects = groups['objs']
                objects = objects.split('#')[0] if '#' in objects else objects
                objects = [o.strip() for o in objects.split(' \\\n\t')]
                content.append(Assignment(target=groups['target'], objects=objects))

        return cls(content=content, path=path)

    @property
    def content_map(self):
        return {d.target: d for d in self.content}

    def write(self, path=None):
        path = path or self.path

        content = """#
#--- Automatically generated -- please do not edit this file
#

"""
        content += '\n\n'.join(str(c) for c in self.content)
        with Path(path).open('w') as f:
            f.write(content)

    def replace(self, key, replacement):
        """
        Replace specific rule/assignment with another based on the
        targets name as a key.
        """
        for i, o in enumerate(self.content):
            if o.target == key:
                self.content[i] = replacement


class Dependency:

    def __init__(self, target, deps):
        self.target = target
        self.deps = deps

    def __repr__(self):
        deps = ' \\\n\t'.join(self.deps)
        return f'{self.target}: {deps}'

    def find(self, name):
        hits = [d for d in self.deps if name == Path(d).name]
        if len(hits) == 0:
            return None
        return hits if len(hits) > 1 else hits[0]

    def replace(self, target, replacement):
        if target in self.target:
            self.target = self.target.replace(target, replacement)
        for i, dep in enumerate(self.deps):
            if target in dep:
                self.deps[i] = dep.replace(target, replacement)


class Assignment:

    def __init__(self, target, objects):
        self.target = target
        self.objects = objects

    def __repr__(self):
        objects = ' \\\n\t'.join(self.objects)
        return f'{self.target} = {objects}'

    def replace(self, target, replacement):
        if target in self.target:
            self.target = self.target.replace(target, replacement)
        for i, obj in enumerate(self.objects):
            if target in obj:
                self.objects[i] = obj.replace(target, replacement)

    def append_inplace(self, target, replacement):
        for i, obj in enumerate(self.objects):
            if target in obj:
                new = obj.replace(target, replacement)
                self.objects[i:i+1] = [obj, new]


class Rule:

    def __init__(self, target, deps, cmds):
        self.target = target
        self.deps = deps
        self.cmds = cmds

    def __repr__(self):
        deps = ' '.join(self.deps)
        cmds = ' \n\t'.join(self.cmds)
        return f'{self.target}: {deps} \n\t{cmds}'
