#!/usr/bin/env python
from __future__ import print_function

import fnmatch
import os
import sys
import argparse
import subprocess
import traceback
import globals
import toolconfig
import multi_facility_process_helper_client
from globals import UserParamsException


def listCurrentReleases(args):
    """List the releases that are currently deployed in all the facilities for the given application.
    All the work happens in determineIOCVersions, determineHLAVersion, and determinePyDMDisplayVersion in multi_facility_process_helper.py
    This is just formatting.
    """
    packageInfo = toolconfig.determinePackageInfoFromArgs(args)
    for facility in toolconfig.getListOfFacilities(packageInfo['type']):
        arg = {}
        arg['name'] = packageInfo['name']
        arg['releaseFolder'] = toolconfig.determineReleaseFolder(facility, packageInfo['name'], packageInfo['type'])
        arg['linkFolder'] = toolconfig.determineLinkFolder(facility, packageInfo['name'], packageInfo['type'])
        if packageInfo['type'] in ['SIOC', 'HIOC']:
            commandsAndArgs = []
            commandsAndArgs.append(('determineIOCVersions', arg))
            try:
                results = multi_facility_process_helper_client.executeCommandsOnFacility(facility, commandsAndArgs)
            except multi_facility_process_helper_client.connectionException as e:
                print(
                "Skipping listing current releases in facility ", facility, " as we are unable to talk to the server.")
                if globals.verbose:
                    traceback.print_exc(file=sys.stdout)
                continue

            iocVersionResult = results[0]
            if globals.verbose:
                print("After remote execution, results is {0}".format(iocVersionResult))
            print("Current versions on facility:\033[1;31m {0} \033[0m".format(facility))
            if not 'master' in iocVersionResult:
                # Facility does not have a iocTop folder for this package
                continue;
            masterRelease = iocVersionResult['master']
            print("Current master release => {0}".format(masterRelease))
            for ioc in iocVersionResult['iocs']:
                if fnmatch.fnmatch(ioc['ioc'], args.pattern):
                    print("IOC: {0} => {1} {2}".format(ioc['ioc'], ioc['release'], "(*)" if not ioc['isMaster'] else ""))
        elif packageInfo['type'] in ['PyDM']:
            commandsAndArgs = []
            commandsAndArgs.append(('determinePyDMDisplayVersion', arg))
            try:
                results = multi_facility_process_helper_client.executeCommandsOnFacility(facility, commandsAndArgs)
            except multi_facility_process_helper_client.connectionException as e:
                print(
                "Skipping listing current releases in facility ", facility, " as we are unable to talk to the server.")
                if globals.verbose:
                    traceback.print_exc(file=sys.stdout)
                continue

            pydmDisplayVersionResult = results[0]
            if globals.verbose:
                print("After remote execution, results is {0}".format(pydmDisplayVersionResult))
            print("Current version on facility:\033[1;31m {0} \033[0m => {1}".format(facility, pydmDisplayVersionResult))
        else:
            commandsAndArgs = []
            commandsAndArgs.append(('determineHLAVersion', arg))
            try:
                results = multi_facility_process_helper_client.executeCommandsOnFacility(facility, commandsAndArgs)
            except multi_facility_process_helper_client.connectionException as e:
                print(
                "Skipping listing current releases in facility ", facility, " as we are unable to talk to the server.")
                if globals.verbose:
                    traceback.print_exc(file=sys.stdout)
                continue

            hlaVersionResult = results[0]
            if globals.verbose:
                print("After remote execution, results is {0}".format(hlaVersionResult))
            print("Current version on facility:\033[1;31m {0} \033[0m => {1}".format(facility, hlaVersionResult))

def listAvailableReleases(args):
    """List the releases that are currently available. All we are doing here is an ls in the release folder"""
    packageInfo = toolconfig.determinePackageInfoFromArgs(args)
    availableReleases = {}
    for facility in toolconfig.getListOfFacilities(packageInfo['type']):
        commandsAndArgs = []
        releaseFolder = os.path.join(toolconfig.determineReleaseFolder(facility, packageInfo['name'], packageInfo['type']),  packageInfo['name'])
        commandsAndArgs.append(('ls', [releaseFolder]))
        results = multi_facility_process_helper_client.executeCommandsOnFacility(facility, commandsAndArgs)
        paths = results[0]
        if globals.verbose:
            print("After remote execution, results is {0}".format(paths))
        for path in paths:
            if path['isFolder'] and not path['isLink']:
                 if path['name'] in availableReleases:
                     availableReleases[path['name']].append(facility)
                 else:
                     availableReleases[path['name']] = [facility]
    for release in sorted(availableReleases.keys()):
        facilitiesWithRelease = availableReleases[release]
        if facilitiesWithRelease == toolconfig.getListOfFacilities(packageInfo['type']):
            print(release)
        else:
            print("{0} - avail only on {1}".format(release, ",".join(facilitiesWithRelease)))

