# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from resources.types.resource_object import ResourceObject
from resources.resource_map.resource_generator import ResourceGenerator

from pebble_sdk_platform import pebble_platforms

import bitmapgen
import png2pblpng
import re

PNG_MIN_APP_MEMORY = 0x8000  # 32k, fairly arbitrarily

class BitmapResourceGenerator(ResourceGenerator):
    type = 'bitmap'

    @staticmethod
    def definitions_from_dict(bld, definition_dict, resource_source_path):
        definitions = ResourceGenerator.definitions_from_dict(bld, definition_dict,
                                                              resource_source_path)
        for d in definitions:
            d.memory_format = definition_dict.get('memoryFormat', 'smallest')
            d.space_optimization = definition_dict.get('spaceOptimization', None)
            d.storage_format = definition_dict.get('storageFormat', None)
        return definitions

    @classmethod
    def generate_object(cls, task, definition):
        env = task.generator.env

        memory_format = definition.memory_format.lower()
        # Some options are mutually exclusive.
        if definition.space_optimization == 'memory' and definition.storage_format == 'png':
            task.generator.bld.fatal("{}: spaceOptimization: memory and storageFormat: "
                                     "png are mutually exclusive.".format(definition.name))
        if definition.space_optimization == 'storage' and definition.storage_format == 'pbi':
            task.generator.bld.fatal("{}: spaceOptimization: storage and storageFormat: "
                                     "pbi are mutually exclusive.".format(definition.name))
        if definition.storage_format == 'png' and memory_format == '1bit':
            task.generator.bld.fatal("{}: PNG storage does not support non-palettised 1-bit images."
                                     .format(definition.name))

        # If storage_format is not specified, it is completely determined by space_optimization.
        if definition.storage_format is None and definition.space_optimization is not None:
            format_mapping = {
                'storage': 'png',
                'memory': 'pbi',
            }
            try:
                definition.storage_format = format_mapping[definition.space_optimization]
            except KeyError:
                task.generator.bld.fatal("{}: Invalid spaceOptimization '{}'. Use one of {}."
                                         .format(definition.name, definition.space_optimization,
                                                 ', '.join(format_mapping.keys())))

        if definition.storage_format is None:
            if pebble_platforms[env.PLATFORM_NAME]['MAX_APP_MEMORY_SIZE'] < PNG_MIN_APP_MEMORY:
                definition.storage_format = 'pbi'
            else:
                definition.storage_format = 'png'

        # At this point, what we want to do should be completely determined (though, depending on
        # image content, not necessarily actually possible); begin figuring out what to actually do.

        is_color = 'color' in pebble_platforms[env.PLATFORM_NAME]['TAGS']
        palette_name = png2pblpng.get_ideal_palette(is_color)
        _, _, bitdepth, _ = png2pblpng.get_palette_for_png(task.inputs[0].abspath(), palette_name,
                                                           png2pblpng.DEFAULT_COLOR_REDUCTION)

        formats = {
            '1bit',
            '8bit',
            'smallest',
            'smallestpalette',
            '1bitpalette',
            '2bitpalette',
            '4bitpalette',
        }
        if memory_format not in formats:
            task.generator.bld.fatal("{}: Invalid memoryFormat {} (pick one of {})."
                                     .format(definition.name, definition.memory_format,
                                             ', '.join(formats)))

        # "smallest" is always palettised, unless the image has too many colours, in which case it
        # cannot be.
        if memory_format == 'smallest':
            if bitdepth <= 4:
                memory_format = 'smallestpalette'
            else:
                memory_format = '8bit'

        if 'palette' in memory_format:
            if memory_format == 'smallestpalette':
                # If they asked for "smallestpalette", replace that with its actual value.
                if bitdepth > 4:
                    task.generator.bld.fatal("{} has too many colours for a palettised image"
                                             "(max 16), but 'SmallestPalette' specified."
                                             .format(definition.name))
                else:
                    memory_format = '{}bitpalette'.format(bitdepth)

            # Pull out however many bits we're supposed to use (which is exact, not a min or max)
            bits = int(re.match(r'^(\d+)bitpalette$', memory_format).group(1))
            if bits < bitdepth:
                task.generator.bld.fatal("{}: requires at least {} bits.".format(definition.name,
                                                                                 bitdepth))
            if bits > 2 and not is_color:
                task.generator.bld.fatal("{}: can't use more than two bits on a black-and-white"
                                         "platform." .format(definition.name))

            if definition.storage_format == 'pbi':
                pb = bitmapgen.PebbleBitmap(task.inputs[0].abspath(), bitmap_format='color',
                                            bitdepth=bits, crop=False, palette_name=palette_name)
                return ResourceObject(definition, pb.convert_to_pbi())
            else:
                image_bytes = png2pblpng.convert_png_to_pebble_png_bytes(task.inputs[0].abspath(),
                                                                         palette_name,
                                                                         bitdepth=bits)
                return ResourceObject(definition, image_bytes)
        else:
            if memory_format == '1bit':
                pb = bitmapgen.PebbleBitmap(task.inputs[0].abspath(), bitmap_format='bw',
                                            crop=False)
                return ResourceObject(definition, pb.convert_to_pbi())
            elif memory_format == '8bit':
                if not is_color:
                    task.generator.bld.fatal("{}: can't use more than two bits on a black-and-white"
                                             "platform.".format(definition.name))
                # generate an 8-bit pbi or png, as appropriate.
                if definition.storage_format == 'pbi':
                    pb = bitmapgen.PebbleBitmap(task.inputs[0].abspath(), bitmap_format='color_raw',
                                                crop=False, palette_name=palette_name)
                    return ResourceObject(definition, pb.convert_to_pbi())
                else:
                    image_bytes = png2pblpng.convert_png_to_pebble_png_bytes(
                        task.inputs[0].abspath(), palette_name, bitdepth=8)
                    return ResourceObject(definition, image_bytes)

        raise Exception("Got to the end without doing anything?")