#!/usr/bin/env python3

# Copyright 2011 Red Hat Inc.
#
# This file is part of solenopsis
#
# solenopsis is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

"""This is where all the action happens.  This file picks the right library
method to run
"""

__author__ = "Patrick Connelly (patrick@deadlypenguin.com)"
__version__ = "1.3"

import argparse
import logging
import sys

from lib import ant
from lib import logger
from lib import create
from lib import environment
from lib import parser
from lib import osutils

ABSOLUTE = False
FORCE = False
DEBUG = False
VERBOSE = False

DEPENDENT = None
MASTER = None
HOME = None
SFIGNORE = None

TMPDIR = None
MAXPOLL = None
REQUESTID = None

LOGTYPE = None
CHECKONLY = False

SRC_DIR = ""

CONFIG_FILE = '/usr/share/solenopsis/config/defaults.cfg'

PRIMARY_COMMANDS = [ 'push', 'destructive-push', 'git-destructive-push', 'cached-destructive-push',
                    'git-push', 'pull-full', 'pull-full-to-master', 'pull',
                    'pull-to-master', 'create', 'config', 'file-push',
                    'describe-metadata', 'list-metadata', 'run-tests', 'report-diff',
                    'delta-push', 'cached-delta-push',
                    'generate-package', 'generate-full-package',
                    'selective-pull', 'selective-pull-to-master',
                    'file-destructive-push', 'file-delete' ]
LOG_TYPES = [ 'None', 'Debugonly', 'Profiling', 'Callout', 'Detail' ]
TEST_LEVELS = [ 'NoTestRun', 'RunSpecifiedTests', 'RunLocalTests', 'RunAllTestInOrg' ]
METADATA_TYPES = [ 'CustomApplication', 'ApexClass', 'ApexComponent', 'Dashboard',
                    'DataCategoryGroup', 'Document', 'EmailTemplate', 'EntitlementTemplate',
                    'HomePageComponent', 'HomePageLayout', 'CustomLabel', 'Layout', 'Letterhead',
                    'CustomObject', 'CustomObjectTranslation', 'ApexPage', 'PermissionSet',
                    'Portal', 'Profile', 'RemoteSiteSetting', 'Report', 'ReportType', 'Scontrol',
                    'CustomSite', 'StaticResource', 'CustomTab', 'ApexTrigger', 'CustomPageWebLink',
                    'Workflow', 'GlobalPicklist', 'GlobalValueSet', 'GlobalValueSetTranslation',
                    'StandardValueSet', 'StandardValueSetTranslation', 'CustomMetadata' ]
SOL_VERSIONS = [ '1.1', '1.2' ]
API_VERSIONS = [ '16.0', '17.0', '18.0', '19.0',
                    '20.0', '21.0', '22.0', '23.0', '24.0', '25.0', '26.0', '27.0', '28.0', '29.0',
                    '30.0', '31.0', '32.0', '33.0', '34.0', '35.0', '36.0', '37.0', '38.0', '39.0',
                    '40.0', '41.0', '42.0', '43.0', '44.0', '45.0', '46.0', '47.0', '48.0', '49.0',
                    '50.0', '51.0', '52.0', '53.0', '54.0' ]

def setRelativity(status):
    """Sets the relativity based on the environment home

    status - True/False
    """
    global ABSOLUTE # pylint: disable=global-statement
    ABSOLUTE = status

def isRelative():
    """Returns the relativity"""
    return ABSOLUTE

def setForce(status):
    """Sets if file based actions should be forced

    status - True/False
    """
    global FORCE # pylint: disable=global-statement
    FORCE = status

def isForced():
    """Returns if file based actions should be forced"""
    return FORCE

def setSourceDir(path):
    """Sets the source dir

    path - The source dir
    """
    global SRC_DIR # pylint: disable=global-statement
    SRC_DIR = path

def getSourceDir():
    """Gets the source dir"""
    return SRC_DIR

