"""Calxeda: ubootenv.py """

# Copyright (c) 2012-2013, Calxeda Inc.
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of Calxeda Inc. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.


import struct

from cxmanage_api.simg import has_simg, get_simg_contents
from cxmanage_api.crc32 import get_crc32
from cxmanage_api.cx_exceptions import UbootenvError


ENVIRONMENT_SIZE = 8192
UBOOTENV_V1_VARIABLES = ["bootcmd_default", "bootcmd_sata", "bootcmd_pxe",
        "bootdevice"]
UBOOTENV_V2_VARIABLES = ["bootcmd0", "init_scsi", "bootcmd_scsi", "init_pxe",
        "bootcmd_pxe", "devnum"]


class UbootEnv(object):
    """Represents a U-Boot Environment.

    >>> from cxmanage_api.ubootenv import UbootEnv
    >>> uboot = UbootEnv()

    :param contents: UBootEnvironment contnents.
    :type contents: string

    """

    def __init__(self, contents=None):
        """Default constructor for the UbootEnv class."""
        self.variables = {}

        if (contents != None):
            if (has_simg(contents)):
                contents = get_simg_contents(contents)

            contents = contents.rstrip("%c%c" % (chr(0), chr(255)))[4:]
            lines = contents.split(chr(0))
            for line in lines:
                part = line.partition("=")
                self.variables[part[0]] = part[2]

    # pylint: disable=R0912
    def set_boot_order(self, boot_args):
        """Sets the boot order specified in the uboot environment.

        >>> uboot.set_boot_order(boot_args=['disk', 'pxe'])

        .. note::
            * Valid Args:
                             pxe     - boot from pxe server\n
                             disk    - boot from default sata device\n
                             diskX   - boot from sata device X\n
                             diskX:Y - boot from sata device X, partition Y\n
                             retry   - retry last boot device indefinitely\n
                             reset   - reset A9\n

        :param boot_args: Boot args (boot order). A list of strings.
        :type boot_args: list

        :raises ValueError: If an invalid boot device is specified.
        :raises ValueError: If 'retry' and 'reset' args are used together.
        :raises UbootenvError: If the u-boot environment is unrecognized

        """
        validate_boot_args(boot_args)
        if boot_args == self.get_boot_order():
            return

        commands = []
        retry = False
        reset = False

        if all(x in self.variables for x in UBOOTENV_V1_VARIABLES):
            version = 1
        elif all(x in self.variables for x in UBOOTENV_V2_VARIABLES):
            version = 2
        else:
            raise UbootenvError("Unrecognized u-boot environment")

        for arg in boot_args:
            if arg == "retry":
                retry = True
            elif arg == "reset":
                reset = True
            elif version == 1:
                if arg == "pxe":
                    commands.append("run bootcmd_pxe")
                elif arg == "disk":
                    commands.append("run bootcmd_sata")
                elif arg.startswith("disk"):
                    try:
                        # pylint: disable=W0141
                        dev, part = map(int, arg[4:].split(":"))
                        bootdevice = "%i:%i" % (dev, part)
                    except ValueError:
                        bootdevice = str(int(arg[4:]))
                    commands.append("setenv bootdevice %s && run bootcmd_sata"
                            % bootdevice)
            elif version == 2:
                if arg == "pxe":
                    commands.append("run init_pxe && run bootcmd_pxe")
                elif arg == "disk":
                    commands.append("run init_scsi && run bootcmd_scsi")
                elif arg.startswith("disk"):
                    try: