import json
import os
import shutil
from pathlib import Path
from typing import Dict, Optional


class AbstractCacheMark(object):
    DEFAULT_MARK_NAME_PREFIX = ".cache_mark_"

    @classmethod
    def get_mark_path(cls, path: Path, name=None) -> Path:
        if name:
            return path / f"{cls.DEFAULT_MARK_NAME_PREFIX}{name}"
        return path

    @classmethod
    def read_mark(cls, cache_mark_path: Path, name=None) -> Optional[dict]:
        cache_mark_path = cls.get_mark_path(cache_mark_path, name)
        try:
            mark = cache_mark_path.read_text(encoding="utf-8")
        except FileNotFoundError:
            pass  # missing
        else:
            try:
                return json.loads(mark)
            except Exception:
                pass  # encoding error is not recoverable, act as if it was missing

    def check(self, cache_mark_path: Path, name=None) -> bool:
        cache_mark_path = self.get_mark_path(cache_mark_path, name)
        if cache_mark_path.exists():
            with open(cache_mark_path, "r", encoding="utf-8") as f:
                cached_value = json.load(f)
            if cached_value == self._current_value():
                return True
        return False

    def write_mark(self, cache_mark_path: Path, name=None):
        cache_mark_path = self.get_mark_path(cache_mark_path, name)
        with cache_mark_path.open("w", encoding="utf-8") as f:
            json.dump(self._current_value(), f)

    def _current_value(self) -> dict:
        raise NotImplementedError()


class CacheMarkByDict(AbstractCacheMark):
    def __init__(self, current_value):
        self.current_value = current_value

    def _current_value(self) -> dict:
        return self.current_value


class CacheMarkByFiles(AbstractCacheMark):
    @classmethod
    def create_from_template_directory(cls, template_path: Path):
        items = dict()
        for parent_path, _, file_names in os.walk(template_path):
            for file_name in file_names:
                file_path = Path(parent_path) / file_name
                items[file_path.relative_to(template_path).as_posix()] = file_path.read_text("utf-8")
        return cls(items)

    def __init__(self, items: Dict[str, str]):
        self.items = items

    def _current_value(self) -> dict:
        return self.items

    def write_items(self, target_path: Path):
        for relative_path, value in self.items.items():
            path = target_path / relative_path
            path.parent.mkdir(parents=True, exist_ok=True)
            path.write_text(value, encoding="utf-8")

    def remove_items_by_template_root_names(self, target_path: Path, *extra_names):
        names = set(extra_names)
        for p in self.items.keys():
            names.add(Path(p).parts[0])
        for name in names:
            item = target_path / name
            if item.exists():
                if item.is_dir():
                    shutil.rmtree(item)
                else:
                    item.unlink()
