#!/usr/bin/python 
#
# Copyright 2008  Red Hat, Inc.
# Joey Boggs <jboggs@redhat.com>
#
# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
# 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 2 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.

import sys
import os
import logging
import errno
from optparse import OptionParser

import virtinst.cli as cli
import virtinst.util as util
import virtconv
import virtconv.formats as formats
import virtconv.vmcfg as vmcfg
import virtconv.diskcfg as diskcfg

def parse_args():
    """Parse and verify command line."""
    opts = OptionParser()
    opts.set_usage("%prog [options] inputdir|input.vmx "
        "[outputdir|output.xml]")
    opts.add_option("-q", "--quiet", action="store_true", dest="quiet",
                    help=("Don't be verbose"))
    opts.add_option("-a", "--arch", type="string", dest="arch",
                    default=util.get_default_arch(),
                    help=("Machine Architecture Type (i686/x86_64/ppc)"))
    opts.add_option("-t", "--type", type="string", dest="type",
                    help=("Output virtualization type (hvm, paravirt"))
    opts.add_option("-d", "--debug", action="store_true", dest="debug",
                    help=("Print debugging information"))
    opts.add_option("-i", "--input-format", action="store",
                    dest="input_format", help=("Input format, e.g. 'vmx'"))
    opts.add_option("-o", "--output-format", action="store",
                    dest="output_format", default="virt-image",
                    help=("Output format, e.g. 'virt-image'"))
    opts.add_option("-D", "--disk-format", action="store",
                    dest="disk_format", help=("Output disk format"))
    opts.add_option("-v", "--hvm", action="store_true", dest="fullvirt",
                    help=("This guest should be a fully virtualized guest"))
    opts.add_option("-p", "--paravirt", action="store_true", dest="paravirt",
                    help=("This guest should be a paravirtualized guest"))
    opts.add_option("", "--os-type", type="string", dest="os_type",
                      action="callback", callback=cli.check_before_store,
                      help=("The OS type for fully virtualized guests, e.g. 'linux', 'unix', 'windows'"))
    opts.add_option("", "--os-variant", type="string", dest="os_variant",
                      action="callback", callback=cli.check_before_store,
                      help=("The OS variant for fully virtualized guests, e.g. 'fedora6', 'rhel5', 'solaris10', 'win2k', 'vista'"))
    opts.add_option("", "--noapic", action="store_true", dest="noapic",
        help=("Disables APIC for fully virtualized guest (overrides value in os-type/os-variant db)"), default=False)
    opts.add_option("", "--noacpi", action="store_true", dest="noacpi",
        help=("Disables ACPI for fully virtualized guest (overrides value in os-type/os-variant db)"), default=False)

    (options, args) = opts.parse_args()
    if len(args) < 1:
        opts.error(("You need to provide an input VM definition"))
    if len(args) > 2:
        opts.error(("Too many arguments provided"))
    
    if (options.disk_format and
        options.disk_format not in diskcfg.disk_formats()):
        opts.error("Unknown output disk format \"%s\"" % options.disk_format)

    if len(args) == 1:
        options.output_file = None
        options.output_dir = None
        if os.path.isdir(args[0]):
            options.output_dir = args[0]
    elif os.path.isdir(args[1]) or args[1].endswith("/"):
        options.output_file = None
        options.output_dir = args[1]
    else:
        options.output_file = args[1]
        options.output_dir = os.path.dirname(os.path.realpath(args[1]))

    if options.output_format not in formats.formats():
        opts.error(("Unknown output format \"%s\"" % options.output_format))
    if options.output_format not in formats.output_formats():
        opts.error(("No output handler for format \"%s\""
            % options.output_format))

    if not os.access(args[0], os.R_OK):
        opts.error("Couldn't access input argument \"%s\"\n" % args[0])
        sys.exit(1)

    if not options.input_format:
        try:
            (args[0], options.input_format) = formats.find_input(args[0])
        except StandardError, e:
            opts.error("Couldn't determine input format for \"%s\": %s" % 
                (args[0], e))
            sys.exit(1)

    if options.input_format not in formats.formats():
        opts.error(("Unknown input format \"%s\"" % options.input_format))
    if options.input_format not in formats.input_formats():
        opts.error(("No input handler for format \"%s\""
            % options.input_format))

    if os.path.isdir(args[0]):
        (options.input_file, _) = formats.find_input(args[0],
            options.input_format)
        options.input_dir =  args[0]
    else:
        options.input_file = args[0]
        options.input_dir = os.path.dirname(os.path.realpath(args[0]))

    return options

