#!/usr/bin/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
}

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

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

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:
        resizer.resize(path)

        # 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:
                pool.apply_async(doWork, [resizer, path], callback=onComplete)
            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()
