# -*- coding: utf-8 -*-
#
# Copyright 2018-2020 - Swiss Data Science Center (SDSC)
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
# Eidgenössische Technische Hochschule Zürich (ETHZ).
#
# 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.

from pathlib import Path

import pytest

from renku.core.models.cwl.command_line_tool import CommandLineToolFactory
from renku.core.models.entities import Collection, Entity


def test_1st_tool(client):
    """Check creation of 1st tool example from args."""
    tool = CommandLineToolFactory(("echo", "Hello world!")).generate_process_run(
        client=client, commit=client.repo.head.commit, path="dummy.yaml"
    )

    tool = tool.association.plan
    assert "Hello world!" == tool.arguments[0].value


def test_03_input(client):
    """Check the essential input parameters."""
    whale = Path(client.path) / "whale.txt"
    whale.touch()

    client.repo.index.add([str(whale)])
    client.repo.index.commit("add whale.txt")

    argv = [
        "echo",
        "-f",
        "-i42",
        "--example-string",
        "hello",
        "--file=whale.txt",
    ]
    tool = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path).generate_process_run(
        client=client, commit=client.repo.head.commit, path="dummy.yaml"
    )

    tool = tool.association.plan

    assert ["-f"] == tool.arguments[0].to_argv()

    assert 42 == tool.arguments[1].value
    assert "-i" == tool.arguments[1].prefix

    assert "hello" == tool.arguments[2].value
    assert "--example-string " == tool.arguments[2].prefix

    assert tool.inputs[0].consumes.path == "whale.txt"
    assert isinstance(tool.inputs[0].consumes, Entity)
    assert not isinstance(tool.inputs[0].consumes, Collection)
    assert "--file=" == tool.inputs[0].prefix

    assert argv == tool.to_argv()


def test_base_command_detection(client):
    """Test base command detection."""
    hello = Path(client.path) / "hello.tar"
    hello.touch()

    client.repo.index.add([str(hello)])
    client.repo.index.commit("add hello.tar")

    argv = ["tar", "xf", "hello.tar"]
    tool = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path).generate_process_run(
        client=client, commit=client.repo.head.commit, path="dummy.yaml"
    )

    tool = tool.association.plan

    assert "tar xf" == tool.command
    assert tool.inputs[0].consumes.path == "hello.tar"
    assert isinstance(tool.inputs[0].consumes, Entity)
    assert not isinstance(tool.inputs[0].consumes, Collection)
    assert tool.inputs[0].prefix is None

    assert argv == tool.to_argv()


def test_base_command_as_file_input(client):
    """Test base command detection when it is a script file."""
    cwd = Path(client.path)
    script = cwd / "script.py"
    script.touch()

    input_file = cwd / "input.csv"
    input_file.touch()

    client.repo.index.add([str(script), str(input_file)])
    client.repo.index.commit("add file")

    argv = ["script.py", "input.csv"]
    tool = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path,).generate_process_run(
        client=client, commit=client.repo.head.commit, path="dummy.yaml"
    )

    tool = tool.association.plan

    assert not tool.command
    assert 2 == len(tool.inputs)


def test_short_base_command_detection(client):
    """Test base command detection without arguments."""
    tool = CommandLineToolFactory(("echo", "A")).generate_process_run(
        client=client, commit=client.repo.head.commit, path="dummy.yaml"
    )

    tool = tool.association.plan

    assert "A" == tool.arguments[0].value
    assert ["echo", "A"] == tool.to_argv()


def test_04_output(client):
    """Test describtion of outputs from a command."""
    hello = Path(client.path) / "hello.tar"
    hello.touch()

    client.repo.index.add([str(hello)])
    client.repo.index.commit("add hello.tar")

    argv = ["tar", "xf", "hello.tar"]
    factory = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path)

    # simulate run

    output = Path(client.path) / "hello.txt"
    output.touch()

    parameters = list(factory.guess_outputs([output]))

    assert "File" == parameters[0][0].type
    assert "hello.txt" == parameters[0][0].outputBinding.glob

    tool = factory.generate_process_run(client=client, commit=client.repo.head.commit, path="dummy.yaml")

    tool = tool.association.plan
    assert argv == tool.to_argv()


def test_05_stdout(client):
    """Test stdout mapping."""
    output = Path(client.path) / "output.txt"
    output.touch()

    client.repo.index.add([str(output)])
    client.repo.index.commit("add output")

    argv = ["echo", "Hello world!"]
    factory = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path, stdout="output.txt",)

    assert "output.txt" == factory.stdout
    assert "stdout" == factory.outputs[0].type

    tool = factory.generate_process_run(client=client, commit=client.repo.head.commit, path="dummy.yaml")

    tool = tool.association.plan
    assert argv == tool.to_argv()


def test_stdout_with_conflicting_arg(client):
    """Test stdout with conflicting argument value."""
    output = Path(client.path) / "lalala"
    output.touch()

    client.repo.index.add([str(output)])
    client.repo.index.commit("add lalala")

    argv = ["echo", "lalala"]
    factory = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path, stdout="lalala",)

    assert "lalala" == factory.inputs[0].default
    assert "string" == factory.inputs[0].type
    assert "lalala" == factory.stdout
    assert "stdout" == factory.outputs[0].type

    tool = factory.generate_process_run(client=client, commit=client.repo.head.commit, path="dummy.yaml")

    tool = tool.association.plan
    assert argv == tool.to_argv()


