#!/bin/sh
''''[ ! -z $VIRTUAL_ENV ] && exec python -u -- "$0" ${1+"$@"}; command -v python3 > /dev/null && exec python3 -u -- "$0" ${1+"$@"}; exec python2 -u -- "$0" ${1+"$@"} # '''

import sys
import os
import argparse
import json
import shutil
import tempfile
import traceback
import textwrap
try:
    from urllib2 import urlopen
except:
    from urllib.request import urlopen

HERE = os.path.dirname(__file__)
ROOT = os.path.abspath(os.path.join(HERE, ".."))
sys.path.insert(0, ROOT)
import paella  # noqa: F401

os.environ["PYTHONWARNINGS"] = 'ignore:DEPRECATION::pip._internal.cli.base_command'

DEFAULT_REDIS_VERSION = "6.0"

#----------------------------------------------------------------------------------------------

class RedisSourceSetup(paella.Setup):
    # version can be of either forms: 5.0.8 or 5.0
    # the latter will fetch the latest released version on the 5.0 branch
    # branch can be either 6 (will be mapped to 6.0) or 6.1 or any string
    # for partial semantic versions branches, we'll fetch HEAD of the given branch from github
    def __init__(self, version=None, branch=None, valgrind=False, libc=False, no_tls=False,
                 keep=False, just_prepare=False, no_prepare=False, 
                 no_install=False, workdir=None, into=None, info_file=None,
                 nop=False, verbose=False):
        paella.Setup.__init__(self, nop)

        if version is not None and branch is not None:
            raise RuntimeError('conflicting arguments: version, branch')
        self.version = version
        self.branch = branch
        self.valgrind = valgrind
        self.libc = libc
        self.tls = not no_tls
        self.just_prepare = just_prepare
        self.no_prepare = no_prepare
        self.no_install = no_install
        self.keep = keep or workdir is not None
        self.workdir = workdir
        self.into = into
        self.info_file = info_file
        self.verbose = verbose
        self.nop = nop

        self.repo_refresh = not no_prepare

    def read_redis_versions(self):
        j = []
        url = 'https://api.github.com/repos/antirez/redis/tags?per_page=100'
        while True:
            r = urlopen(url)
            if r.code != 200:
                raise RuntimeError('cannot read Redis version list from guthub')
            t = r.read()
            # for 3 <= Python < 3.6
            try:
                t = t.decode()
            except (UnicodeDecodeError, AttributeError):
                pass
            j1 = json.loads(t)
            j += j1
            if r.headers["link"] == "":
                break
            links0 = r.headers["link"].split(",")
            links1 = list(map(lambda a: list(map(lambda b: str.strip(b), a.split(';'))), links0))
            next_link = list(filter(lambda x: x[1] == 'rel="next"', links1))
            if next_link == []:
                break
            url = next_link[0][0][1:-1]
        self.redis_versions = list(map(lambda v: v['name'], j))

    def wget(self, url, file):
        self.run('wget -O {} {}'.format(file, url), output_on_error=True)

    def get_requested_redis_versions(self):
        from semantic_version import Version

        self.read_redis_versions()
        if self.version in self.redis_versions:
            return [self.version]

        sv = Version(self.version, partial=True)
        if sv.patch is not None:
            # this would fail, as the fully qualified self.version is not in self.redis_versions
            version = str(sv)
            return [version]

        if sv.minor is None:
            sv.minor = 0
        br = '{}.{}'.format(sv.major, sv.minor)
        # select the latest version of the major.minor branch
        return list(filter(lambda v: v.startswith(br + '.'), self.redis_versions))
    
    def download_redis(self):
        from semantic_version import Version
        if self.workdir is not None:
            paella.mkdir_p(self.workdir)
            self.base_dir = self.workdir
        else:
            self.base_dir = tempfile.mkdtemp(prefix='redis.')
        print('work dir: ' + self.base_dir)

        if self.version is None and self.branch is None:
            self.branch = 'unstable'
        if self.version is not None:
            versions = self.get_requested_redis_versions()
            if versions == []:
                raise RuntimeError('no version matches request')
            version = versions[0]

            file = os.path.join(self.base_dir, 'redis-{}.tgz'.format(str(version)))
            self.wget('https://github.com/redis/redis/archive/{}.tar.gz'.format(str(version)), file)
            self.run('tar -C {} -xzf {}'.format(self.base_dir, file), output_on_error=True)
            with paella.cwd(self.base_dir):
                shutil.move('redis-{}'.format(version), 'redis')
            self.redis_dir = 'redis'
            # self.redis_dir = os.path.join(self.base_dir, 'redis-{}'.format(version))
        if self.branch is not None:
            try:
                sv = Version(self.branch, partial=True)
                if sv.patch is not None:
                    raise RuntimeError('branch can only include major/minor numbers')
                if sv.minor is None:
                    sv.minor = 0
                branch = '{}.{}'.format(sv.major, sv.minor)
            except:
                branch = self.branch
            self.run('cd {}; git clone https://github.com/redis/redis.git --branch {}'.format(self.base_dir, branch), output_on_error=True)
            self.redis_dir = os.path.join(self.base_dir, 'redis')

    def build_redis(self):
        build_args = ''
        build_args += ' valgrind' if self.valgrind else ''
        build_args += ' MALLOC=libc' if self.libc else ''
        build_args += ' BUILD_TLS=yes' if self.tls else ''
        
        install_args = ''
        install_args += ' BUILD_TLS=yes' if self.tls else ''
        if self.into is not None:
            install_args += ' PREFIX=' + self.into

        with paella.cwd(os.path.join(self.base_dir, self.redis_dir)):
            self.run("""
                make -j `{}/bin/nproc` {}
                """.format(ROOT, build_args), output_on_error=True)

            if not self.no_install:
                self.run("""
                    make install {}
                    """.format(install_args), output_on_error=True)
        if not self.keep:
            shutil.rmtree(self.base_dir)
        if self.info_file is not None:
            info = """
                base_dir {base_dir}
                redis_dir {redis_dir}
            """.format(base_dir=os.path.abspath(self.base_dir), redis_dir=os.path.abspath(self.redis_dir))
            info = textwrap.dedent(info).split("\n", 1)[1]
            paella.fwrite(self.info_file, info)
    
    def common_first(self):
        if self.no_prepare:
            return
        self.install_downloaders()
        self.install("git")

        self.setup_pip()
        self.pip_install("wheel")
        self.pip_install("setuptools --upgrade")
        self.pip_install("semantic_version")

    def debian_compat(self):
        if self.no_prepare:
            return
        self.install("build-essential")
        self.install("libssl-dev")

    def redhat_compat(self):
        if self.no_prepare:
            return
        self.group_install("'Development Tools'")
        self.install("openssl-devel")

    def fedora(self):
        if self.no_prepare:
            return
        self.redhat_compat()
        self.install("openssl-devel")

    def macosx(self):
        if self.no_prepare:
            return
        if sh('xcode-select -p') == '':
            fatal("Xcode tools are not installed. Please run xcode-select --install.")

    def common_last(self):
        if not self.just_prepare:
            self.download_redis()
            self.build_redis()
            if self.no_install:
                self.run("%s/src/redis-server --version" % os.path.join(self.base_dir, self.redis_dir))
            else:
                self.run("redis-server --version")