def checkSolenopsis():
    """Checks to see if solenopsis is setup"""
    if not environment.hasConfigFile():
        logger.critical(
            'Could not find solenopsis property file at "%s"' % (
                environment.getDefaultConfig(),)
            )
        setupInput = input("Would you like to set up solenopsis? (Y/N): ")
        if setupInput.lower() == 'y':
            environment.setupInteractive()
        else:
            sys.exit(-1)

def setup():
    """Sets up and reads the config file"""
    try:
        config = parser.getParser()
        config.read(CONFIG_FILE)
        create.setApiVersion(config.get('general', 'api_version'))
    except: # pylint: disable=bare-except
        logger.critical('Could not open config file "%s"' % (CONFIG_FILE,))
        sys.exit(-1)

def handlePriCommand(command, secondary): # pylint: disable=too-many-branches,too-many-statements
    """Does the bulk of the work translating the command into the calls

    command - The command to run
    secondary - The rest of the command to be passed on
    """
    if command == 'push':
        checkSolenopsis()
        ant.push()
    elif command == 'destructive-push':
        checkSolenopsis()
        ant.destructivePush()
    elif command == 'git-destructive-push':
        checkSolenopsis()
        ant.gitDestructivePush()
    elif command == 'cached-destructive-push':
        checkSolenopsis()
        ant.cachedDestructivePush()
    elif command == 'git-push':
        checkSolenopsis()
        ant.gitPush()
    elif command == 'file-push':
        checkSolenopsis()
        environment.parseSolConfig()

        homeKey = ('solenopsis.env.%s.HOME' % (environment.getMaster(),)).lower()
        rawConfig = environment.getRawConfig()
        rootDir = None

        if osutils.isPython2():
            if rawConfig.has_key(homeKey):
                rootDir = rawConfig[homeKey]
        else:
            if homeKey in rawConfig:
                rootDir = rawConfig[homeKey]

        if HOME:
            rootDir = HOME

        ant.setRootDir(rootDir)
        ant.filePush(secondary)
    elif command in ('file-destructive-push', 'file-delete'):
        checkSolenopsis()
        environment.parseSolConfig()

        homeKey = ('solenopsis.env.%s.HOME' % (environment.getMaster(),)).lower()
        rawConfig = environment.getRawConfig()
        rootDir = None

        if osutils.isPython2():
            if rawConfig.has_key(homeKey):
                rootDir = rawConfig[homeKey]
        else:
            if homeKey in rawConfig:
                rootDir = rawConfig[homeKey]

        if HOME:
            rootDir = HOME

        ant.setRootDir(rootDir)
        ant.fileDestructivePush(secondary)
    elif command == 'pull-full':
        checkSolenopsis()
        ant.pullFull()
    elif command == 'pull-full-to-master':
        checkSolenopsis()
        ant.pullFullToMaster()
    elif command == 'pull':
        checkSolenopsis()
        ant.pull()
    elif command == 'pull-to-master':
        checkSolenopsis()
        ant.pullToMaster()
    elif command == 'describe-metadata':
        checkSolenopsis()
        ant.describeMetadata()
    elif command == 'generate-package':
        checkSolenopsis()
        ant.generatePackage()
    elif command == 'generate-full-package':
        checkSolenopsis()
        ant.generateFullPackage()
    elif command == 'list-metadata':
        checkSolenopsis()
        ant.listMetadata(secondary[0])
    elif command == 'run-tests':
        checkSolenopsis()

        if secondary:
            filenames = ','.join(secondary)
            logger.debug('Setting tests to run to %r' % (filenames,))
            ant.addFlag('sf.testClasses=%r' % (filenames,))

        ant.runTests()
    elif command == 'selective-pull':
        checkSolenopsis()
        ant.selectivePull()
    elif command == 'selective-pull-to-master':
        checkSolenopsis()
        ant.selectivePullToMaster()
    elif command == 'report-diff':
        checkSolenopsis()
        ant.reportDiff()
    elif command == 'delta-push':
        checkSolenopsis()
        ant.deltaPush()
    elif command == 'cached-delta-push':
        checkSolenopsis()
        ant.cachedDeltaPush()
    elif command == 'create':
        create.setRelativity(isRelative())
        create.setForce(isForced())
        create.setSourceDir(getSourceDir())
        create.createFile(secondary)
    elif command == 'config':
        environment.setForce(isForced())
        environment.config(secondary)

