# (c) 2014 Michael DeHaan, <michael@ansible.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from six import iteritems, string_types

import os

from ansible.errors import AnsibleError, AnsibleParserError
from ansible.playbook.role.definition import RoleDefinition

__all__ = ['RoleRequirement']


class RoleRequirement(RoleDefinition):

    """
    FIXME: document various ways role specs can be specified
    """

    def __init__(self):
        pass

    def _get_valid_spec_keys(self):
        return (
            'name',
            'role',
            'scm',
            'src',
            'version',
        )

    def parse(self, ds):
        '''
        FIXME: docstring
        '''

        assert type(ds) == dict or isinstance(ds, string_types)

        role_name    = None
        role_params  = dict()
        new_ds       = dict()

        if isinstance(ds, string_types):
            role_name = ds
        else:
            (new_ds, role_params) = self._split_role_params(self._preprocess_role_spec(ds))

            # pull the role name out of the ds
            role_name = new_ds.pop('role_name', new_ds.pop('role', None))

        if role_name is None:
            raise AnsibleError("Role requirement did not contain a role name!", obj=ds)
        return (new_ds, role_name, role_params)

    def _preprocess_role_spec(self, ds):
        if 'role' in ds:
            # Old style: {role: "galaxy.role,version,name", other_vars: "here" }
            role_info = role_spec_parse(ds['role'])
            if isinstance(role_info, dict):
                # Warning: Slight change in behaviour here.  name may be being
                # overloaded.  Previously, name was only a parameter to the role.
                # Now it is both a parameter to the role and the name that
                # ansible-galaxy will install under on the local system.
                if 'name' in ds and 'name' in role_info:
                    del role_info['name']
                ds.update(role_info)
        else:
            # New style: { src: 'galaxy.role,version,name', other_vars: "here" }
            if 'github.com' in ds["src"] and 'http' in ds["src"] and '+' not in ds["src"] and not ds["src"].endswith('.tar.gz'):
                ds["src"] = "git+" + ds["src"]

            if '+' in ds["src"]:
                (scm, src) = ds["src"].split('+')
                ds["scm"] = scm
                ds["src"] = src

            if 'name' in ds:
                ds["role"] = ds["name"]
                del ds["name"]
            else:
                ds["role"] = repo_url_to_role_name(ds["src"])

            # set some values to a default value, if none were specified
            ds.setdefault('version', '')
            ds.setdefault('scm', None)

        return ds

def repo_url_to_role_name(repo_url):
    # gets the role name out of a repo like
    # http://git.example.com/repos/repo.git" => "repo"

    if '://' not in repo_url and '@' not in repo_url:
        return repo_url
    trailing_path = repo_url.split('/')[-1]
    if trailing_path.endswith('.git'):
        trailing_path = trailing_path[:-4]
    if trailing_path.endswith('.tar.gz'):
        trailing_path = trailing_path[:-7]
    if ',' in trailing_path:
        trailing_path = trailing_path.split(',')[0]
    return trailing_path

def role_spec_parse(role_spec):
    # takes a repo and a version like
    # git+http://git.example.com/repos/repo.git,v1.0
    # and returns a list of properties such as:
    # {
    #   'scm': 'git',
    #   'src': 'http://git.example.com/repos/repo.git',
    #   'version': 'v1.0',
    #   'name': 'repo'
    # }

    default_role_versions = dict(git='master', hg='tip')

    role_spec = role_spec.strip()
    role_version = ''
    if role_spec == "" or role_spec.startswith("#"):
        return (None, None, None, None)

    tokens = [s.strip() for s in role_spec.split(',')]

    # assume https://github.com URLs are git+https:// URLs and not
    # tarballs unless they end in '.zip'
    if 'github.com/' in tokens[0] and not tokens[0].startswith("git+") and not tokens[0].endswith('.tar.gz'):
        tokens[0] = 'git+' + tokens[0]

    if '+' in tokens[0]:
        (scm, role_url) = tokens[0].split('+')
    else:
        scm = None
        role_url = tokens[0]

    if len(tokens) >= 2:
        role_version = tokens[1]

    if len(tokens) == 3:
        role_name = tokens[2]
    else:
        role_name = repo_url_to_role_name(tokens[0])

    if scm and not role_version:
        role_version = default_role_versions.get(scm, '')

    return dict(scm=scm, src=role_url, version=role_version, role_name=role_name)

# FIXME: all of these methods need to be cleaned up/reorganized below this
def get_opt(options, k, defval=""):
    """
    Returns an option from an Optparse values instance.
    """
    try:
        data = getattr(options, k)
    except:
        return defval
    if k == "roles_path":
        if os.pathsep in data:
            data = data.split(os.pathsep)[0]
    return data

def get_role_path(role_name, options):
    """
    Returns the role path based on the roles_path option
    and the role name.
    """
    roles_path = get_opt(options,'roles_path')
    roles_path = os.path.join(roles_path, role_name)
    roles_path = os.path.expanduser(roles_path)
    return roles_path

def get_role_metadata(role_name, options):
    """
    Returns the metadata as YAML, if the file 'meta/main.yml'
    exists in the specified role_path
    """
    role_path = os.path.join(get_role_path(role_name, options), 'meta/main.yml')
    try:
        if os.path.isfile(role_path):
            f = open(role_path, 'r')
            meta_data = yaml.safe_load(f)
            f.close()
            return meta_data
        else:
            return None
    except:
        return None    