#----------------------------------------------------------------------------------------------

class RedisRepoSetup(paella.Setup):
    def __init__(self, nop=False):
        paella.Setup.__init__(self, nop)

    def common_first(self):
        pass

    def debian_compat(self):
        # https://chilts.org/installing-redis-from-chris-leas-ppa/
        self.add_repo("ppa:chris-lea/redis-server")
        self.install("redis-server")
        # if not removed, might break apt-get update
        self.run("add-apt-repository -r -y ppa:chris-lea/redis-server")

    def redhat_compat(self):
        # https://linuxize.com/post/how-to-install-and-configure-redis-on-centos-7/
        self.install("epel-release yum-utils")

        self.install("http://rpms.remirepo.net/enterprise/remi-release-7.rpm")
        self.run("yum-config-manager -y --enable remi")
        self.install("redis")

    def fedora(self):
        self.install("dnf-plugins-core")
        
        self.install("https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm")
        self.install("--allowerasing https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm")
        # self.install("epel-release")
        self.install("http://rpms.remirepo.net/enterprise/remi-release-7.rpm")
        self.run("dnf config-manager -y --set-enabled remi")
        self.install("redis")

    def macosx(self):
        self.install("redis")

    def common_last(self):
        pass

#----------------------------------------------------------------------------------------------