def listIOCSpecificReleases(args):
    '''List IOCs and their releases that point to a release other than the master release.
    Very similar to listCurrentReleases - changes are to only list IOCS which point to something other than the current release'''
    packageInfo = toolconfig.determinePackageInfoFromArgs(args)
    for facility in toolconfig.getListOfFacilities(packageInfo['type']):
        arg = {}
        arg['name'] = packageInfo['name']
        arg['releaseFolder'] = toolconfig.determineReleaseFolder(facility, packageInfo['name'], packageInfo['type'])
        arg['linkFolder'] = toolconfig.determineLinkFolder(facility, packageInfo['name'], packageInfo['type'])
        if packageInfo['type'] in ['SIOC', 'HIOC']:
            commandsAndArgs = []
            commandsAndArgs.append(('determineIOCVersions', arg))
            results = multi_facility_process_helper_client.executeCommandsOnFacility(facility, commandsAndArgs)
            iocVersionResult = results[0]
            if globals.verbose:
                print("After remote execution, results is {0}".format(iocVersionResult))
            if not iocVersionResult:
                print("Package {0} does not seem to be in facility {1}".format(packageInfo['name'], facility))
                continue
            print("IOC specific versions on facility:\033[1;31m {0} \033[0m".format(facility))
            masterRelease = iocVersionResult['master']
            print("Current master release => {0}".format(masterRelease))
            for ioc in iocVersionResult['iocs']:
                if not ioc['isMaster'] and fnmatch.fnmatch(ioc['ioc'], args.pattern):
                    print("IOC: {0} => {1} {2}".format(ioc['ioc'], ioc['release'], "(*)" if not ioc['isMaster'] else ""))
        else:
            print("IOC specific releases do not apply to software packages of type {0}".format(packageInfo['type']))

def determineUnusedReleases(name, type):
    '''Determine the list of releases that are unused'''
    usedReleases = set()
    availableReleases = set()
    for facility in toolconfig.getListOfFacilities(type):
        arg = {}
        arg['name'] = name
        arg['releaseFolder'] = toolconfig.determineReleaseFolder(facility, name, type)
        arg['linkFolder'] = toolconfig.determineLinkFolder(facility, name, type)
        commandsAndArgs = []
        if type in ['SIOC', 'HIOC']:
            determineVersionCommand = 'determineIOCVersions'
        elif type == 'PyDM':
            determineVersionCommand = 'determinePyDMDisplayVersion'
        else:
            determineVersionCommand = 'determineHLAVersion'

        commandsAndArgs.append((determineVersionCommand, arg))
        releaseFolder = os.path.join(toolconfig.determineReleaseFolder(facility, name, type),  name)
        commandsAndArgs.append(('ls', [releaseFolder]))
        results = multi_facility_process_helper_client.executeCommandsOnFacility(facility, commandsAndArgs)
        versionResult = results[0]
        if globals.verbose:
            print("After remote execution, version results is {0}".format(versionResult))
        if not versionResult:
            print("Package {0} does not seem to be in facility {1}".format(name, facility))
            continue
        usedReleases.add(versionResult['master'] if type in ['SIOC', 'HIOC'] else versionResult)
        if type in ['SIOC', 'HIOC']:
            for ioc in versionResult['iocs']:
                if not ioc['isMaster']:
                    usedReleases.add(ioc['release'])
        if globals.verbose:
            print("Used releases {0}".format(usedReleases))
        # Let's compute the available releases
        paths = results[1]
        if globals.verbose:
            print("After remote execution, available releases paths is {0}".format(paths))
        for path in paths:
            if path['isFolder'] and not path['isLink']:
                availableReleases.add(path['name'])
        if globals.verbose:
            print("All available releases {0}".format(availableReleases))
    unusedReleases = availableReleases.difference(usedReleases)
    return unusedReleases

