# Copyright (C) 2013 Red Hat, Inc.
#
# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
#

import collections
import os
import re
import shlex

import virtinst
from virtinst import log
from virtinst import xmlutil

from .formats import parser_class


class _VMXLine(object):
    """
    Class tracking an individual line in a VMX/VMDK file
    """
    def __init__(self, content):
        self.content = content

        self.pair = None
        self.is_blank = False
        self.is_comment = False
        self.is_disk = False
        self._parse()

    def _parse(self):
        line = self.content.strip()
        if not line:
            self.is_blank = True
        elif line.startswith("#"):
            self.is_comment = True
        elif line.startswith("RW ") or line.startswith("RDONLY "):
            self.is_disk = True
        else:
            # Expected that this will raise an error for unknown format
            before_eq, after_eq = line.split("=", 1)
            key = before_eq.strip().lower()
            value = after_eq.strip().strip('"')
            self.pair = (key, value)

    def parse_disk_path(self):
        # format:
        # RW 16777216 VMFS "test-flat.vmdk"
        # RDONLY 156296322 V2I "virtual-pc-diskformat.v2i"
        content = self.content.split(" ", 3)[3]
        if not content.startswith("\""):
            raise ValueError("Path was not fourth entry in VMDK storage line")
        return shlex.split(content, " ", 1)[0]


class _VMXFile(object):
    """
    Class tracking a parsed VMX/VMDK format file
    """
    def __init__(self, content):
        self.content = content
        self.lines = []

        self._parse()

    def _parse(self):
        for line in self.content:
            try:
                lineobj = _VMXLine(line)
                self.lines.append(lineobj)
            except Exception as e:
                raise Exception(_("Syntax error at line %d: %s\n%s") %
                    (len(self.lines) + 1, line.strip(), e))

    def pairs(self):
        ret = collections.OrderedDict()
        for line in self.lines:
            if line.pair:
                ret[line.pair[0]] = line.pair[1]
        return ret


def parse_vmdk(filename):
    """
    Parse a VMDK descriptor file
    Reference: http://sanbarrow.com/vmdk-basics.html
    """
    # Detect if passed file is a descriptor file
    # Assume descriptor isn't larger than 10K
    if not os.path.exists(filename):
        log.debug("VMDK file '%s' doesn't exist", filename)
        return
    if os.path.getsize(filename) > (10 * 1024):
        log.debug("VMDK file '%s' too big to be a descriptor", filename)
        return

    f = open(filename, "r")
    content = f.readlines()
    f.close()

    try:
        vmdkfile = _VMXFile(content)
    except Exception:
        log.exception("%s looked like a vmdk file, but parsing failed",
                          filename)
        return

    disklines = [l for l in vmdkfile.lines if l.is_disk]
    if len(disklines) == 0:
        raise RuntimeError(_("Didn't detect a storage line in the VMDK "
                             "descriptor file"))
    if len(disklines) > 1:
        raise RuntimeError(_("Don't know how to handle multistorage VMDK "
                             "descriptors"))

    return disklines[0].parse_disk_path()


def parse_netdev_entry(conn, ifaces, fullkey, value):
    """
    Parse a particular key/value for a network.  Throws ValueError.
    """
    ignore, ignore, inst, key = re.split("^(ethernet)([0-9]+).", fullkey)
    lvalue = value.lower()

    if key == "present" and lvalue == "false":
        return

    net = None
    for checkiface in ifaces:
        if getattr(checkiface, "vmx_inst") == inst:
            net = checkiface
            break
    if not net:
        net = virtinst.DeviceInterface(conn)
        setattr(net, "vmx_inst", inst)
        net.set_default_source()
        ifaces.append(net)

    if key == "virtualdev":
        # "vlance", "vmxnet", "e1000"
        if lvalue in ["e1000"]:
            net.model = lvalue
    if key == "addresstype" and lvalue == "generated":
        # Autogenerate a MAC address, the default
        pass
    if key == "address":
        # we ignore .generatedAddress for auto mode
        net.macaddr = lvalue
    return net, inst


