# Copyright (C) 2019-2021 Intel Corporation
#
# SPDX-License-Identifier: MIT

import os
import re
from typing import Optional, Tuple

from datumaro.cli.util.errors import WrongRevpathError
from datumaro.components.dataset import Dataset
from datumaro.components.environment import DEFAULT_ENVIRONMENT, Environment
from datumaro.components.errors import DatumaroError, ProjectNotFoundError
from datumaro.components.project import Project, Revision
from datumaro.util.os_util import generate_next_name
from datumaro.util.scope import on_error_do, scoped


def load_project(project_dir, readonly=False):
    return Project(project_dir, readonly=readonly)


def generate_next_file_name(basename, basedir=".", sep=".", ext=""):
    """
    If basedir does not contain basename, returns basename,
    otherwise generates a name by appending sep to the basename
    and the number, next to the last used number in the basedir for
    files with basename prefix. Optionally, appends ext.
    """

    return generate_next_name(os.listdir(basedir), basename, sep, ext)


def parse_dataset_pathspec(s: str, env: Optional[Environment] = None) -> Dataset:
    """
    Parses Dataset paths. The syntax is:
        - <dataset path>[ :<format> ]

    Returns: a dataset from the parsed path
    """

    match = re.fullmatch(
        r"""
        (?P<dataset_path>(?: [^:] | :[/\\] )+)
        (:(?P<format>.+))?
        """,
        s,
        flags=re.VERBOSE,
    )
    if not match:
        raise ValueError("Failed to recognize dataset pathspec in '%s'" % s)
    match = match.groupdict()

    path = match["dataset_path"]
    format = match["format"]
    return Dataset.import_from(path, format, env=env)


@scoped
def parse_revspec(s: str, ctx_project: Optional[Project] = None) -> Tuple[Dataset, Project]:
    """
    Parses Revision paths. The syntax is:
        - <project path> [ @<rev> ] [ :<target> ]
        - <rev> [ :<target> ]
        - <target>
    The second and the third forms assume an existing "current" project.

    Returns: the dataset and the project from the parsed path.
        The project is only returned when specified in the revpath.
    """

    match = re.fullmatch(
        r"""
        (?P<proj_path>(?: [^@:] | :[/\\] )+)
        (@(?P<rev>[^:]+))?
        (:(?P<source>.+))?
        """,
        s,
        flags=re.VERBOSE,
    )
    if not match:
        raise ValueError("Failed to recognize revspec in '%s'" % s)
    match = match.groupdict()

    proj_path = match["proj_path"]
    rev = match["rev"]
    source = match["source"]

    target_project = None

    assert proj_path
    if rev:
        target_project = load_project(proj_path, readonly=True)
        project = target_project
    # proj_path is either proj_path or rev or source name
    elif Project.find_project_dir(proj_path):
        target_project = load_project(proj_path, readonly=True)
        project = target_project
    elif ctx_project:
        project = ctx_project
        if project.is_ref(proj_path):
            rev = proj_path
        elif not source:
            source = proj_path

    else:
        raise ProjectNotFoundError(
            "Failed to find project at '%s'. "
            "Specify project path with '-p/--project' or in the "
            "target pathspec." % proj_path
        )

    if target_project:
        on_error_do(Project.close, target_project, ignore_errors=True)

    tree = project.get_rev(rev)
    return tree.make_dataset(source), target_project


def parse_full_revpath(
    s: str, ctx_project: Optional[Project] = None
) -> Tuple[Dataset, Optional[Project]]:
    """
    revpath - either a Dataset path or a Revision path.

    Returns: the dataset and the project from the parsed path
      The project is only returned when specified in the revpath.
    """

    if ctx_project:
        env = ctx_project.env
    else:
        env = DEFAULT_ENVIRONMENT

    errors = []
    try:
        return parse_revspec(s, ctx_project=ctx_project)
    except (DatumaroError, OSError) as e:
        errors.append(e)

    try:
        return parse_dataset_pathspec(s, env=env), None
    except (DatumaroError, OSError) as e:
        errors.append(e)

    raise WrongRevpathError(problems=errors)


def split_local_revpath(revpath: str) -> Tuple[Revision, str]:
    """
    Splits the given string into revpath components.

    A local revpath is a path to a revision withing the current project.
    The syntax is:
        - [ <revision> : ] [ <target> ]
    At least one part must be present.

    Returns: (revision, build target)
    """

    sep_pos = revpath.find(":")
    if -1 < sep_pos:
        rev = revpath[:sep_pos]
        target = revpath[sep_pos + 1 :]
    else:
        rev = ""
        target = revpath

    return rev, target
