/*
 * This file is part of the CircuitPython project, https://github.com/adafruit/circuitpython
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2018 Rose Hooper
 * Copyright (c) 2022 Jeff Epler for Adafruit Industries
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "py/smallint.h"
#include "py/runtime.h"

#include "shared-bindings/_pixelmap/PixelMap.h"
#include "shared-bindings/adafruit_pixelbuf/PixelBuf.h"
#include "shared-module/_pixelmap/PixelMap.h"


static void pixelmap_set_pixel_rgbw(pixelmap_pixelmap_obj_t *self, size_t i, color_u rgbw) {
    mp_arg_validate_index_range(i, 0, self->len - 1, MP_QSTR_index);

    mp_obj_t item = self->items[i];
    if (mp_obj_is_small_int(item)) {
        common_hal_adafruit_pixelbuf_pixelbuf_set_pixel_color(self->pixelbuf, MP_OBJ_SMALL_INT_VALUE(item), rgbw.r, rgbw.g, rgbw.b, rgbw.w);
    } else {
        size_t len;
        mp_obj_t *items;
        mp_obj_tuple_get(item, &len, &items);

        for (size_t j = 0; j < len; j++) {
            common_hal_adafruit_pixelbuf_pixelbuf_set_pixel_color(self->pixelbuf, MP_OBJ_SMALL_INT_VALUE(items[j]), rgbw.r, rgbw.g, rgbw.b, rgbw.w);
        }
    }
}

static void pixelmap_set_pixel(pixelmap_pixelmap_obj_t *self, size_t i, mp_obj_t color) {
    color_u rgbw;
    common_hal_adafruit_pixelbuf_pixelbuf_parse_color(self->pixelbuf, color, &rgbw.r, &rgbw.g, &rgbw.b, &rgbw.w);
    pixelmap_set_pixel_rgbw(self, i, rgbw);
}

void shared_module_pixelmap_pixelmap_construct(pixelmap_pixelmap_obj_t *self, mp_obj_t pixelbuf, mp_obj_t indices) {
    self->pixelbuf = pixelbuf;
    self->indices = indices;
    mp_obj_tuple_get(indices, &self->len, &self->items);
}

static bool auto_write_get_and_clear(pixelmap_pixelmap_obj_t *self) {
    bool auto_write = self->auto_write && common_hal_adafruit_pixelbuf_pixelbuf_get_auto_write(self->pixelbuf);
    if (auto_write) {
        common_hal_adafruit_pixelbuf_pixelbuf_set_auto_write(self->pixelbuf, false);
    }
    return auto_write;
}

static void auto_write_reapply(pixelmap_pixelmap_obj_t *self, bool auto_write) {
    if (auto_write) {
        common_hal_adafruit_pixelbuf_pixelbuf_set_auto_write(self->pixelbuf, true);
        common_hal_adafruit_pixelbuf_pixelbuf_show(self->pixelbuf);
    }
}

bool shared_module_pixelmap_pixelmap_auto_write_get(pixelmap_pixelmap_obj_t *self) {
    return self->auto_write;
}

void shared_module_pixelmap_pixelmap_auto_write_set(pixelmap_pixelmap_obj_t *self, bool auto_write) {
    self->auto_write = auto_write;
}

void shared_module_pixelmap_pixelmap_fill(pixelmap_pixelmap_obj_t *self, const mp_obj_t color) {
    color_u rgbw;
    common_hal_adafruit_pixelbuf_pixelbuf_parse_color(self->pixelbuf, color, &rgbw.r, &rgbw.g, &rgbw.b, &rgbw.w);
    bool auto_write = auto_write_get_and_clear(self);

    for (size_t i = 0; i < self->len; i++) {
        pixelmap_set_pixel_rgbw(self, i, rgbw);
    }

    auto_write_reapply(self, auto_write);
}

mp_obj_t shared_module_pixelmap_pixelmap_indices(pixelmap_pixelmap_obj_t *self, int index) {
    mp_arg_validate_index_range(index, 0, self->len - 1, MP_QSTR_index);

    mp_obj_t item = self->items[index];
    if (mp_obj_is_small_int(item)) {
        return mp_obj_new_tuple(1, &item);
    } else {
        return item;
    }
}

#if MICROPY_PY_BUILTINS_SLICE

mp_obj_t shared_module_pixelmap_pixelmap_getslice(pixelmap_pixelmap_obj_t *self, mp_bound_slice_t slice, size_t slice_len) {
    mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(slice_len, NULL));
    for (uint i = 0; i < slice_len; i++) {
        t->items[i] = shared_module_pixelmap_pixelmap_getitem(self, i * slice.step + slice.start);
    }
    return MP_OBJ_FROM_PTR(t);
}

void shared_module_pixelmap_pixelmap_setslice(pixelmap_pixelmap_obj_t *self, const mp_obj_t values, mp_bound_slice_t slice, size_t slice_len) {
    size_t num_items = mp_obj_get_int(mp_obj_len(values));
    if (num_items != slice_len) {
        mp_raise_ValueError_varg(translate("Unmatched number of items on RHS (expected %d, got %d)."), slice_len, num_items);
    }

    bool auto_write = auto_write_get_and_clear(self);

    // because we didn't preflight the pixel values, an exception could occur.
    // In that case we need to do the auto-write of any pixels that were set
    // before re-raising the exception
    nlr_buf_t nlr;

    size_t start = slice.start;
    mp_int_t step = slice.step;

    if (nlr_push(&nlr) == 0) {
        mp_obj_iter_buf_t iter_buf;
        mp_obj_t iterable = mp_getiter(values, &iter_buf);
        mp_obj_t item;
        while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
            pixelmap_set_pixel(self, start, item);
            start += step;
        }
        nlr_pop();
        auto_write_reapply(self, auto_write);
    } else {
        auto_write_reapply(self, auto_write);
        // exception converting color value, re-raise
        nlr_raise(MP_OBJ_FROM_PTR(nlr.ret_val));
    }
}
#endif

void shared_module_pixelmap_pixelmap_setitem(pixelmap_pixelmap_obj_t *self, mp_int_t i, mp_obj_t color) {
    color_u rgbw;
    common_hal_adafruit_pixelbuf_pixelbuf_parse_color(self->pixelbuf, color, &rgbw.r, &rgbw.g, &rgbw.b, &rgbw.w);
    bool auto_write = auto_write_get_and_clear(self);
    pixelmap_set_pixel_rgbw(self, i, rgbw);
    auto_write_reapply(self, auto_write);
}

mp_obj_t shared_module_pixelmap_pixelmap_getitem(pixelmap_pixelmap_obj_t *self, mp_int_t i) {
    mp_arg_validate_index_range(i, 0, self->len - 1, MP_QSTR_index);
    mp_obj_t item = self->items[i];
    if (mp_obj_is_small_int(item)) {
        return common_hal_adafruit_pixelbuf_pixelbuf_get_pixel(self->pixelbuf, MP_OBJ_SMALL_INT_VALUE(item));
    } else {
        size_t len;
        mp_obj_t *items;
        mp_obj_tuple_get(item, &len, &items);
        return common_hal_adafruit_pixelbuf_pixelbuf_get_pixel(self->pixelbuf, MP_OBJ_SMALL_INT_VALUE(items[0]));
    }
}
