#!python

import os
import sys
import argparse
import time

from glob import glob
from PIL import Image

from android_asset_resizer import __version__
from android_asset_resizer.resizer import AssetResizer

SOURCE_DENSITY_CHOICES = ('xxxhdpi', 'xxhdpi', 'xhdpi',)
PIL_FILTER_CHOICES = ('NEAREST', 'BILINEAR', 'BICUBIC', 'ANTIALIAS')
PIL_FILTER_MAP = {
    'NEAREST': Image.NEAREST,
    'BILINEAR': Image.BILINEAR,
    'BICUBIC': Image.BICUBIC,
    'ANTIALIAS': Image.ANTIALIAS
}
PIL_VERIFIED_IMAGE_MODES = ('RGB', 'RGBA')

DEFAULT_DENSITY = 'xhdpi'
DEFAULT_FILTER = 'ANTIALIAS'
DEFAULT_QUALITY = 85
DEFAULT_OUT_PATH = os.getcwd()
DEFAULT_OUT_MODE = 0755
NINE_PATCH_EXT = '.9.png'

# Legacy setting if multiprocessing is not available
legacy = False

# Quiet setting for this session
quiet = False

# Number of images processed in this session
count = 0

# Images to be excluded from processing for this session
exclude = []

try:
    import multiprocessing
except ImportError:
    legacy = True


def uprint(msg, newline=True, quiet=False):
    """
    Unbuffered print.
    """
    if not quiet:
        sys.stdout.write("%s%s" % (msg, "\n" if newline else ''))
        sys.stdout.flush()


def onComplete(filename):
    """
    Callback invoked when a thread is finished processing an image.
    """
    if not filename:
        return  # skipped due to an error

    # Increment the number of images that were processed
    global count
    count += 1


def doWork(resizer, path):
    """
    Process an image using the given asset resizer.
    """
    _, filename = os.path.split(path)

    if filename in exclude:
        return  # skip to the next file

    if filename.endswith(NINE_PATCH_EXT):
        uprint("Skipping: %s!\n"
               "  Nine-patch resizing not supported." % filename, quiet=quiet)
        return  # skip to the next file

    try:
        im = Image.open(path)

        # Warn on unusual Image modes
        if im.mode not in PIL_VERIFIED_IMAGE_MODES:
            msg = ("\n  Error: This file uses the '{mode}' Image mode."
                   "\n         Resizing this image will cause unpredictable results."
                   "\n         This is probably not what you want."
                   "\n         Try again after exporting your image using the RGB or RGBA mode.").format(mode=im.mode)
            uprint("Warning: %s%s" % (filename, msg), quiet=quiet)

        # Resize the image
        resizer.resize_image(path, im)

        # Indicate the file was finished processing
        uprint('Processed: %s' % filename, quiet=quiet)

        # The file was successfully resized
        return filename
    except IOError as e:
        if e.message:
            uprint("Failed: %s\n  Error: %s" % (filename, e.message))
        else:
            uprint("Failed: %s\n  Error %s: %s" % (filename, e.errno, e.strerror))


def main():
    # Start the clock
    start = time.time()

    # Create our parser
    parser = argparse.ArgumentParser(prog='Android Asset Resizer',
            description='Resize image assets for an Android project')

    # Set up our command-line arguments
    parser.add_argument('-d', '--density', default=DEFAULT_DENSITY, choices=SOURCE_DENSITY_CHOICES,
            help='the density of the source images')
    parser.add_argument('-p', '--prefix',
            help='a prefix to add to the filename')
    parser.add_argument('-f', '--filter', default=DEFAULT_FILTER, choices=PIL_FILTER_CHOICES,
            help='the image filter to use')
    parser.add_argument('-q', '--quality', type=int, default=DEFAULT_QUALITY,
            help='the image quality to use when resizing jpeg images')
    parser.add_argument('-o', '--out',
            help='the path to the output directory')
    parser.add_argument('-l', '--ldpi', action='store_true',
            help='generate ldpi assets')
    parser.add_argument('-x', '--xxxhdpi', action='store_true',
            help='generate xxxhdpi assets')
    parser.add_argument('-e', '--exclude',
            help='comma-separated list of filenames to be excluded from resizing')
    parser.add_argument('--legacy', action='store_true',
            help='disable experimental multiprocessing support and process images serially')
    parser.add_argument('--quiet', action='store_true',
            help='suppress all output except for errors')
    parser.add_argument('--version', action='version',
            version='%(prog)s {v}'.format(v=__version__))
    parser.add_argument('pattern', nargs='+',
            help='the path to the source images (supports wildcards)')

    # Get our arguments
    args = parser.parse_args()

    # Set the global legacy setting
    global legacy
    legacy = legacy or args.legacy

    # Set the global quiet setting
    global quiet
    quiet = args.quiet

    # Set our excluded filenames
    if args.exclude:
        global exclude
        exclude = [f.strip() for f in args.exclude.split(',')]

    # Get our output directory
    if args.out:
        out_dir = os.path.abspath(args.out)

        if os.path.isfile(out_dir):
            parser.error('The specified out is not a directory!')
        if not os.access(out_dir, os.W_OK):
            parser.error('The specified out is not writable!')
    else:
        out_dir = DEFAULT_OUT_PATH

    # Create the output directory if necessary
    try:
        os.makedirs(out_dir, DEFAULT_OUT_MODE)
    except OSError as e:
        pass

    # Create our resizer
    resizer = AssetResizer(out_dir)

    # Configure our resizer
    resizer.source_density = args.density
    resizer.ldpi = args.ldpi
    resizer.xxxhdpi = args.xxxhdpi
    resizer.prefix = args.prefix
    resizer.image_filter = PIL_FILTER_MAP.get(args.filter)
    resizer.image_quality = args.quality

    # Make our resource directories
    resizer.mkres()

    # Set up our pool of workers
    if not legacy:
        pool = multiprocessing.Pool(processes=multiprocessing.cpu_count())
    else:
        pool = None
        uprint("Warning: Processing images serially in legacy mode!", quiet=quiet)

    # Resize the assets
    for p in args.pattern:
        for path in glob(p):
            if not legacy:
                result = pool.apply_async(doWork, [resizer, path], callback=onComplete)

                # Forces any exception raised by the remote call to be reraised
                result.get()
            else:
                onComplete(doWork(resizer, path))

    if not legacy:
        # We are not adding any more work to the pool
        pool.close()

        # Wait until all work has finished
        pool.join()

    # Prepare our summary
    if not quiet:
        global count

        # Formatting
        pluralize = ('' if count == 1 else 's')

        # Determine our elapsed time
        elapsed = round((time.time() - start), 3)

        uprint('Resized {count} asset{plural} in {elapsed}s'.format(count=count,
                plural=pluralize, elapsed=elapsed), quiet=quiet)

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