def parse_disk_entry(conn, disks, fullkey, value, topdir):
    """
    Parse a particular key/value for a disk.
    """
    # skip bus values, e.g. 'scsi0.present = "TRUE"'
    if re.match(r"^(scsi|ide)[0-9]+[^:]", fullkey):
        return

    ignore, bus, bus_nr, inst, key = re.split(
        r"^(scsi|ide)([0-9]+):([0-9]+)\.", fullkey)

    lvalue = value.lower()

    if key == "present" and lvalue == "false":
        return

    # Does anyone else think it's scary that we're still doing things
    # like this?
    if bus == "ide":
        inst = int(bus_nr) * 2 + (int(inst) % 2)
    elif bus == "scsi":
        inst = int(bus_nr) * 16 + (int(inst) % 16)

    disk = None
    for checkdisk in disks:
        if checkdisk.bus == bus and getattr(checkdisk, "vmx_inst") == inst:
            disk = checkdisk
            break
    if not disk:
        disk = virtinst.DeviceDisk(conn)
        disk.bus = bus
        setattr(disk, "vmx_inst", inst)
        disks.append(disk)

    if key == "devicetype":
        if (lvalue == "atapi-cdrom" or
            lvalue == "cdrom-raw" or
            lvalue == "cdrom-image"):
            disk.device = "cdrom"

    if key == "filename":
        disk.path = value
        fmt = "raw"
        if lvalue.endswith(".vmdk"):
            fmt = "vmdk"
            # See if the filename is actually a VMDK descriptor file
            newpath = parse_vmdk(os.path.join(topdir, disk.path))
            if newpath:
                log.debug("VMDK file parsed path %s->%s",
                    disk.path, newpath)
                disk.path = newpath

        disk.driver_type = fmt


class vmx_parser(parser_class):
    """
    Support for VMWare .vmx files.  Note that documentation is
    particularly sparse on this format, with pretty much the best
    resource being http://sanbarrow.com/vmx.html
    """
    name = "vmx"
    suffix = ".vmx"

    @staticmethod
    def identify_file(input_file):
        """
        Return True if the given file is of this format.
        """
        if os.path.getsize(input_file) > (1024 * 1024 * 2):
            return

        infile = open(input_file, "r")
        content = infile.readlines()
        infile.close()

        for line in content:
            # some .vmx files don't bother with the header
            if (re.match(r'^config.version\s+=', line) or
                re.match(r'^#!\s*/usr/bin/vm(ware|player)', line)):
                return True
        return False

    @staticmethod
    def export_libvirt(conn, input_file):
        topdir = os.path.dirname(os.path.abspath(input_file))
        infile = open(input_file, "r")
        contents = infile.readlines()
        infile.close()
        log.debug("Importing VMX file:\n%s", "".join(contents))

        vmxfile = _VMXFile(contents)
        config = vmxfile.pairs()

        if not config.get("displayname"):
            raise ValueError(_("No displayName defined in '%s'") %
                             input_file)

        name = config.get("displayname")
        mem = config.get("memsize")
        desc = config.get("annotation")
        vcpus = config.get("numvcpus")

        def _find_keys(prefixes):
            ret = []
            for key, value in config.items():
                for p in xmlutil.listify(prefixes):
                    if key.startswith(p):
                        ret.append((key, value))
                        break
            return ret

        disks = []
        for key, value in _find_keys(["scsi", "ide"]):
            parse_disk_entry(conn, disks, key, value, topdir)

        ifaces = []
        for key, value in _find_keys("ethernet"):
            parse_netdev_entry(conn, ifaces, key, value)

        for disk in disks:
            if disk.device == "disk":
                continue

            # vmx files often have dross left in path for CD entries
            if (disk.path is None or
                disk.path.lower() == "auto detect" or
                not os.path.exists(disk.path)):
                disk.path = None

        guest = virtinst.Guest(conn)
        guest.name = name.replace(" ", "_")
        guest.description = desc or None
        if vcpus:
            guest.vcpus = int(vcpus)
        if mem:
            guest.currentMemory = int(mem) * 1024

        for dev in ifaces + disks:
            guest.add_device(dev)

        return guest
