#!/usr/bin/env python

import argparse
from os import chmod, makedirs
import os.path

check = (
    "Check jobs are triggered when a pull request is created. Any job can be "
    "added as a check, the most common use is for lint, unit and basic "
    "functional tests."
)

gate = (
    "Gate jobs are triggered by a comment on a pull request. The change is "
    "merged automatically if all the tests pass. The gate job ensures that "
    "changes are tested against the state of the target branch when the code "
    "is merged. In comparison, check jobs test against the state of the "
    "branch when the test is started."
)

release = (
    "Release jobs are used to validate that a particular commit can be used "
    "for a new release of the project. They tend to be the most expensive "
    "tests in terms of time and resources."
)

periodic = (
    "Periodic jobs are triggered by a configurable timer, by default they run "
    "daily and use the latest commit to the branch being tested."
)

rel_notes = (
    "The release notes generated by this step are used when releasing a new "
    "version of the project. The releases should document the changes between "
    "the next version and the latest released."
)

dep_update = (
    "Dependency updates are checked periodically. A project should codify the "
    "process for updating the working directory of the repository with any "
    "changes to upstream dependencies. If changes are made, the job will "
    "automatically create a new pull request."
)

std_pre = (
    "pre is an optional executable for preparing the node prior to running "
    "the build. This step is retried on failure given that it performs "
    "preparatory actions and not the actual function of the job. Examples of "
    "actions that should be taken here include things that would be performed "
    "manually such as setting configuration options."
)

std_run = (
    "run is a required executable that executes the purpose of the job. For "
    "example, if the project deploys some software, this file would be used "
    "to perform the deployment and then test the success of the operation."
)

std_post = (
    "post is an optional executable that is the last stage to run. It is used "
    "to perform actions that make use of the data generated. Such actions "
    "include log aggregation and preparing the node to be used as an image."
)

std_env_vars = (
    "Environment variables\n"
    "RE_HOOK_ARTIFACT_DIR: The directory where job artifacts (log files, "
    "configuration used, etc) should be written.\n"
    "RE_HOOK_RESULT_DIR: The directory where test results should be written.\n"
    "RE_JOB_NAME: The name of the job itself. For example "
    "PM_repo-name-series-image-scenario-action\n"
    "RE_JOB_IMAGE: The name of the 'image' as defined in the job.\n"
    "RE_JOB_SCENARIO: The name of the 'scenario' as defined in the job.\n"
    "RE_JOB_ACTION: The name of the 'action' as defined in the job.\n"
    "RE_JOB_FLAVOR: The name of the 'flavor' as defined in the job.\n"
    "RE_JOB_TRIGGER: The name of the 'trigger' that initiated the build, "
    "examples include 'PULL', 'PUSH', 'TIMER' and 'OTHER'.\n"
    "RE_JOB_TRIGGER_DETAIL: Provides additional information regarding the "
    "job trigger.\n"
    "RE_JOB_REPO_NAME: The project's GitHub repository which gets cloned in "
    "the job. For example, for the repository found at "
    "https://github.com/rcbops/rpc-gating, the name is rpc-gating."
)

rel_notes_env_vars = (
    "Environment variables\n"
    "RE_HOOK_VERSION: The version that is being released, e.g. 1.2.0.\n"
    "RE_HOOK_PREVIOUS_VERSION: The version to compare against, e.g. 1.1.0.\n"
    "RE_HOOK_RELEASE_NOTES: The file where release notes should be written. "
    "The release notes should be formatted with GitHub-flavoured Markdown.\n"
    "RE_HOOK_REPO_HTTP_URL: The URL for the repository being released e.g. "
    "https://github.com/rcbops/rpc-openstack.git."
)

rel_notes_not_implemented = (
    'echo "Release notes generation has not been implemented for '
    '${RE_HOOK_REPO_HTTP_URL}" > $RE_HOOK_RELEASE_NOTES'
)

hooks = {
    "gating/check": [
        {"name": "pre", "docs": [check, std_pre, std_env_vars]},
        {"name": "run", "docs": [check, std_run, std_env_vars]},
        {"name": "post", "docs": [check, std_post, std_env_vars]},
    ],
    "gating/gate": [
        {"name": "pre", "docs": [gate, std_pre, std_env_vars]},
        {"name": "run", "docs": [gate, std_run, std_env_vars]},
        {"name": "post", "docs": [gate, std_post, std_env_vars]},
    ],
    "gating/release": [
        {"name": "pre", "docs": [release, std_pre, std_env_vars]},
        {"name": "run", "docs": [release, std_run, std_env_vars]},
        {"name": "post", "docs": [release, std_post, std_env_vars]},
    ],
    "gating/periodic": [
        {"name": "pre", "docs": [periodic, std_pre, std_env_vars]},
        {"name": "run", "docs": [periodic, std_run, std_env_vars]},
        {"name": "post", "docs": [periodic, std_post, std_env_vars]},
    ],
    "gating/update_dependencies": [
        {"name": "pre", "docs": [dep_update, std_pre]},
        {"name": "run", "docs": [dep_update, std_run]},
        {"name": "post", "docs": [dep_update, std_post]},
    ],
    "gating/generate_release_notes": [
        {"name": "pre", "docs": [rel_notes, std_pre, rel_notes_env_vars]},
        {"name": "run", "docs": [rel_notes, std_run, rel_notes_env_vars],
            "commands": [rel_notes_not_implemented]},
        {"name": "post", "docs": [rel_notes, std_post, rel_notes_env_vars]},
    ],
}


def add_std_hooks(repo_root):
    for d, files in hooks.items():
        directory = os.path.join(repo_root, d)
        if not os.path.exists(directory):
            makedirs(directory)
            for file_ in files:
                file_bits = ["#!/bin/bash"] + map(to_sh_comment, file_["docs"])
                if "commands" in file_:
                    file_bits.extend(file_["commands"])
                path = os.path.join(directory, file_["name"])
                with open(path, "w") as f:
                    f.write("\n\n".join(file_bits))
                chmod(path, 0o755)


def add_component_files(directory):
    component_files = [
        {
            "name": "component_metadata.yml",
            "docs": [
                "This file is used to track component information in the "
                "repository, currently it is limited to component "
                "dependencies."
            ],
        },
        {
            "name": "component_requirements.yml",
            "docs": [
                "This file records the specific versions of dependencies that "
                "a project should use. Put another way it is used to pin "
                "component dependencies. This file should be updated "
                "automatically by the dependency update job."
            ],
        },
    ]
    for component in component_files:
        path = os.path.join(directory, component["name"])
        if not os.path.exists(path):
            file_bits = map(to_sh_comment, component["docs"]) + ["{}"]
            with open(path, "w") as f:
                f.write("\n\n".join(file_bits))


def to_sh_comment(text, max_length=80):
    comment_lines = []
    for line in text.split("\n"):
        comment_line = "#"
        for word in line.split(" "):
            if len(comment_line) + len(word) >= max_length:
                comment_lines.append(comment_line)
                comment_line = "#"
            comment_line = " ".join((comment_line, word))
        comment_lines.append(comment_line)

    return "\n".join(comment_lines)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "directory",
        help="The path to the repository where the skeleton should be applied."
    )
    args = parser.parse_args()

    add_std_hooks(args.directory)
    add_component_files(args.directory)
