#
# Copyright (c) SAS Institute Inc.
#
# 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.
#


"""
rBuilder facade module

This module provides a high-level stable API for use within rbuild
plugins.  It provides public methods which are only to be accessed
via C{handle.facade.rbuilder} which is automatically available to
all plugins through the C{handle} object.
"""
import os
import re
import time
import socket
import random
import urllib
import urllib2
import urlparse

import robj
from xobj import xobj

from conary.lib import util
from conary.lib.cfg import ConfigFile

from rpath_proddef import api1 as proddef
from rbuild import constants
from rbuild import errors
from rbuild import facade


class _rBuilderConfig(ConfigFile):
    serverUrl = None

    def __init__(self):
        ConfigFile.__init__(self)
        if os.environ.has_key('HOME'):
            fn = '/'.join((os.environ['HOME'], '.rbuilderrc'))
            self.read(fn, exception=False)


class _AbstractRbuilderClient(object):
    """
    Abstract class for both the XMLRPC and REST rBuilder client to
    inherit from.
    """

    def __init__(self, rbuilderUrl, user, pw, handle):
        self.rbuilderUrl = rbuilderUrl
        self._handle = handle


class RbuilderRPCClient(_AbstractRbuilderClient):
    """
    XMLRPC rBuilder Client. As rBuilder moves functionality to the REST
    interface this client will become deprecated.
    """

    def __init__(self, rbuilderUrl, user, pw, handle):
        _AbstractRbuilderClient.__init__(self, rbuilderUrl, user, pw, handle)
        rpcUrl = rbuilderUrl + '/xmlrpc-private'
        self.server = facade.ServerProxy(rpcUrl, username=user, password=pw,
                allow_none=True)

    def _pollBuild(self, buildId, interval=10, max_dropped=3):
        dropped = 0
        buildStatus = None
        while buildStatus is None:
            try:
                error, buildStatus = self.server.getBuildStatus(buildId)
            except socket.timeout:
                dropped += 1
                if dropped >= max_dropped:
                    raise errors.RbuildError(
                        'rBuilder connection timed out after 3 attempts')
                self._handle.ui.info(
                    'Status request timed out, trying again')
                time.sleep(interval)
                continue

        if error:
            raise errors.RbuilderError(*buildStatus)
        return buildStatus

    def getBranchIdFromName(self, productName, versionName):
        #pylint: disable-msg=R0914
        # not a great candidate for refactoring
        productId = self.getProductId(productName)
        error, versionList = self.server.getProductVersionListForProduct(
                                                                    productId)
        if error:
            raise errors.RbuilderError(*versionList)

        versionNames = []
        # W0612: leave unused variables as documentation
        # W0631: versionId is guaranteed to be defined
        #pylint: disable-msg=W0612,W0631
        for (versionId2, productId2,
             namespace, versionName2, desc)  in versionList:
            versionNames.append(versionName2)
            if versionName == versionName2:
                return versionId2

        errstr = '%s is not a valid version for product %s.' % \
            (versionName, productName)
        if versionNames:
            errstr += '\nValid versions are: %s' % \
                ', '.join(versionNames)
        else:
            errstr += '\nNo versions found for product %s.' % productName
        raise errors.RbuildError(errstr)

    def getProductLabelFromNameAndVersion(self, productName, versionName):
        versionId = self.getBranchIdFromName(productName, versionName)
        error, stream = self.server.getProductDefinitionForVersion(versionId)
        if error:
            raise errors.RbuilderError(*stream)
        product = proddef.ProductDefinition(stream)
        return product.getProductDefinitionLabel()

    def startProductBuilds(self, productName, versionName, stageName,
            buildNames=None, groupSpecs=None, groupVersion=None):
        versionId = self.getBranchIdFromName(productName, versionName)
        methodArgs = [versionId, stageName, False, buildNames ]
        if groupSpecs is not None:
            # image builds from system model was added later (Sept # 2013),
            # and it causes tracebacks on older rbuilders; only supply
            # it if really needed
            methodArgs.extend([None, groupSpecs])
        elif groupVersion is not None:
            methodArgs.append(groupVersion)

        error, buildIds = self.server.newBuildsFromProductDefinition(
                *methodArgs)
        if error:
            raise errors.RbuilderError(*buildIds)
        return buildIds

    def watchImages(self, buildIds, timeout = 0, interval = 5, quiet=False):
        interval = 10
        st = time.time()
        timedOut = False
        dropped = 0
        finalStatus = {}

        activeBuilds = dict.fromkeys(buildIds)
        while activeBuilds:
            for buildId in list(activeBuilds):
                try:
                    error, buildStatus = self.server.getBuildStatus(buildId)
                except socket.timeout:
                    dropped += 1
                    if dropped >= 3:
                        raise errors.RbuildError(
                            'rBuilder connection timed out after 3 attempts')
                    self._handle.ui.info(
                        'Status request timed out, trying again')
                    time.sleep(interval)
                    continue

                if error:
       
             raise errors.RbuilderError(*buildStatus)
                dropped = 0
                if activeBuilds[buildId] != 
buildStatus:
                    st = time.time() # reset timeout counter if status changes
                    activeBuilds[buildId] = buildStatus
                    if not quiet:
                        # try to get build name, fall back to just ID
                        try:
                            id = '%s %s' % (buildStatus['name'], buildId)
                        except KeyError:
                            id = buildId
                        self._handle.ui.write('%s: %s "%s"',
                            id, self.statusNames.get(buildStatus['status'],
                            self.statusNames[-1]), buildStatus['message'])
                    if activeBuilds[buildId]['status'] > 200:
                        finalStatus[buildId] = activeBuilds.pop(buildId)
            if activeBuilds:
                time.sleep(interval)
                if timeout and time.time() - st > timeout:
                    timedOut = True
                    break

        if timedOut:
            self._handle.ui.error('Timed out while waiting for build status'
                ' to change (%d seconds)', timeout)
        else:
            self._handle.ui.write('All jobs completed')
        if activeBuilds:
            self._handle.ui.warning('Unfinished builds:')
            self._printStatus(activeBuilds, '    Last status: ')
        self._handle.ui.write('Finished builds:')
        self._printStatus(finalStatus, '    ')
        if any(x['status'] != 300 for x in finalStatus.values()):
            return False
        else:
            return True

    statusNames = {
            -1:  'Unknown',
            0:   'Waiting',
            100: 'Running',
            200: 'Built',
            300: 'Finished',
            301: 'Failed',
            302: 'Killed',
            401: 'No job',
        }

    def _printStatus(self, statusDict, prefix = ''):
        f