parser = argparse.ArgumentParser(description='Set up system for build.')
parser.add_argument('-n', '--nop', action="store_true", help='no operation')
parser.add_argument('-s', '--source', action="store_true", help="Build from source")
parser.add_argument('-v', '--version', type=str, default=DEFAULT_REDIS_VERSION, help='Redis version (e.g. 6, 6.0, 6.0.1')
parser.add_argument('-b', '--branch', type=str, help='Redis branch (e.g. 6, 6.0)')
parser.add_argument('--valgrind', action="store_true", help="Build a Valgdind-compatible Redis (i.e. with libc & -O0)")
parser.add_argument('--libc', action="store_true", help="Build with libc instead of jemalloc")
parser.add_argument('--no-tls', action="store_true", help="Do not support TLS")
parser.add_argument('--repo', action="store_true", help='Install from package repo')
parser.add_argument('--force', action="store_true", help="Install even if redis-server is present")
parser.add_argument('--prepare', action="store_true", help="Only install prerequisites, do not build")
parser.add_argument('--no-prepare', action="store_true", help="Do not install prerequisites, just build")
parser.add_argument('--no-install', action="store_true", help="Build but don't install")
parser.add_argument('-p', '--just-print-version', action="store_true", help="Jump print version, do not install")
parser.add_argument('--keep', action="store_true", help="Do not remove source files and build artifacts")
parser.add_argument('--workdir', type=str, help='Directory in which to extract and build')
parser.add_argument('--info-file', type=str, help='Information file path')
parser.add_argument('--into', type=str, help='Copy artifacts to DIR')
parser.add_argument('-V', '--verbose', action="store_true", help="Verbose operation")
# parser.add_argument('--strict', action="store_true", help="Verify we get the Redis version we ask for")
args = parser.parse_args()

if args.source and args.repo:
    fatal('conflicting options: --source, --repo. Aborting.')
if args.valgrind and args.repo:
    fatal('--valgrind and --repo are incompatible. Aborting.')
if not args.source and not args.repo:
    args.source = True

if args.branch and args.repo:
    fafal('--branch and --repo are incompatible. Aborting.')
if args.version and args.repo:
    fatal('--version and --repo are incompatible. Aborting.')
if args.version and args.branch:
    fatal('conflicting options: --version, --branch. Aborting.')

if args.just_print_version:
    setup = RedisSourceSetup(version=args.version, nop=args.nop)
    version = setup.get_requested_redis_versions()[0]
    print(version)
    exit(0)

if paella.Setup.has_command("redis-server") and not args.force and not args.no_install:
    vermsg = sh('redis-server --version')
    eprint("redis-server is present:\n{}".format(vermsg))
    exit(0)

try:
    if args.source:
        RedisSourceSetup(version=args.version, branch=args.branch,
                         valgrind=args.valgrind, libc=args.libc, no_tls=args.no_tls,
                         keep=args.keep, nop=args.nop, into=args.into,
                         just_prepare=args.prepare, no_prepare=args.no_prepare,
                         no_install=args.no_install, workdir=args.workdir,
                         info_file=args.info_file, verbose=args.verbose).setup()
    else:
        RedisRepoSetup(nop=args.nop).setup()
except Exception as x:
    traceback.print_exc()
    fatal(str(x))

exit(0)
