#!/usr/bin/env python

# Copyright 2016 neurodata (http://neurodata.io/)
#
# 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.
#

# ingest.py
# Created by <NAME> on 2016-11-25.
# Email: <EMAIL>

import sys, os
from time import sleep, time
from git import Repo
from github import Github
from travispy import TravisPy

from settings import *
from bl_exceptions import FileNotFoundException, err
from config import config
from common import is_git_repo, is_git_branch, read_token
from common import write_yml
from os.path import join
from build import trigger_build

def handle_gitignore(projecthome, bl_conf):
    """
    Build/update the .gitignore file and ensure that blci ignored files are
    not added to repo. Some files like the credentials file should never be
    tracked or added to the remote repo.

    **Positional Arguments:**

    bl_conf:
        - A BLCI configuration :class:`~include.config.config` object

    credentials_fn:
        - An existing path to a file containing credentials
    """

    credentials_fn = bl_conf.get(BL_CREDS)
    git_ignore_fn = join(projecthome, GIT_IGNORE_FN)

    if not os.path.exists(credentials_fn):
        raise FileNotFoundException("Cannot find file credentials file '{}'".
                format(join(projecthome, credentials_fn)))

    has_creds = False
    ignored = set()
    if os.path.exists(git_ignore_fn):
        with open(git_ignore_fn, "rb") as f:
            while (True):
                line = f.readline()
                if not line: break

                line = line.strip()
                ignored.add(line)
                # TODO: Account for bash regexs matching credentials_fn
                if (line == ".*" and credentials_fn.startswith(".")) \
                        or line.startswith(credentials_fn):
                    has_creds = True
                    break # No work to do

    with open(git_ignore_fn, "ab") as f:
        f.write(credentials_fn + "\n")
        for exp in bl_conf.get(BL_IGNORE):
            # TODO: Account for more advanced regexs
            bash_exp = "*"+exp if (exp.startswith(".") and exp != ".*") else exp
            if bash_exp not in ignored:
                f.write(bash_exp + "\n")

    print "Writing gitignore .Writing gitignore ...."

def create_remote_repo(bl_conf):
    """
    Uses user-defined configuration file to create a **public** github repo that
    is initialized using configurations supplied to the BLCI configuration file.

    **Positional Arguments:**

    bl_conf:
        - A BLCI configuration :class:`~include.config.config` object with a
            credentials entry that is a path for a file containing a string
            representing a `Github OAuth2 token
            <https://help.github.com/articles/creating-an-access-token-for-command-line-use/>`_.

    **Returns:**

    A `PyGithub Repository <http://pygithub.readthedocs.io/>`_ object.
    """

    g = Github(read_token(bl_conf.get(BL_CREDS)))
    user = g.get_user()

    print "Creating remote repo '{}' ...".format(bl_conf.get(BL_NAME))
    return user.create_repo(bl_conf.get(BL_NAME), bl_conf.get(BL_DESCRIPTION),
           private=False, auto_init=True)

def travis_track(bl_conf):
    """
    Track a github repo using travis CI programmatically

    **Positional Arguments:**

    bl_conf:
        - A BLCI configuration :class:`~include.config.config` object
    """

    travis = TravisPy.github_auth(read_token(bl_conf.get(BL_CREDS)))

    print "Synchronizing Github and Travis ..."
    travis.user().sync()

    print "Tracking repo with Travis, wait .",

    response = time()
    response_timeout = 120 # 2 minute response time max
    while (True):
        try:
            repo = travis.repo("{}/{}".format(
                travis.user().login, bl_conf.get(BL_NAME)))
            break
        except:
            if (time()-response) >= response_timeout:
                sys.stderr.write(err("Travis Tracking failed! Ensure Travis"
                    " has synched with Github\n"))
                exit(808)

            print ".",
            sleep(2)

    print " Done!\nEnabling repo ..."
    repo.enable() # Switch is now on

def create_base_CI_conf(bl_conf, Git):
    """
    Given a user defined project blci configuration file and the CI
    configuration file in the root of blci, create a new a config that encodes
    the following:
    - User defined tests
    - Triggers for code and data dependencies for any code or data change that
    will occur from an `update` action

    **Positional Arguments:**

    bl_conf:
        - A BLCI configuration :class:`~include.config.config` object
        - Base CI configuration file defined by blci
    Git:
        - Git repo object from Gitpython package
    """

    base_CI_conf = {}
    for setting, val in bl_conf.getall().iteritems():
        if setting not in BL_SPECIFIC_CONFS:
            base_CI_conf[setting] = val

    # Which branch to work with
    base_CI_conf["branches"] = { "only": bl_conf.get(BL_NAME) }

    diff = Git.diff()
    if diff: # Things have changed
        # TODO: Add webhooky things
        pass

    return base_CI_conf

def add(projecthome, message):
    """
    Add or update a blci repo

    **Positional Arguments:**

    projecthome:
        - The root directory of where the blci project is.
    """

    projecthome = os.path.abspath(projecthome)
    conf = config(join(projecthome, BL_DEFAULT_CONFIG_FN), projecthome,
            on_anomaly="ERROR")

    new_repo = False # is this a new blci repo or not
    if not is_git_repo(projecthome):
        repo = Repo.init(projecthome, bare=False)
        new_repo = True
    else:
        repo = Repo(projecthome)

    Git = repo.git(work_tree=projecthome)
    if not Git.branch(): # We may have a repo with no commits ...
        new_repo = True

    handle_gitignore(projecthome, conf)

    if new_repo:
        Git.add(projecthome)
        Git.add("-f", join(projecthome, GIT_IGNORE_FN))
        Git.commit("-m", "BLCI: Repo creating")

    # Don't have a branch already ..
    if not is_git_branch(conf.get(BL_NAME), projecthome):
        Git.branch(conf.get(BL_NAME)) # Create new branch with repo name

    # Update and ingest should be the same process
    Git.checkout(conf.get(BL_NAME)) # Hop into that branch

    # Now from the new branch
    # TODO: No need to rewrite everytime actually ..
    base_CI_conf = create_base_CI_conf(conf, Git)
    write_yml(base_CI_conf, join(projecthome, BASE_CI_CONFIG_FN), verbose=True)
    if new_repo:
        Git.add("-f", join(projecthome, BASE_CI_CONFIG_FN))

    if new_repo: # Make and add the remote
        remote_repo = create_remote_repo(conf)
        Git.remote("add", "origin", remote_repo.ssh_url) # Must have SSH

    Git.commit("-am", message) # Add all
    Git.push("origin", conf.get(BL_NAME))

    if new_repo:
        travis_track(conf)
        trigger_build(conf, projecthome)

    print "Add action successful ..."
