#!/usr/bin/env python
#
# Copyright (c) 2012 Intel, Inc.
# License: GPLv2
# Author: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>

# Note! We use the below docstring for the program help text as well.
"""
Generate block map (AKA bmap) for an image. The idea is that while images files
may generally be very large (e.g., 4GiB), they may nevertheless contain only
little real data, e.g., 512MiB. This data are files, directories, file-system
meta-data, partition table, etc. When flashing the image to the target device,
you do not have to copy all the 4GiB of data, you can copy only 512MiB of it,
which is 4 times less, so flashing shoud presumably be 4 times faster.

The block map file is an XML file which contains a list of blocks which have to
be copied to the target device. The other blocks are not used and there is no
need to copy them. The XML file also contains some additional information like
block size, image size, count of mapped blocks, etc. There are also many
commentaries, so it is human-readable.

The image file has to be a sparse file. Generally, this often means that when
you generate this image file, you should start with a huge sparse file which
contains a single hole spanning the entire file. Then you should partition it,
write all the data (probably by means of loop-back mounting the image file or
parts of it), etc. The end result should be a sparse file where holes represent
the areas which do not have to be flashed. On the other hand, the mapped file
areas represent the areas which have to be flashed. The block map file lists
these areas.
"""

VERSION = "0.1.0"

import argparse
import sys
import logging
from bmaptools import BmapCreator

def parse_arguments():
    """ A helper function which parses the input arguments. """

    parser = argparse.ArgumentParser(description = __doc__,
                                     prog = 'bmap-creator')

    # Mandatory command-line argument - image file
    text = "the image to generate bmap for"
    parser.add_argument("image", help = text)

    # The --output option
    text = "the output file name (otherwise stdout is used)"
    parser.add_argument("-o", "--output", help = text)

    # The --quiet option
    text = "be quiet"
    parser.add_argument("-q", "--quiet", action="store_true", help = text)

    # The --no-checksum option
    text = "do not generate the checksum for block ranges in the bmap"
    parser.add_argument("--no-checksum", action="store_true", help = text)

    # The --version option
    parser.add_argument("--version", action="version", \
                        version="%(prog)s " + "%s" % VERSION)

    return parser.parse_args()


def setup_logger(loglevel):
    """ A helper function which sets up and configures the logger. The log
        level is initialized to 'loglevel'. Returns the logger object. """

    # Change log level names to something less nicer than the default
    # all-capital 'INFO' etc.
    logging.addLevelName(logging.ERROR, "error!")
    logging.addLevelName(logging.WARNING, "warning!")
    logging.addLevelName(logging.DEBUG, "debug")
    logging.addLevelName(logging.INFO, "info")

    log = logging.getLogger('bmap-creator-logger')
    log.setLevel(loglevel)
    formatter = logging.Formatter("bmap-creator: %(levelname)s: %(message)s")
    where = logging.StreamHandler()
    where.setFormatter(formatter)
    log.addHandler(where)

    return log

def setup_output_stream(file_path):
    """ Create, initialize and return a logger object for the output stream
        (where we'll write the bmap). The stream is re-directed to 'file_path'
        or to stdout if 'file_path' is None. """

    output = logging.getLogger('bmap-creator-output')
    output.setLevel(logging.INFO)
    if file_path:
        where = logging.FileHandler(file_path)
    else:
        where = logging.StreamHandler(sys.stdout)

    output.addHandler(where)

    return output

def main():
    """ Script entry point. """

    args = parse_arguments()

    if args.quiet:
        log = setup_logger(logging.ERROR)
    else:
        log = setup_logger(logging.INFO)

    if args.output:
        # Make sure the output file is accessible
        try:
            open(args.output, "w").close()
        except IOError as err:
            log.error("cannot open the output file '%s': %s" \
                      % (args.output, err))
            raise SystemExit(1)

    output = setup_output_stream(args.output)

    try:
        creator = BmapCreator.BmapCreator(args.image, output)
        creator.generate(not args.no_checksum)
    except BmapCreator.Error as err:
        log.error(str(err))
        raise SystemExit(1)

    if creator.bmap_mapped_cnt == creator.bmap_blocks_cnt:
        log.warning("all %s are mapped, no holes in '%s'" \
                    % (creator.bmap_image_size_human, args.image))
        log.warning("was the image handled incorrectly and holes " \
                    "were expanded?")

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