def listUnusedReleases(args):
    ''' List unused releases. This is a combination of listCurrentReleases and listAvailableReleases.
    We gather the releases available on all sites and the releases used on all sites.
    Unused releases is the set difference between these.
    '''
    packageInfo = toolconfig.determinePackageInfoFromArgs(args)
    unusedReleases = determineUnusedReleases(packageInfo['name'], packageInfo['type'])
    if unusedReleases:
        print("Releases that are not currently being used")
        for release in unusedReleases:
            print("-->", release)
            print("--> {0}".format(release))
    else:
        print("All available releases are being used")

def listRunningReleases(args):
    """List the releases that are currently deployed and running. Note this applies only for IOC applications.
    """
    packageInfo = toolconfig.determinePackageInfoFromArgs(args)
    if packageInfo['type'] not in ['SIOC', 'HIOC']:
        print("Running releases can be listed only for IOC applications")
        return
    for facility in toolconfig.getListOfFacilities(packageInfo['type']):
        arg = {}
        arg['name'] = packageInfo['name']
        arg['releaseFolder'] = toolconfig.determineReleaseFolder(facility, packageInfo['name'], packageInfo['type'])
        arg['linkFolder'] = toolconfig.determineLinkFolder(facility, packageInfo['name'], packageInfo['type'])
        arg['timeout'] = 1
        if packageInfo['type'] in ['SIOC', 'HIOC']:
            commandsAndArgs = []
            commandsAndArgs.append(('determineIOCVersions', arg))
            commandsAndArgs.append(('determineIOCRunningVersions', arg))
            results = multi_facility_process_helper_client.executeCommandsOnFacility(facility, commandsAndArgs)
            iocVersionResult = results[0]
            iocRunningResult = results[1]
            if globals.verbose:
                print("After remote execution, results is {0}".format(iocVersionResult))
                print("After remote execution, running results is {0}".format(iocRunningResult))
            print("Current versions on facility:\033[1;31m {0} \033[0m".format(facility))
            if not 'master' in iocVersionResult:
                # Facility does not have a iocTop folder for this package
                continue;
            masterRelease = iocVersionResult['master']
            print("Current master release => {0}".format(masterRelease))
            for ioc in iocVersionResult['iocs']:
                if fnmatch.fnmatch(ioc['ioc'], args.pattern):
                    print("IOC: {0} => {1} {2} =}} {3}".format(ioc['ioc'], ioc['release'], "(*)" if not ioc['isMaster'] else "", iocRunningResult[ioc['ioc']] if ioc['ioc'] in iocRunningResult else "N/A"))


def execute(args):
    parser = argparse.ArgumentParser(prog="cram ls")
    parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose logging')
    parser.add_argument('-n', '--name', help='Specify the application name; we automatically pick this up from the package info if we are running this command in the folder containing the release')
    parser.add_argument('-t', '--type', help='Specifies the type of application from the deployment perspective. Possible values are ' + ", ".join(toolconfig.appTypes.keys()))
    parser.add_argument('-p', '--pushed', action='store_true', help='Show releases that are have been pushed/submitted and are available for this software package.')
    parser.add_argument('-u', '--unused', action='store_true', help='Show releases that are not being used currently.')
    parser.add_argument('-i', '--iocspecific', action='store_true', help='For IOC software packages, show IOCs and their releases that are different from the master release for this software package.')
    parser.add_argument('-r', '--running', action='store_true', help='For IOC software packages, also show the versions currently running on the IOC.')
    parser.add_argument('pattern', nargs='?', default='*', help='Search pattern for IOCs. Supported wildcards are * and ?. Quote pattern to prevent globbing.')

    args = parser.parse_args(args)
    globals.verbose = args.verbose
    try:
        if args.pattern != '*' and (args.pushed or args.unused):
            print("Warning: Search pattern is irrelevant when --pushed or --unused is specified")
        if args.pushed:
            listAvailableReleases(args)
        elif args.iocspecific:
            listIOCSpecificReleases(args)
        elif args.unused:
            listUnusedReleases(args)
        elif args.running:
            listRunningReleases(args)
        else:
            listCurrentReleases(args)
    except UserParamsException as upe:
        if not globals.verbose and not globals.isunittests:
            print(upe)
            sys.exit(-1)
        else:
            raise


if __name__ == "__main__":
    execute(sys.argv[1:])
