import argparse
import bpy
import os
import sys

# run outside as: blender -b <blender-file> -P <this-script> -- args
# run inside blender with exec(bpy.data.texts['convert.py'].as_string())

# Example:
#  blender -t 8 /tmp/upstream-mouse-touchpad-v2.blend -b \
#  -P yaru-assets-generator.py -- --accent red --output-path=$PWD/rendered

# mouse-touchpad.blend file was originally at:
# https://gitlab.gnome.org/Teams/Design/settings-mockups/-/commits/master/mouse-and-touchpad/src/mouse-touchpad-v2.blend

class ArgumentParserForBlender(argparse.ArgumentParser):
    """
    This class is identical to its superclass, except for the parse_args
    method (see docstring). It resolves the ambiguity generated when calling
    Blender from the CLI with a python script, and both Blender and the script
    have arguments. E.g., the following call will make Blender crash because
    it will try to process the script's -a and -b flags:
    >>> blender --python my_script.py -a 1 -b 2

    To bypass this issue this class uses the fact that Blender will ignore all
    arguments given after a double-dash ('--'). The approach is that all
    arguments before '--' go to Blender, arguments after go to the script.
    The following calls work fine:
    >>> blender --python my_script.py -- -a 1 -b 2
    >>> blender --python my_script.py --
    """

    def _get_argv_after_doubledash(self):
        """
        Given the sys.argv as a list of strings, this method returns the
        sublist right after the '--' element (if present, otherwise returns
        an empty list).
        """
        try:
            idx = sys.argv.index("--")
            return sys.argv[idx+1:] # the list after '--'
        except ValueError as e: # '--' not in the list:
            return []

    # overrides superclass
    def parse_args(self):
        """
        This method is expected to behave identically as in the superclass,
        except that the sys.argv list will be pre-processed using
        _get_argv_after_doubledash before. See the docstring of the class for
        usage examples and details.
        """
        return super().parse_args(args=self._get_argv_after_doubledash())

parser = ArgumentParserForBlender()
parser.add_argument('-o', '--output-path',
        help='Where to save the output .xml files (default: %(default)s)',
        default=os.path.realpath(os.path.join(os.path.curdir, '/renderer')))
parser.add_argument('-a', '--accent',
        help='What accent color to generate')
parser.add_argument('-s', '--asset',
        help='What asset to generate')
parser.add_argument('--list-assets', action='store_true')
parser.add_argument('--list-accents', action='store_true')
ARGS = parser.parse_args()

DEFAULT_REPLACEMENTS = ['3584E4', '98C1F1', '323232']

ACCENTS = {
    'default': [ 'E95420', 'E99A7F', '3D3D3D' ],
    'bark': [ '787859', 'BBBB90', '3D3D3D' ],
    'sage': [ '657B69', 'A0C2A6', '3D3D3D' ],
    'olive': [ '4B8501', '6BAC18', '3D3D3D' ],
    'viridian': [ '03875B', '4E9A81', '3D3D3D' ],
    'prussiangreen': [ '308280', '5CB698', '3D3D3D' ],
    'blue': [ '0073E5', '71ABE5', '3D3D3D' ],
    'purple': [ '7764D8', 'A49AD8', '3D3D3D' ],
    'magenta': [ 'B34CB3', 'B38EB3', '3D3D3D' ],
    'red': [ 'DA3450', 'DA7586', '3D3D3D' ],
}

def srgb_to_linear(color):
    def color_conversion(c):
        if c <= 0.0404482362771082:
            return c / 12.92
        else:
            return ((c + 0.055) / 1.055) ** 2.4

    return list(map(lambda c: color_conversion(c), color[:3])) + [color[-1]]

def linear_to_srgb(color):
    def color_conversion(c):
        if c <= 0.00313066844250063:
            return c * 12.92
        else:
            return 1.055 * (c ** (1/2.4)) - 0.055

    return list(map(lambda c: color_conversion(c), color[:3])) + [color[-1]]

def hex_to_rgba(color_str):
    # supports '123456', '#123456' and '0x123456'
    (r,g,b), a = map(lambda c: c / 255, bytes.fromhex(color_str[-6:])), 1.0
    return (r,g,b,a)

def rgba_to_hex(color, alpha=False):
    rgba = ''.join(map(lambda c: "{:02x}".format(round(c * 255)), color))
    return rgba[:0 if alpha else -2].upper()

def replace_color(original, replacement):
    print('replacing', original, 'with', replacement)
    for material in bpy.data.materials:
        hex_color = rgba_to_hex(linear_to_srgb(material.diffuse_color))
        #print(material.name, 'display color (linear)', hex_color)

        emission = material.node_tree.nodes.get('Emission', None)
        if not emission:
            continue

        emission_color = emission.inputs['Color']
        hex_color = rgba_to_hex(linear_to_srgb(emission_color.default_value))
        #print('', material.name, 'emission color', hex_color)

        if hex_color == original:
            new_color = srgb_to_linear(hex_to_rgba(replacement))
            print('  converting',material.name,'to', new_color, '({})'.format(replacement))
            emission_color.default_value = new_color

def render_scene(scene, name_prefix=''):
    bpy.context.window.scene = scene

    if name_prefix:
        old_path = bpy.context.scene.render.filepath
        basename = bpy.path.basename(old_path)
        if ARGS.output_path:
            output_dir = ARGS.output_path
        else:
            output_dir = os.path.dirname(old_path)
        new_path = output_dir = os.path.join(output_dir,
            name_prefix + '-' + basename)

        bpy.context.scene.render.filepath = new_path

    print('Rendering as', bpy.context.scene.render.filepath)
    bpy.ops.render.render(animation=True) #use scene=""

    if name_prefix:
        bpy.context.scene.render.filepath = old_path

def render_all_scenes(name_prefix=''):
    for scene in bpy.data.scenes.values():
        render_scene(scene, name_prefix)


if ARGS.list_accents:
    print('\n'.join(ACCENTS.keys()))
    sys.exit(0)

if ARGS.list_assets:
    print('\n'.join(bpy.data.scenes.keys()))
    sys.exit(0)

previous_colors = DEFAULT_REPLACEMENTS
restored_values = [['original', DEFAULT_REPLACEMENTS]]

if ARGS.accent:
    all_replacements = [[ARGS.accent, ACCENTS[ARGS.accent]]] + restored_values
else:
    all_replacements = list(ACCENTS.items()) + restored_values

for (accent, replacements) in all_replacements:
    print('Processing accent', accent, replacements)
    for i in range(0, len(DEFAULT_REPLACEMENTS)):
        original = previous_colors[i]
        replacement = replacements[i]
        replace_color(original, replacement)

    if accent != 'original':
        if ARGS.asset:
            render_scene(bpy.data.scenes[ARGS.asset], name_prefix=accent)
        else:
            render_all_scenes(name_prefix=accent)

    previous_colors = replacements
