"""
BitBake 'Fetch' implementation for mercurial DRCS (hg).

"""

# Copyright (C) 2003, 2004  Chris Larson
# Copyright (C) 2004        Marcin Juszkiewicz
# Copyright (C) 2007        Robert Schuster
#
# SPDX-License-Identifier: GPL-2.0-only
#
# Based on functions from the base bb module, Copyright 2003 Holger Schurig
#

import os
import bb
import errno
from bb.fetch2 import FetchMethod
from bb.fetch2 import FetchError
from bb.fetch2 import MissingParameterError
from bb.fetch2 import runfetchcmd
from bb.fetch2 import logger

class Hg(FetchMethod):
    """Class to fetch from mercurial repositories"""
    def supports(self, ud, d):
        """
        Check to see if a given url can be fetched with mercurial.
        """
        return ud.type in ['hg']

    def supports_checksum(self, urldata):
        """
        Don't require checksums for local archives created from
        repository checkouts.
        """ 
        return False

    def urldata_init(self, ud, d):
        """
        init hg specific variable within url data
        """
        if not "module" in ud.parm:
            raise MissingParameterError('module', ud.url)

        ud.module = ud.parm["module"]

        if 'protocol' in ud.parm:
            ud.proto = ud.parm['protocol']
        elif not ud.host:
            ud.proto = 'file'
        else:
            ud.proto = "hg"

        # Create paths to mercurial checkouts
        hgsrcname = '%s_%s_%s' % (ud.module.replace('/', '.'), \
                            ud.host, ud.path.replace('/', '.'))
        mirrortarball = 'hg_%s.tar.gz' % hgsrcname
        ud.fullmirror = os.path.join(d.getVar("DL_DIR"), mirrortarball)
        ud.mirrortarballs = [mirrortarball]

        hgdir = d.getVar("HGDIR") or (d.getVar("DL_DIR") + "/hg")
        ud.pkgdir = os.path.join(hgdir, hgsrcname)
        ud.moddir = os.path.join(ud.pkgdir, ud.module)
        ud.localfile = ud.moddir
        ud.basecmd = d.getVar("FETCHCMD_hg") or "/usr/bin/env hg"

        ud.setup_revisions(d)

        if 'rev' in ud.parm:
            ud.revision = ud.parm['rev']
        elif not ud.revision:
            ud.revision = self.latest_revision(ud, d)

        ud.write_tarballs = d.getVar("BB_GENERATE_MIRROR_TARBALLS")

    def need_update(self, ud, d):
        revTag = ud.parm.get('rev', 'tip')
        if revTag == "tip":
            return True
        if not os.path.exists(ud.localpath):
            return True
        return False

    def try_premirror(self, ud, d):
        # If we don't do this, updating an existing checkout with only premirrors
        # is not possible
        if bb.utils.to_boolean(d.getVar("BB_FETCH_PREMIRRORONLY")):
            return True
        if os.path.exists(ud.moddir):
            return False
        return True

    def _buildhgcommand(self, ud, d, command):
        """
        Build up an hg commandline based on ud
        command is "fetch", "update", "info"
        """

        proto = ud.parm.get('protocol', 'http')

        host = ud.host
        if proto == "file":
            host = "/"
            ud.host = "localhost"

        if not ud.user:
            hgroot = host + ud.path
        else:
            if ud.pswd:
                hgroot = ud.user + ":" + ud.pswd + "@" + host + ud.path
            else:
                hgroot = ud.user + "@" + host + ud.path

        if command == "info":
            return "%s identify -i %s://%s/%s" % (ud.basecmd, proto, hgroot, ud.module)

        options = [];

        # Don't specify revision for the fetch; clone the entire repo.
        # This avoids an issue if the specified revision is a tag, because
        # the tag actually exists in the specified revision + 1, so it won't
        # be available when used in any successive commands.
        if ud.revision and command != "fetch":
            options.append("-r %s" % ud.revision)

        if command == "fetch":
            if ud.user and ud.pswd:
                cmd = "%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" clone %s %s://%s/%s %s" % (ud.basecmd, ud.user, ud.pswd, proto, " ".join(options), proto, hgroot, ud.module, ud.module)
            else:
                cmd = "%s clone %s %s://%s/%s %s" % (ud.basecmd, " ".join(options), proto, hgroot, ud.module, ud.module)
        elif command == "pull":
            # do not pass options list; limiting pull to rev causes the local
            # repo not to contain it and immediately following "update" command
            # will crash
            if ud.user and ud.pswd:
                cmd = "%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" pull" % (ud.basecmd, ud.user, ud.pswd, proto)
            else:
                cmd = "%s pull" % (ud.basecmd)
        elif command == "update" or command == "up":
            if ud.user and ud.pswd:
                cmd = "%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" update -C %s" % (ud.basecmd, ud.user, ud.pswd, proto, " ".join(options))
            else:
                cmd = "%s update -C %s" % (ud.basecmd, " ".join(options))
        else:
            raise FetchError("Invalid hg command %s" % command, ud.url)

        return cmd

    def download(self, ud, d):
        """Fetch url"""

        logger.debug(2, "Fetch: checking for module directory '" + ud.moddir + "'")

        # If the checkout doesn't exist and the mirror tarball does, extract it
        if not os.path.exists(ud.pkgdir) and os.path.exists(ud.fullmirror):
            bb.utils.mkdirhier(ud.pkgdir)
            runfetchcmd("tar -xzf %s" % (ud.fullmirror), d, workdir=ud.pkgdir)

        if os.access(os.path.join(ud.moddir, '.hg'), os.R_OK):
            # Found the source, check whether need pull
            updatecmd = self._buildhgcommand(ud, d, "update")
            logger.debug(1, "Running %s", updatecmd)
            try:
                runfetchcmd(updatecmd, d, workdir=ud.moddir)
            except bb.fetch2.FetchError:
                # Runnning pull in the repo
                pullcmd = self._buildhgcommand(ud, d, "pull")
                logger.info("Pulling " + ud.url)
                # update sources there
                logger.debug(1, "Running %s", pullcmd)
                bb.fetch2.check_network_access(d, pullcmd, ud.url)
                runfetchcmd(pullcmd, d, workdir=ud.moddir)
                try:
                    os.unlink(ud.fullmirror)
                except OSError as exc:
                    if exc.errno != errno.ENOENT:
                        raise

        # No source found, clone it.
        if not os.path.exists(ud.moddir):
            fetchcmd = self._buildhgcommand(ud, d, "fetch")
            logger.info("Fetch " + ud.url)
            # check out sources there
            bb.utils.mkdirhier(ud.pkgdir)
            logger.debug(1, "Running %s", fetchcmd)
            bb.fetch2.check_network_access(d, fetchcmd, ud.url)
            runfetchcmd(fetchcmd, d, workdir=ud.pkgdir)

        # Even when we clone (fetch), we still need to update as hg's clone
        # won't checkout the specified revision if its on a branch
        updatecmd = self._buildhgcommand(ud, d, "update")
        logger.debug(1, "Running %s", updatecmd)
        runfetchcmd(updatecmd, d, workdir=ud.moddir)

    def clean(self, ud, d):
        """ Clean the hg dir """

        bb.utils.remove(ud.localpath, True)
        bb.utils.remove(ud.fullmirror)
        bb.utils.remove(ud.fullmirror + ".done")

    def supports_srcrev(self):
        return True

    def _latest_revision(self, ud, d, name):
        """
        Compute tip revision for the url
        """
        bb.fetch2.check_network_access(d, self._buildhgcommand(ud, d, "info"), ud.url)
        output = runfetchcmd(self._buildhgcommand(ud, d, "info"), d)
        return output.strip()

    def _build_revision(self, ud, d, name):
        return ud.revision

    def _revision_key(self, ud, d, name):
        """
        Return a unique key for the url
        """
        return "hg:" + ud.moddir

    def build_mirror_data(self, ud, d):
        # Generate a mirror tarball if needed
        if ud.write_tarballs == "1" and not os.path.exists(ud.fullmirror):
            # it's possible that this symlink points to read-only filesystem with PREMIRROR
            if os.path.islink(ud.fullmirror):
                os.unlink(ud.fullmirror)

            logger.info("Creating tarball of hg repository")
            runfetchcmd("tar -czf %s %s" % (ud.fullmirror, ud.module), d, workdir=ud.pkgdir)
            runfetchcmd("touch %s.done" % (ud.fullmirror), d, workdir=ud.pkgdir)

    def localpath(self, ud, d):
        return ud.pkgdir

    def unpack(self, ud, destdir, d):
        """
        Make a local clone or export for the url
        """

        revflag = "-r %s" % ud.revision
        subdir = ud.parm.get("destsuffix", ud.module)
        codir = "%s/%s" % (destdir, subdir)

        scmdata = ud.parm.get("scmdata", "")
        if scmdata != "nokeep":
            proto = ud.parm.get('protocol', 'http')
            if not os.access(os.path.join(codir, '.hg'), os.R_OK):
                logger.debug(2, "Unpack: creating new hg repository in '" + codir + "'")
                runfetchcmd("%s init %s" % (ud.basecmd, codir), d)
            logger.debug(2, "Unpack: updating source in '" + codir + "'")
            if ud.user and ud.pswd:
                runfetchcmd("%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" pull %s" % (ud.basecmd, ud.user, ud.pswd, proto, ud.moddir), d, workdir=codir)
            else:
                runfetchcmd("%s pull %s" % (ud.basecmd, ud.moddir), d, workdir=codir)
            if ud.user and ud.pswd:
                runfetchcmd("%s --config auth.default.prefix=* --config auth.default.username=%s --config auth.default.password=%s --config \"auth.default.schemes=%s\" up -C %s" % (ud.basecmd, ud.user, ud.pswd, proto, revflag), d, workdir=codir)
            else:
                runfetchcmd("%s up -C %s" % (ud.basecmd, revflag), d, workdir=codir)
        else:
            logger.debug(2, "Unpack: extracting source to '" + codir + "'")
            runfetchcmd("%s archive -t files %s %s" % (ud.basecmd, revflag, codir), d, workdir=ud.moddir)