def handleArgs(args): # pylint: disable=too-many-branches,too-many-statements
    """Takes the command-line args and parses them into something meaningful

    arg - The arguments
    """
    global DEPENDENT # pylint: disable=global-statement
    global MASTER # pylint: disable=global-statement
    global HOME # pylint: disable=global-statement
    global SFIGNORE # pylint: disable=global-statement
    global TMPDIR # pylint: disable=global-statement
    global MAXPOLL # pylint: disable=global-statement
    global REQUESTID # pylint: disable=global-statement

    if args.debug:
        logger.setLevel(logging.DEBUG)

    if args.verbose:
        logger.setLevel(logging.INFO)

    if args.force:
        setForce(args.force)

    setRelativity(args.absolute)

    if args.test:
        logger.debug('Enabling runAllTests')
        ant.addFlag('sf.runAllTests=true')

    if args.env_dep:
        logger.debug('Setting the DEPENDENT environment to %r' % (args.env_dep,))
        DEPENDENT = args.env_dep
        environment.setDependent(DEPENDENT)
        ant.addFlag('solenopsis.env.DEPENDENT=%r' % (args.env_dep,))
        ant.addFlag('sf.env=%r' % (args.env_dep,))

    if args.env_master:
        logger.debug('Setting the MASTER environment to %r' % (args.env_master,))
        MASTER = args.env_master
        ant.addFlag('solenopsis.env.MASTER=%r' % (args.env_master,))

    if args.env_home:
        logger.debug('Setting the local.HOME environment to %r' % (args.env_home,))
        HOME = args.env_home
        ant.addFlag('solenopsis.env.local.HOME=%r' % (args.env_home,))

    if args.username:
        logger.debug('Setting the solenopsis.USER to %r' % (args.username))
        ant.addFlag('solenopsis.USER=%r' % (args.username))

    if args.password:
        logger.debug('Setting the solenopsis.PASSWORD to %r' % (args.password))
        ant.addFlag('solenopsis.PASSWORD=%r' % (args.password))

    if args.token:
        logger.debug('Setting the solenopsis.TOKEN to %r' % (args.token))
        ant.addFlag('solenopsis.TOKEN=%r' % (args.token))

    if args.env_sfignore:
        logger.debug('Setting the sf.ignoreFile to %r' % (args.env_sfignore,))
        SFIGNORE = args.env_sfignore
        ant.addFlag('sf.ignoreFile=%r' % (args.env_sfignore,))

    if args.env_tmpdir:
        logger.debug('Setting the solenopsis.temp.DIR to %r' % (args.env_tmpdir,))
        TMPDIR = args.env_tmpdir
        ant.addFlag('solenopsis.temp.DIR=%r' % (args.env_tmpdir,))

    if args.env_pkgdir:
        logger.debug('Setting the sf.packageDir to %r' % (args.env_pkgdir,))
        ant.addFlag('sf.packageDir=%r' % (args.env_pkgdir,))

    if args.env_maxpoll:
        logger.debug('Setting the sf.maxPoll to %r' % (args.env_maxpoll,))
        MAXPOLL = args.env_maxpoll
        ant.addFlag('sf.maxPoll=%r' % (args.env_maxpoll,))

    if args.env_requestid:
        logger.debug('Setting the sf.asyncRequestId to %r' % (args.env_requestid,))
        REQUESTID = args.env_requestid
        ant.addFlag('sf.asyncRequestId=%r' % (args.env_requestid,))

    if args.checkonly:
        logger.debug('Setting checkonly')
        ant.addFlag('sf.checkOnly=true')

    if args.logtype:
        logger.debug('Setting sf.logType to %r' % (args.logtype,))
        ant.addFlag('sf.logType=%r' % (args.logtype,))

    if args.solversion:
        logger.debug('Setting solenopsis.VERSION to %r' % (args.solversion,))
        ant.addFlag('solenopsis.VERSION=%r' % (args.solversion,))

    if args.apiversion:
        logger.debug('Setting sf.version to %r' % (args.apiversion,))
        ant.addFlag('sf.version=%r' % (args.apiversion,))

    if args.filecontains:
        logger.debug('Setting sf.filesContain to %r' % (args.filecontains,))
        ant.addFlag('sf.filesContain=%r' % (args.filecontains,))

    if args.gitshell:
        logger.debug('Setting solenopsis.git-status.shell=true')
        ant.addFlag('solenopsis.git-status.shell=true')

    if args.batchsize:
        logger.debug('Setting sf.batchSize to %r' % (args.batchsize,))
        ant.addFlag('sf.batchSize=%r' % (args.batchsize,))

    if args.packagefile:
        logger.debug('Setting sf.packageFile to %r' % (args.packagefile,))
        ant.addFlag('sf.packageFile=%r' % (args.packagefile,))

    if args.types:
        logger.debug('Setting sf.types to %r' % (args.types,))
        ant.addFlag('sf.types=%r' % (args.types,))

    if args.dryrun:
        logger.debug('Setting dry run')
        ant.addFlag('sf.dryRun=true')

    if args.fast:
        logger.debug('Setting fast deploy')
        ant.addFlag('sf.fastDeploy=true')

    if args.antfile:
        logger.debug('Setting the sf.antFile to %r' % (args.antfile,))
        ant.addFlag('sf.antFile=%r' % (args.antfile,))

    if args.dumpfiles:
        logger.debug('Setting dump files')
        ant.addFlag('sf.dumpFiles=true')

        if args.showpasswords:
            logger.debug('Showing passwords in the dump files')
            ant.addFlag('sf.showPasswords=true')

    if args.xsldir:
        logger.debug('Setting the sf.xslDir to %r' % (args.xsldir,))
        ant.addFlag('sf.xslDir=%r' % (args.xsldir))

    if args.properties:
        logger.debug('Setting the solenopsis.PROPERTIES to %r' % (args.properties,))
        ant.addFlag('solenopsis.PROPERTIES=%r' % (args.properties))
        environment.setDefaultConfig(args.properties)

    if args.testlevel:
        logger.debug('Setting the sf.testLevel to %r' % (args.testlevel,))
        ant.addFlag('sf.testLevel=%r' % (args.testlevel))

    if args.destructiveChangesFile:
        logger.debug('Setting the sf.destructiveChangesFile to %r' % (args.destructiveChangesFile,))
        ant.addFlag('sf.destructiveChangesFile=%r' % (args.destructiveChangesFile))

    if args.src:
        setSourceDir(args.src)

    handlePriCommand(args.pricommand[0], args.seccommand)

