# -*- coding: utf-8 -*-
#
# Copyright 2010-2014 The pygit2 contributors
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.
#
# In addition to the permissions in the GNU General Public License,
# the authors give you unlimited permission to link the compiled
# version of this file into combinations with other programs,
# and to distribute those combinations without any restriction
# coming from the use of this file.  (The General Public License
# restrictions do apply in other respects; for example, they cover
# modification of the file, and distribution when not linked into
# a combined executable.)
#
# This file 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 this program; see the file COPYING.  If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.

# Import from the future
from __future__ import absolute_import

# Low level API
from _pygit2 import *

# High level API
from .blame import Blame, BlameHunk
from .config import Config
from .credentials import *
from .errors import check_error
from .ffi import ffi, C
from .index import Index, IndexEntry
from .remote import Remote, get_credentials
from .repository import Repository
from .settings import Settings
from .utils import to_bytes, to_str
from ._utils import __version__


# Features
features = C.git_libgit2_features()
GIT_FEATURE_THREADS = C.GIT_FEATURE_THREADS
GIT_FEATURE_HTTPS = C.GIT_FEATURE_HTTPS
GIT_FEATURE_SSH = C.GIT_FEATURE_SSH

# GIT_REPOSITORY_INIT_*
GIT_REPOSITORY_INIT_OPTIONS_VERSION = C.GIT_REPOSITORY_INIT_OPTIONS_VERSION
GIT_REPOSITORY_INIT_BARE = C.GIT_REPOSITORY_INIT_BARE
GIT_REPOSITORY_INIT_NO_REINIT = C.GIT_REPOSITORY_INIT_NO_REINIT
GIT_REPOSITORY_INIT_NO_DOTGIT_DIR = C.GIT_REPOSITORY_INIT_NO_DOTGIT_DIR
GIT_REPOSITORY_INIT_MKDIR = C.GIT_REPOSITORY_INIT_MKDIR
GIT_REPOSITORY_INIT_MKPATH = C.GIT_REPOSITORY_INIT_MKPATH
GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = C.GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE
GIT_REPOSITORY_INIT_RELATIVE_GITLINK = C.GIT_REPOSITORY_INIT_RELATIVE_GITLINK
GIT_REPOSITORY_INIT_SHARED_UMASK = C.GIT_REPOSITORY_INIT_SHARED_UMASK
GIT_REPOSITORY_INIT_SHARED_GROUP = C.GIT_REPOSITORY_INIT_SHARED_GROUP
GIT_REPOSITORY_INIT_SHARED_ALL = C.GIT_REPOSITORY_INIT_SHARED_ALL


def init_repository(path, bare=False,
                    flags=GIT_REPOSITORY_INIT_MKPATH,
                    mode=0,
                    workdir_path=None,
                    description=None,
                    template_path=None,
                    initial_head=None,
                    origin_url=None):
    """
    Creates a new Git repository in the given *path*.

    If *bare* is True the repository will be bare, i.e. it will not have a
    working copy.

    The *flags* may be a combination of:

    - GIT_REPOSITORY_INIT_BARE (overriden by the *bare* parameter)
    - GIT_REPOSITORY_INIT_NO_REINIT
    - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR
    - GIT_REPOSITORY_INIT_MKDIR
    - GIT_REPOSITORY_INIT_MKPATH (set by default)
    - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE

    The *mode* parameter may be any of GIT_REPOSITORY_SHARED_UMASK (default),
    GIT_REPOSITORY_SHARED_GROUP or GIT_REPOSITORY_INIT_SHARED_ALL, or a custom
    value.

    The *workdir_path*, *description*, *template_path*, *initial_head* and
    *origin_url* are all strings.

    See libgit2's documentation on git_repository_init_ext for further details.
    """
    # Pre-process input parameters
    if bare:
        flags |= GIT_REPOSITORY_INIT_BARE

    # Options
    options = ffi.new('git_repository_init_options *')
    C.git_repository_init_init_options(options,
                                       GIT_REPOSITORY_INIT_OPTIONS_VERSION)
    options.flags = flags
    options.mode = mode

    if workdir_path:
        workdir_path_ref = ffi.new('char []', to_bytes(workdir_path))
        options.workdir_path = workdir_path_ref

    if description:
        description_ref = ffi.new('char []', to_bytes(description))
        options.description = description_ref

    if template_path:
        template_path_ref = ffi.new('char []', to_bytes(template_path))
        options.template_path = template_path_ref

    if initial_head:
        initial_head_ref = ffi.new('char []', to_bytes(initial_head))
        options.initial_head = initial_head_ref

    if origin_url:
        origin_url_ref = ffi.new('char []', to_bytes(origin_url))
        options.origin_url = origin_url_ref

    # Call
    crepository = ffi.new('git_repository **')
    err = C.git_repository_init_ext(crepository, to_bytes(path), options)
    check_error(err)

    # Ok
    return Repository(to_str(path))


