#!/usr/bin/env python

# Copyright (C) 2012-2013 Science and Technology Facilities Council.
#
# This program 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, see <http://www.gnu.org/licenses/>.

from codecs import latin_1_decode
import sys
import subprocess
from optparse import OptionParser

from crab import CrabError, CrabStatus
from crab.client import CrabClient
from crab.util.compat import subprocess_options
from crab.util.guesstimezone import guess_timezone

def send_crontab(crontab):
    """Sends the given crontab to the server.

    The crontab should be given as a unicode string.
    This function also attempts to determine the system timezone
    to send along with the crontab.  This might not always be the
    timezone cron uses, for example if configured in /etc/default/cron
    so if in doubt, the environment variable TZ should be set, or
    the crontab should include a CRON_TZ variable."""

    tz = guess_timezone()

    client = CrabClient()
    return client.send_crontab(crontab, timezone=tz)

def read_crontab():
    """Fetches the current user's crontab file.

    The output from ``crontab -l`` is decoded to give a unicode
    string.  Currently ISO-8859-1 encoding is assumed."""

    try:
        p = subprocess.Popen(['crontab', '-l'],
                             shell=False, stdout=subprocess.PIPE,
                             **subprocess_options)
        (stdoutdata, stderrdata) = p.communicate()

        if p.returncode == 0:
            return latin_1_decode(stdoutdata, 'replace')[0]

        else:
            print(sys.argv[0] + ': failed to read crontab')

    #except OSError as err:
    except OSError:
        print(sys.argv[0] + ': could not execute crontab command')

def read_file(path, silent=False):
    """Returns the contents of the given file.

    The file is opened in text-mode read, so it should return
    a unicode string.

    If the "silent" option is specified, a warning will not
    be printed on failure.  (Useful for commands which we
    expect to be running in a cron job.)"""

    if path == '-':
        return sys.stdin.read()

    try:
        fh = None

        try:
            fh = open(path, 'r')
            return fh.read()

        except IOError:
            if not silent:
                err = sys.exc_info()[1]
                print(sys.argv[0] + ': cannot read file: ' + str(err))

    finally:
        if fh is not None:
            fh.close()

def do_import(crontabfile=None):
    """Action for the "import" command.

    Either reads the given file or fetches the user's crontab.  Then sends
    it to the Crab server."""

    if crontabfile is None:
        crontab = read_crontab()
    else:
        crontab = read_file(crontabfile)

    if crontab is None:
        return 1

    try:
        warning = send_crontab(crontab)

        if warning:
            print(sys.argv[0] + ': crontab import warnings:')
            for message in warning:
                print(message)

            return 1

    #except CrabError as err:
    except CrabError:
        err = sys.exc_info()[1]
        print(sys.argv[0] + ': failed to send crontab: ' + str(err))
        return 1

    return 0

def main():
    parser = OptionParser(usage = """Usage: %prog [options] [command]

Commands:
  start, finish          - report cron job progress
  fail, couldnotstart    - report problems with cron job
  warning, unknown       - report warning on cron job completion
  alreadyrunning         - long running job need not start
  import                 - send crontab to server
  export                 - display cron jobs from server
  edit                   - edit the crontab, and then import it
  info                   - print current configuration""")

    parser.add_option('-c',
        type='string', dest='command',
        help='specify the cron COMMAND being reported', metavar='COMMAND')
    parser.add_option('--id',
        type='string', dest='crabid',
        help='specify the Crab job ID to report', metavar='ID')
    parser.add_option('--file',
        type='string', dest='crontabfile',
        help='specify file name containing crontab to import')
    parser.add_option('--raw',
        action="store_true", dest="raw", default=False,
        help="fetch crontab from server exactly as last imported")
    parser.add_option('--stdout',
        type="string", dest='stdoutfile',
        help='file containg standard output to send')
    parser.add_option('--stderr',
        type="string", dest='stderrfile',
        help='file containg standard error to send')


    (options, args) = parser.parse_args()

    status_commands = {
                       'finish': CrabStatus.SUCCESS,
                       'fail': CrabStatus.FAIL,
                       'unknown': CrabStatus.UNKNOWN,
                       'couldnotstart': CrabStatus.COULDNOTSTART,
                       'alreadyrunning': CrabStatus.ALREADYRUNNING,
                       'warning': CrabStatus.WARNING,
                      }

    if len(args) == 0:
        parser.error('no command specified')

    elif args[0] == 'start' or args[0] in status_commands:
        if options.command is None:
            parser.error('no command specified')
        else:
            try:
                client = CrabClient(options.command, crabid=options.crabid)

                if args[0] == 'start':
                    client.start()
                else:
                    status = status_commands[args[0]]
                    stdoutdata = ''
                    stderrdata = ''

                    if options.stdoutfile is not None:
                        stdoutdata = read_file(options.stdoutfile, silent=True)
                        if stdoutdata is None:
                            stdoutdata = 'Failed to read from file: ' + \
                                         options.stdoutfile
                            if CrabStatus.is_ok(status):
                                status = CrabStatus.WARNING

                    if options.stderrfile is not None:
                        stderrdata = read_file(options.stderrfile, silent=True)
                        if stderrdata is None:
                            stderrdata = 'Failed to read from file: ' + \
                                         options.stderrfile
                            if CrabStatus.is_ok(status):
                                status = CrabStatus.WARNING


                    client.finish(status,
                                  stdoutdata=stdoutdata, stderrdata=stderrdata)

            #except CrabError as err:
            except CrabError:
                err = sys.exc_info()[1]
                print(sys.argv[0] + ': ' + str(err))
                return 1

    elif args[0] == 'import':
        return do_import(options.crontabfile)

    elif args[0] == 'edit':
        status = subprocess.call(['crontab', '-e'],
                                 shell=False, **subprocess_options)
        if status != 0:
            print(sys.argv[0] + ': failed to edit crontab')
            return 1

        print(sys.argv[0] + ': importing crontab')
        return do_import()

    elif args[0] == 'export':
        try:
            client = CrabClient()
            crontab = client.fetch_crontab(raw=options.raw)
            print(crontab)

        #except CrabError as err:
        except CrabError:
            err = sys.exc_info()[1]
            print(sys.argv[0] + ': failed to fetch crontab: ' + str(err))
            return 1

    elif args[0] == 'help':
        parser.print_help()

    elif args[0] == 'info':
        client = CrabClient()
        print(client.get_info())

    else:
        parser.error('command not recognised')

    return 0

if __name__ == '__main__':
    sys.exit(main())