def verbose(options, msg):
    """Output a message if --quiet is not set."""
    if not options.quiet:
        print msg

def cleanup(msg, options, vmdef, paths):
    """
    After failure, clean up anything we created.
    """
    logging.error(msg)

    try:
        for disk in vmdef.disks.values():
            disk.cleanup()

        paths.reverse()
        for path in paths:
            if os.path.isdir(path):
                os.rmdir(path)
            elif os.path.isfile(path):
                os.remove(path)
    except OSError, e:
        logging.error("Couldn't clean up output directory \"%s\": %s" %
            (options.output_dir, e.strerror))

    sys.exit(1)

def main():
    options = parse_args()
    cli.setupLogging("virt-convert", options.debug)

    inp = formats.parser_by_name(options.input_format)
    outp = formats.parser_by_name(options.output_format)

    vmdef = None

    try:
        vmdef = inp.import_file(options.input_file)
    except IOError, e:
        logging.error("Couldn't import file \"%s\": %s" %
            (options.input_file, e.strerror))
        sys.exit(1)
    except Exception, e:
        logging.error("Couldn't import file \"%s\": %s" %
            (options.input_file, e))
        sys.exit(1)

    if options.paravirt:
        vmdef.type = vmcfg.VM_TYPE_PV
    else:
        vmdef.type = vmcfg.VM_TYPE_HVM

    vmdef.arch = options.arch
    vmdef.os_type = options.os_type
    vmdef.os_variant = options.os_variant
    vmdef.noapic = options.noapic
    vmdef.noacpi = options.noacpi

    clean = []

    unixname = vmdef.name.replace(" ", "-")

    if not options.output_dir:
        options.output_dir = unixname
    try:
        logging.debug("Creating directory %s" % options.output_dir)
        os.mkdir(options.output_dir)
        clean += [ options.output_dir ]
    except OSError, e:
        if (e.errno != errno.EEXIST):
            logging.error("Could not create directory %s: %s" %
                (options.output_dir, e.strerror))
            sys.exit(1)

    if not options.output_file:
        options.output_file = os.path.join(options.output_dir,
           "%s%s" % (unixname, outp.suffix))

    logging.debug("input_file: %s" % options.input_file)
    logging.debug("input_dir: %s" % options.input_dir)
    logging.debug("output_file: %s" % options.output_file)
    logging.debug("output_dir: %s" % options.input_dir)

    verbose(options, "Generating output in \"%s\" format to %s/" %
        (options.output_format, options.output_dir))


    try:
        for d in vmdef.disks.values():
            format = options.disk_format

            # VMDK disks on Solaris converted to vdisk by default
            if (d.format == diskcfg.DISK_FORMAT_VMDK and
                not format and vmcfg.host() == "SunOS"):
                format = "vdisk"

            if not format:
                format = "raw"

            if d.path and format != "none":
                verbose(options, "Converting disk \"%s\" to type %s..." %
                    (d.path, format))

            d.convert(options.input_dir, options.output_dir, format)

    except OSError, e:
        cleanup("Couldn't convert disks: %s" % e.strerror,
            options, vmdef, clean)
    except RuntimeError, e:
        cleanup("Couldn't convert disks: %s" % e, options, vmdef, clean)

    try:
        clean += [ options.output_file ]
        outp.export_file(vmdef, options.output_file)
    except ValueError, e:
        cleanup("Couldn't export to file \"%s\": %s" %
            (options.output_file, e), options, vmdef, clean)

    verbose(options, "Done.")

    
if __name__ == "__main__":
    try:
        main()
    except SystemExit, e:
        sys.exit(e.code)
    except KeyboardInterrupt, e:
        print >> sys.stderr, "Aborted at user request"
    except Exception, e:
        logging.exception(e)
        sys.exit(1)