@ffi.callback('int (*credentials)(git_cred **cred, const char *url,'
              'const char *username_from_url, unsigned int allowed_types,'
              'void *data)')
def _credentials_cb(cred_out, url, username_from_url, allowed, data):
    d = ffi.from_handle(data)

    try:
        ccred = get_credentials(d['credentials_cb'], url, username_from_url, allowed)
        cred_out[0] = ccred[0]
    except Exception as e:
        d['exception'] = e
        return C.GIT_EUSER

    return 0

@ffi.callback('int (*git_repository_create_cb)(git_repository **out,'
              'const char *path, int bare, void *payload)')
def _repository_create_cb(repo_out, path, bare, data):
    d = ffi.from_handle(data)
    try:
        repository = d['repository_cb'](ffi.string(path), bare != 0)
        # we no longer own the C object
        repository._disown()
        repo_out[0] = repository._repo
    except Exception as e:
        d['exception'] = e
        return C.GIT_EUSER

    return 0

@ffi.callback('int (*git_remote_create_cb)(git_remote **out, git_repository *repo,'
              'const char *name, const char *url, void *payload)')
def _remote_create_cb(remote_out, repo, name, url, data):
    d = ffi.from_handle(data)
    try:
        remote = d['remote_cb'](Repository._from_c(repo, False), ffi.string(name), ffi.string(url))
        remote_out[0] = remote._remote
        # we no longer own the C object
        remote._remote = ffi.NULL
    except Exception as e:
        d['exception'] = e
        return C.GIT_EUSER

    return 0

@ffi.callback('int (*git_transport_certificate_check_cb)'
              '(git_cert *cert, int valid, const char *host, void *payload)')
def _certificate_cb(cert_i, valid, host, data):
    d = ffi.from_handle(data)
    try:
        # python's parting is deep in the libraries and assumes an OpenSSL-owned cert
        val = d['certificate_cb'](None, bool(valid), ffi.string(host))
        if not val:
            return C.GIT_ECERTIFICATE
    except Exception as e:
        d['exception'] = e
        return C.GIT_EUSER

    return 0

def clone_repository(
        url, path, bare=False, repository=None, remote=None,
        checkout_branch=None, credentials=None, certificate=None):
    """Clones a new Git repository from *url* in the given *path*.

    Returns a Repository class pointing to the newly cloned repository.

    :param str url: URL of the repository to clone

    :param str path: Local path to clone into

    :param bool bare: Whether the local repository should be bare

    :param callable remote: Callback for the remote to use.

    :param callable repository: Callback for the repository to use.

    :param str checkout_branch: Branch to checkout after the
     clone. The default is to use the remote's default branch.

    :param callable credentials: authentication to use if the remote
     requires it

    :param callable certificate: callback to verify the host's
     certificate or fingerprint.

    :rtype: Repository

    The repository callback has `(path, bare) -> Repository` as a
    signature. The Repository it returns will be used instead of
    creating a new one.

    The remote callback has `(Repository, name, url) -> Remote` as a
    signature. The Remote it returns will be used instead of the default
    one.

    The certificate callback has `(cert, valid, hostname) -> bool` as
    a signature. Return True to accept the connection, False to abort.

    """

    opts = ffi.new('git_clone_options *')
    crepo = ffi.new('git_repository **')

    branch = checkout_branch or None

    # Data, let's use a dict as we don't really want much more
    d = {}
    d['credentials_cb'] = credentials
    d['repository_cb'] = repository
    d['remote_cb'] = remote
    d['certificate_cb'] = certificate
    d_handle = ffi.new_handle(d)

    # Perform the initialization with the version we compiled
    C.git_clone_init_options(opts, C.GIT_CLONE_OPTIONS_VERSION)

    # We need to keep the ref alive ourselves
    checkout_branch_ref = None
    if branch:
        checkout_branch_ref = ffi.new('char []', to_bytes(branch))
        opts.checkout_branch = checkout_branch_ref

    if repository:
        opts.repository_cb = _repository_create_cb
        opts.repository_cb_payload = d_handle

    if remote:
        opts.remote_cb = _remote_create_cb
        opts.remote_cb_payload = d_handle


    opts.bare = bare
    if credentials:
        opts.remote_callbacks.credentials = _credentials_cb
        opts.remote_callbacks.payload = d_handle

    if certificate:
        opts.remote_callbacks.certificate_check = _certificate_cb
        opts.remote_callbacks.payload = d_handle

    err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts)

    if 'exception' in d:
        raise d['exception']

    check_error(err)

    return Repository._from_c(crepo[0], owned=True)

settings = Settings()