#!/usr/bin/env python
# Copyright (C) 2015 The Android Open Source Project
#
# 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 __future__ import print_function

import hashlib
import json
import optparse
import os
import shutil
import subprocess
import sys

import bowerutil

CACHE_DIR = os.path.expanduser(os.path.join(
    '~', '.gerritcodereview', 'bazel-cache', 'downloaded-artifacts'))


def bower_cmd(bower, *args):
    cmd = bower.split(' ')
    cmd.extend(args)
    return cmd


def bower_info(bower, name, package, version):
    cmd = bower_cmd(bower, '-l=error', '-j',
                    'info', '%s#%s' % (package, version))
    try:
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
    except:
        sys.stderr.write("error executing: %s\n" % ' '.join(cmd))
        raise
    out, err = p.communicate()
    if p.returncode:
        sys.stderr.write(err)
        raise OSError('Command failed: %s' % ' '.join(cmd))

    try:
        info = json.loads(out)
    except ValueError:
        raise ValueError('invalid JSON from %s:\n%s' % (" ".join(cmd), out))
    info_name = info.get('name')
    if info_name != name:
        raise ValueError(
            'expected package name %s, got: %s' % (name, info_name))
    return info


def ignore_deps(info):
    # Tell bower to ignore dependencies so we just download this component.
    # This is just an optimization, since we only pick out the component we
    # need, but it's important when downloading sizable dependency trees.
    #
    # As of 1.6.5 I don't think ignoredDependencies can be specified on the
    # command line with --config, so we have to create .bowerrc.
    deps = info.get('dependencies')
    if deps:
        with open(os.path.join('.bowerrc'), 'w') as f:
            json.dump({'ignoredDependencies': list(deps.keys())}, f)


def cache_entry(name, package, version, sha1):
    if not sha1:
        sha1 = hashlib.sha1('%s#%s' % (package, version)).hexdigest()
    return os.path.join(CACHE_DIR, '%s-%s.zip-%s' % (name, version, sha1))


def main(args):
    opts = optparse.OptionParser()
    opts.add_option('-n', help='short name of component')
    opts.add_option('-b', help='bower command')
    opts.add_option('-p', help='full package name of component')
    opts.add_option('-v', help='version number')
    opts.add_option('-s', help='expected content sha1')
    opts.add_option('-o', help='output file location')
    opts, args_ = opts.parse_args(args)

    assert opts.p
    assert opts.v
    assert opts.n

    cwd = os.getcwd()
    outzip = os.path.join(cwd, opts.o)
    cached = cache_entry(opts.n, opts.p, opts.v, opts.s)

    if not os.path.exists(cached):
        info = bower_info(opts.b, opts.n, opts.p, opts.v)
        ignore_deps(info)
        subprocess.check_call(
            bower_cmd(
                opts.b, '--quiet', 'install', '%s#%s' % (opts.p, opts.v)))
        bc = os.path.join(cwd, 'bower_components')
        subprocess.check_call(
            ['zip', '-q', '--exclude', '.bower.json', '-r', cached, opts.n],
            cwd=bc)

        if opts.s:
            path = os.path.join(bc, opts.n)
            sha1 = bowerutil.hash_bower_component(
                hashlib.sha1(), path).hexdigest()
            if opts.s != sha1:
                print((
                    '%s#%s:\n'
                    'expected %s\n'
                    'received %s\n') % (opts.p, opts.v, opts.s, sha1),
                    file=sys.stderr)
                try:
                    os.remove(cached)
                except OSError as err:
                    if path.exists(cached):
                        print('error removing %s: %s' % (cached, err),
                              file=sys.stderr)
                return 1

    shutil.copyfile(cached, outzip)
    return 0


if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