if __name__ == "__main__":
    setup()

    parser = argparse.ArgumentParser(description='A set of commands to do Salesforce related work')

    # True/False flags
    parser.add_argument('-a', action='store_true',
        help='Absolute path should be used', dest='absolute')
    parser.add_argument('-r', action='store_false',
        help='Relative path should be used', dest='absolute')
    parser.add_argument('-d', action='store_true',
        help='Make the output very verbose', dest='debug')
    parser.add_argument('-v', action='store_true', help='Make the output verbose', dest='verbose')
    parser.add_argument('-f', action='store_true', help='Force overwrite', dest='force')
    parser.add_argument('-t', action='store_true', help='Run all tests', dest='test')
    parser.add_argument('--gitshell', action='store_true',
        help='Shell out for git instead of using jGit', dest='gitshell')

    # Parse the environment information
    parser.add_argument('-e', '--env', action='store',
                        help='Then dependent environment', dest='env_dep',
                        metavar='DEPENDENT')
    parser.add_argument('-m', '--master', action='store',
                        help='Then master environment', dest='env_master',
                        metavar='MASTER')
    parser.add_argument('-s', '--src', action='store',
                        help='The src path', dest='src',
                        metavar='PATH')
    parser.add_argument('-l', '--home', action='store',
                        help='The local.home path', dest='env_home',
                        metavar='HOME')
    parser.add_argument('-i', '--ignorefile', action='store',
                        help='The sfdcignore file', dest='env_sfignore',
                        metavar='SFIGNORE')
    parser.add_argument('--tmpdir', action='store',
                        help='The temporary directory', dest='env_tmpdir',
                        metavar='TMPDIR')
    parser.add_argument('--pkgdir', action='store',
                        help='The package directory', dest='env_pkgdir',
                        metavar='PKGDIR')
    parser.add_argument('--maxpoll', action='store',
                        help='The maximum amount of time to poll SFDC', dest='env_maxpoll',
                        metavar='MAXPOLL')
    parser.add_argument('--requestid', action='store',
                        help='The ongoing request id to repoll for', dest='env_requestid',
                        metavar='REQUESTID')
    parser.add_argument('--checkonly', action='store_true',
                        help='Will try to validate a deploy, and not do the deploy',
                        dest='checkonly')
    parser.add_argument('--logtype', action='store',
                        help='The log type to use', dest='logtype', choices=LOG_TYPES,
                        metavar='LOGTYPE')
    parser.add_argument('--solversion', action='store',
                        help='The version of solenopsis to run',
                        dest='solversion', choices=SOL_VERSIONS,
                        metavar='SOLVERSION')
    parser.add_argument('--apiversion', action='store',
                        help='The version of api to use', dest='apiversion', choices=API_VERSIONS,
                        metavar='APIVERSION')
    parser.add_argument('--filecontains', action='store',
                        help='Check the files to see if they contain the string and push them',
                        dest='filecontains')
    parser.add_argument('--batchsize', action='store',
                        help='The number of items to retrieve for multipart retrieves')
    parser.add_argument('--types', action='store', choices=METADATA_TYPES,
                        help='The metadata types to retrieve')
    parser.add_argument('--packagefile', action='store',
                        help='Fully qualified path to package.xml')
    parser.add_argument('--dryrun', action='store_true', dest='dryrun',
                        help='Do a dry run and do not actually preform the action')
    parser.add_argument('--fast', action='store_true', dest='fast',
                        help='Deploy using the Quick Deploy feature')
    parser.add_argument('--antfile', action='store', dest='antfile',
                        help='The location to the ant-salesforce jar')
    parser.add_argument('--dump-files', action='store_true', dest='dumpfiles',
                        help='If the build.xml should be dumped to disk')
    parser.add_argument('--show-passwords', action='store_true', dest='showpasswords',
                        help='Used with --dump-files to show cleartext passwords in build.xml')
    parser.add_argument('--xsldir', action='store', dest='xsldir',
                        help='Specifies where the XSLs are stored to be applied')
    parser.add_argument('--properties', action='store', dest='properties',
                        help='Specifies which properties file to use')
    parser.add_argument('--testlevel', action='store',
                        help='Specifies which tests to run', dest='testlevel', choices=TEST_LEVELS)
    parser.add_argument('--username', action='store',
                        help='The username', dest='username')
    parser.add_argument('--password', action='store',
                        help='The password', dest='password')
    parser.add_argument('--token', action='store',
                        help='The token', dest='token')
    parser.add_argument('--destructiveChangesFile', action='store',
                        help='What destructive changes file to use', dest='destructiveChangesFile')

    # Handle primary command
    parser.add_argument('pricommand', metavar='COMMAND', nargs=1,
                        help='The command to run', choices=PRIMARY_COMMANDS)

    parser.add_argument('seccommand', metavar='SUBCOMMAND', nargs='*',
                        help='The rest of the commands')

    handleArgs(parser.parse_args())