def test_06_params(client):
    """Test referencing input parameters in other fields."""
    hello = Path(client.path) / "hello.tar"
    hello.touch()
    client.repo.index.add([str(hello)])
    client.repo.index.commit("add hello.tar")

    argv = ["tar", "xf", "hello.tar", "goodbye.txt"]
    factory = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path,)

    assert "goodbye.txt" == factory.inputs[1].default
    assert "string" == factory.inputs[1].type
    assert 2 == factory.inputs[1].inputBinding.position

    goodbye_id = factory.inputs[1].id

    # simulate run

    output = Path(client.path) / "goodbye.txt"
    output.touch()

    parameters = list(factory.guess_outputs([output]))

    assert "File" == parameters[0][0].type
    assert "$(inputs.{0})".format(goodbye_id) == parameters[0][0].outputBinding.glob

    tool = factory.generate_process_run(client=client, commit=client.repo.head.commit, path="dummy.yaml")

    tool = tool.association.plan
    assert argv == tool.to_argv()


def test_09_array_inputs(client):
    """Test specification of input parameters in arrays."""
    argv = [
        "echo",
        "-A",
        "one",
        "two",
        "three",
        "-B=four",
        "-B=five",
        "-B=six",
        "-C=seven,eight,nine",
    ]
    tool = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path).generate_process_run(
        client=client, commit=client.repo.head.commit, path="dummy.yaml"
    )

    tool = tool.association.plan

    assert "seven,eight,nine" == tool.arguments[-1].value
    assert "-C=" == tool.arguments[-1].prefix

    assert argv == tool.to_argv()


@pytest.mark.parametrize("argv", [["wc"], ["wc", "-l"]])
def test_stdin_and_stdout(argv, client):
    """Test stdout mapping."""
    input_ = Path(client.path) / "input.txt"
    input_.touch()
    output = Path(client.path) / "output.txt"
    output.touch()
    error = Path(client.path) / "error.log"
    error.touch()

    client.repo.index.add([str(input_), str(output), str(error)])
    client.repo.index.commit("add files")

    factory = CommandLineToolFactory(
        argv,
        directory=client.path,
        working_dir=client.path,
        stdin="input.txt",
        stdout="output.txt",
        stderr="error.log",
    )

    assert factory.stdin
    if len(argv) > 1:
        assert factory.arguments

    assert "output.txt" == factory.stdout
    assert "stdout" == factory.outputs[0].type

    tool = factory.generate_process_run(client=client, commit=client.repo.head.commit, path="dummy.yaml")

    tool = tool.association.plan
    assert argv == tool.to_argv()
    assert any(i.mapped_to and i.mapped_to.stream_type == "stdin" for i in tool.inputs)
    assert any(o.mapped_to and o.mapped_to.stream_type == "stdout" for o in tool.outputs)
    assert any(o.mapped_to and o.mapped_to.stream_type == "stderr" for o in tool.outputs)


def test_input_directory(client):
    """Test input directory."""
    cwd = Path(client.path)
    src = cwd / "src"
    src.mkdir(parents=True)

    for i in range(5):
        (src / str(i)).touch()

    src_tar = cwd / "src.tar"
    src_tar.touch()

    client.repo.index.add([str(src), str(src_tar)])
    client.repo.index.commit("add file and folder")

    argv = ["tar", "czvf", "src.tar", "src"]
    factory = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path,)

    tool = factory.generate_process_run(client=client, commit=client.repo.head.commit, path="dummy.yaml")

    tool = tool.association.plan
    assert argv == tool.to_argv()

    inputs = sorted(tool.inputs, key=lambda x: x.position)

    assert src_tar.name == inputs[0].consumes.path
    assert isinstance(inputs[0].consumes, Entity)
    assert not isinstance(inputs[0].consumes, Collection)
    assert inputs[1].consumes.path == src.name
    assert isinstance(inputs[1].consumes, Collection)


def test_existing_output_directory(client, runner, project):
    """Test creation of InitialWorkDirRequirement for output."""
    from renku.core.models.workflow.converters.cwl import CWLConverter

    client.path = client.path
    output = client.path / "output"

    argv = ["script", "output"]
    factory = CommandLineToolFactory(argv, directory=client.path, working_dir=client.path,)

    with factory.watch(client, no_output=True) as tool:
        # Script creates the directory.
        output.mkdir(parents=True)

    run = factory.generate_process_run(client=client, commit=client.repo.head.commit, path="dummy.yaml")

    cwl, _ = CWLConverter.convert(run.association.plan, client)

    assert 0 == len([r for r in cwl.requirements if hasattr(r, "listing")])

    output.mkdir(parents=True, exist_ok=True)
    with factory.watch(client) as tool:
        # The directory already exists.
        (output / "result.txt").touch()

    assert 1 == len(tool.inputs)

    run = tool.generate_process_run(client=client, commit=client.repo.head.commit, path="dummy.yaml")
    cwl, _ = CWLConverter.convert(run.association.plan, client)

    reqs = [r for r in cwl.requirements if hasattr(r, "listing")]

    assert 1 == len(reqs)
    assert output.name == reqs[0].listing[0].entryname
    assert 1 == len(tool.outputs)
