use crate::avm2::{Object as Avm2Object, Value as Avm2Value};
use crate::display_object::{DisplayObject, DisplayObjectWeak, TDisplayObject};
use bitflags::bitflags;
use gc_arena::{Collect, Mutation};
use ruffle_render::backend::RenderBackend;
use ruffle_render::bitmap::{Bitmap, BitmapFormat, BitmapHandle, PixelRegion, SyncHandle};
use ruffle_wstr::WStr;
use std::fmt::Debug;
use std::ops::Range;
use swf::{Rectangle, Twips};
use tracing::instrument;

/// An implementation of the Lehmer/Park-Miller random number generator
/// Uses the fixed parameters m = 2,147,483,647 and a = 16,807
pub struct LehmerRng {
    x: u32,
}

impl LehmerRng {
    pub fn with_seed(seed: u32) -> Self {
        Self { x: seed }
    }

    /// Generate the next value in the sequence via the following formula
    /// X_(k+1) = a * X_k mod m
    pub fn gen(&mut self) -> u32 {
        self.x = ((self.x as u64).overflowing_mul(16_807).0 % 2_147_483_647) as u32;
        self.x
    }

    pub fn gen_range(&mut self, rng: Range<u8>) -> u8 {
        rng.start + (self.gen() % ((rng.end - rng.start) as u32 + 1)) as u8
    }
}

/// This can represent both a premultiplied and an unmultiplied ARGB color value.
///
/// Note that most operations only make sense on one of these representations:
/// For example, blending on premultiplied values, and applying a `ColorTransform` on
/// unmultiplied values. Make sure to convert the color to the correct form beforehand.
// TODO: Maybe split the type into `PremultipliedColor(u32)` and
//   `UnmultipliedColor(u32)`?
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct Color(u32);

#[derive(Debug, Clone)]
pub enum BitmapDataDrawError {
    Unimplemented,
}

impl Color {
    pub fn blue(&self) -> u8 {
        (self.0 & 0xFF) as u8
    }

    pub fn green(&self) -> u8 {
        ((self.0 >> 8) & 0xFF) as u8
    }

    pub fn red(&self) -> u8 {
        ((self.0 >> 16) & 0xFF) as u8
    }

    pub fn alpha(&self) -> u8 {
        ((self.0 >> 24) & 0xFF) as u8
    }

    #[must_use]
    pub fn to_premultiplied_alpha(self, transparency: bool) -> Self {
        // This has some accuracy issues with some alpha values

        let old_alpha = if transparency { self.alpha() } else { 255 };

        let a = old_alpha as u32;
        let r = ((self.red() as u32 * a + 127) / 255) as u8;
        let g = ((self.green() as u32 * a + 127) / 255) as u8;
        let b = ((self.blue() as u32 * a + 127) / 255) as u8;

        Self::argb(old_alpha, r, g, b)
    }

    #[must_use]
    pub fn to_un_multiplied_alpha(self) -> Self {
        // We need to match Flash's results, and this lookup table was generated by brute force.
        // For each alpha value, every value between 0..256^3 was tested to see if it produced the
        // correct color value when reversing the premultiplication.
        // Source code used to generate this table can be found at:
        // https://gist.github.com/pdewacht/614b428cd42c2052dc0fd292516c9f9f
        const FLASH_PREMUL_FACTOR: [u32; 256] = [
            0, 16678912, 8339456, 5559638, 4169728, 3335783, 2779819, 2386603, 2086230, 1855488,
            1667892, 1518251, 1391151, 1285234, 1193302, 1111928, 1043895, 981113, 927744, 879275,
            834621, 795535, 759126, 726358, 695839, 668183, 642538, 618737, 596651, 576171, 555964,
            538706, 522104, 506319, 490557, 477321, 464038, 451353, 439544, 428244, 417582, 407500,
            397768, 388535, 379630, 371117, 363179, 355235, 348050, 340965, 334052, 327038, 321269,
            315077, 309159, 303586, 298189, 293092, 287981, 283080, 278251, 273892, 269268, 265179,
            261087, 256971, 253160, 249322, 245508, 242164, 238575, 235245, 231859, 228848, 225785,
            222712, 219616, 216827, 213985, 211432, 208835, 206075, 203750, 201196, 198895, 196223,
            194301, 191987, 189686, 187636, 185559, 183426, 181453, 179444, 177638, 175855, 174054,
            171948, 170489, 168695, 166889, 165365, 163519, 162045, 160508, 158970, 157429, 156150,
            154610, 153081, 151803, 150511, 148986, 147709, 146420, 145116, 143868, 142586, 141545,
            140277, 139194, 137957, 136954, 135676, 134652, 133621, 132604, 131577, 130552, 129527,
            128508, 127476, 126451, 125432, 124670, 123645, 122818, 121847, 121082, 120060, 119288,
            118263, 117502, 116720, 115967, 115195, 114424, 113655, 112893, 112125, 111356, 110563,
            109811, 109048, 108287, 107766, 107004, 106236, 105724, 104953, 104434, 103676, 102904,
            102375, 101879, 101119, 100604, 99834, 99321, 98813, 98112, 97533, 97019, 96509, 95994,
            95486, 94713, 94185, 93689, 93179, 92667, 92149, 91643, 91129, 90621, 90068, 89597,
            89342, 88829, 88318, 87804, 87294, 87034, 86523, 85994, 85499, 85245, 84732, 84222,
            83956, 83450, 82937, 82685, 82173, 81840, 81405, 80889, 80638, 80127, 79862, 79354,
            79103, 78590, 78332, 78077, 77565, 77308, 76795, 76541, 76284, 75766, 75518, 75262,
            74748, 74493, 74238, 73691, 73470, 73214, 72959, 72447, 72189, 71935, 71671, 71166,
            70911, 70651, 70399, 70140, 69886, 69615, 69116, 68861, 68603, 68350, 68093, 67839,
            67576, 67326, 67070, 66813, 66556, 66302, 66046, 65791, 65408,
        ];

        let alpha_factor = FLASH_PREMUL_FACTOR[self.alpha() as usize];
        let unmultiply = |c| ((c as u32 * alpha_factor + 0x8000) >> 16) as u8;

        let r = unmultiply(self.red());
        let g = unmultiply(self.green());
        let b = unmultiply(self.blue());

        Self::argb(self.alpha(), r, g, b)
    }

    #[must_use]
    pub fn argb(alpha: u8, red: u8, green: u8, blue: u8) -> Self {
        Self(u32::from_le_bytes([blue, green, red, alpha]))
    }

    #[must_use]
    pub fn with_alpha(&self, alpha: u8) -> Self {
        Self::argb(alpha, self.red(), self.green(), self.blue())
    }

    /// # Arguments
    ///
    /// * `self` - Must be in premultiplied form.
    /// * `source` - Must be in premultiplied form.
    #[must_use]
    pub fn blend_over(&self, source: &Self) -> Self {
        let sa = source.alpha();

        let r = source.red() + ((self.red() as u16 * (255 - sa as u16)) / 255) as u8;
        let g = source.green() + ((self.green() as u16 * (255 - sa as u16)) / 255) as u8;
        let b = source.blue() + ((self.blue() as u16 * (255 - sa as u16)) / 255) as u8;
        let a = source.alpha() + ((self.alpha() as u16 * (255 - sa as u16)) / 255) as u8;
        Self::argb(a, r, g, b)
    }
}

impl std::fmt::Display for Color {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&format!("{:#x}", self.0))
    }
}

impl From<Color> for u32 {
    fn from(c: Color) -> Self {
        c.0
    }
}

impl From<u32> for Color {
    fn from(i: u32) -> Self {
        Color(i)
    }
}

impl From<swf::Color> for Color {
    fn from(c: swf::Color) -> Self {
        Self::argb(c.a, c.r, c.g, c.b)
    }
}

impl From<Color> for swf::Color {
    fn from(c: Color) -> Self {
        let r = c.red();
        let g = c.green();
        let b = c.blue();
        let a = c.alpha();
        Self { r, g, b, a }
    }
}

bitflags! {
    pub struct ChannelOptions: u8 {
        const RED = 1 << 0;
        const GREEN = 1 << 1;
        const BLUE = 1 << 2;
        const ALPHA = 1 << 3;
        const RGB = Self::RED.bits() | Self::GREEN.bits() | Self::BLUE.bits();
    }
}

#[derive(Collect)]
#[collect(no_drop)]
pub struct BitmapData<'gc> {
    /// The pixels in the bitmap, stored as a array of pre-multiplied ARGB colour values
    #[collect(require_static)]
    pixels: Vec<Color>,

    width: u32,
    height: u32,
    transparency: bool,

    // Note that it's technically possible to have a BitmapData with zero width and height,
    // (by embedding it in the SWF instead of using the BitmapData constructor),
    // so we need a separate 'disposed' flag.
    disposed: bool,

    /// The bitmap handle for this data.
    ///
    /// This is lazily initialized; a value of `None` indicates that
    /// initialization has not yet happened.
    #[collect(require_static)]
    bitmap_handle: Option<BitmapHandle>,

    /// The AVM2 side of this `BitmapData`.
    ///
    /// AVM1 cannot retrieve `BitmapData` back from the display object tree, so
    /// this does not need to hold an AVM1 object.
    avm2_object: Option<Avm2Object<'gc>>,

    /// A list of display objects that are backed by this BitmapData
    display_objects: Vec<DisplayObjectWeak<'gc>>,

    #[collect(require_static)]
    dirty_state: DirtyState,

    /// Holds an egui texture handle, used for rendering this Bitmap in the debug ui.
    /// This is automatically set to `None` when the texture is updated (either from
    /// marking the CPU side dirty, or from performing a GPU -> CPU sync).
    #[cfg(feature = "egui")]
    pub egui_texture: std::cell::RefCell<Option<egui::TextureHandle>>,
}

#[derive(Clone, Debug)]
enum DirtyState {
    // Both the CPU and GPU pixels are up to date. We do not need to wait for any syncs to complete
    Clean,

    // The CPU pixels have been modified, and need to be synced to the GPU via `update_dirty_texture`
    CpuModified(PixelRegion),

    // The GPU pixels have been modified, and need to be synced to the CPU via `BitmapDataWrapper::sync`
    GpuModified(Box<dyn SyncHandle>, PixelRegion),
}

mod wrapper {
    use crate::avm2::{Object as Avm2Object, Value as Avm2Value};
    use crate::context::RenderContext;
    use crate::display_object::DisplayObjectWeak;
    use gc_arena::{Collect, GcCell, Mutation};
    use ruffle_render::backend::RenderBackend;
    use ruffle_render::bitmap::{BitmapHandle, PixelRegion, PixelSnapping};
    use ruffle_render::commands::CommandHandler;
    use std::cell::Ref;

    use super::{copy_pixels_to_bitmapdata, BitmapData, DirtyState};

    /// A wrapper type that ensures that we always wait for a pending
    /// GPU -> CPU sync to complete (using `sync_handle`) before accessing
    /// the CPU-side pixels.
    ///
    /// This is overly conservative - we perform a sync before allowing any access
    /// to the underlying `BitmapData`, even if we wouldn't be accessing the pixels.
    /// Implementing more fine-grained tracking turned out to be extremely invasive,
    /// and made the code much less readable. This should be enough for the simple
    /// case where ActionScript calls `BitmapData.draw`, and then doesn't interact
    /// with the Bitmap/BitmapData object at all for some time.
    ///
    /// There are three ways that this type gets used:
    /// 1. Blocking on the current GPU->CPU sync via the `sync` method,
    ///    and obtainng a `GcCell<'gc, BitmapData<'gc>>` (or implicily through `as_bitmap_data`).
    ///    This is done for the vast majority of BitmapData AS2/AS3 methods, as they need to access CPU-side pixels.
    /// 2. Ignoring the current GPU->CPU sync state. This is done by the `render` method defined on this type,
    ///    since rendering only uses GPU-side data, and ignores CPU-side pixels entirely.
    /// 3. Explicitly cancelling any in-progress GPU->CPU sync via `overwrite_cpu_pixels_from_gpu`. This is
    ///    used by `BitmapData.draw` and `BitmapData.apply_filter`, since the new rendering result will completely
    ///    replace the current CPU-side pixels. This performs a CPU -> GPU sync, to ensure that the GPU side
    ///    is up to date before we overwrite the CPU-side pixels.
    ///    In the future, we could explore using this in additional
    ///    cases where we know that the entire CPU-side pixel array will be overwritten without being read
    ///    (e.g. `BitmapData.fillRect` with a rectangle covering the entire bitmap). However, `overwrite_cpu_pixels`
    ///    is always a performance optimization, and can always be safely replaced with `sync` (at the cost of worse performance)
    ///
    /// Note that we also perform CPU-GPU syncs from `BitmapData.update_dirty_texture` when `dirty` is set.
    /// `sync_handle` and `dirty` can never be set at the same time - we can only have one of them set, or none of them set.
    #[derive(Copy, Clone, Collect, Debug)]
    #[collect(no_drop)]
    pub struct BitmapDataWrapper<'gc>(GcCell<'gc, BitmapData<'gc>>);

    impl<'gc> BitmapDataWrapper<'gc> {
        pub fn new(data: GcCell<'gc, BitmapData<'gc>>) -> Self {
            BitmapDataWrapper(data)
        }

        // Creates a dummy BitmapData with no pixels or handle, marked as disposed.
        // This is used for AS3 `Bitmap` instances without a corresponding AS3 `BitmapData` instance.
        // Marking it as disposed skips rendering, and the unset `avm2_object` will cause this to
        // be inaccessible to AS3 code.
        pub fn dummy(mc: &Mutation<'gc>) -> Self {
            BitmapDataWrapper(GcCell::new(
                mc,
                BitmapData {
                    pixels: Vec::new(),
                    width: 0,
                    height: 0,
                    transparency: false,
                    disposed: true,
                    bitmap_handle: None,
                    avm2_object: None,
                    display_objects: vec![],
                    dirty_state: DirtyState::Clean,
                    #[cfg(feature = "egui")]
                    egui_texture: Default::default(),
                },
            ))
        }

        /// Clones the underlying data, producing a new `BitmapData`
        /// that has no GPU texture or associated display objects
        pub fn clone_data(&self, renderer: &mut dyn RenderBackend) -> BitmapData<'gc> {
            // Sync from the GPU to CPU, since our new BitmapData starts out
            // with no GPU texture
            let data = self.sync(renderer);
            let data = data.read();
            BitmapData {
                pixels: data.pixels.clone(),
                width: data.width,
                height: data.height,
                transparency: data.transparency,
                disposed: data.disposed,
                bitmap_handle: None,
                avm2_object: None,
                display_objects: vec![],
                // We have no GPU texture, so there's no need to mark as dirty
                dirty_state: DirtyState::Clean,
                #[cfg(feature = "egui")]
                egui_texture: Default::default(),
            }
        }

        // Provides access to the underlying `BitmapData`. If a GPU -> CPU sync
        // is in progress, waits for it to complete
        pub fn sync(&self, renderer: &mut dyn RenderBackend) -> GcCell<'gc, BitmapData<'gc>> {
            // SAFETY: The only fields that can store gc pointers are `avm2_object` and `dirty_callbacks`,
            // which we don't update here. Ideally, we would refactor this so that
            // `BitmapData` doesn't contain any gc pointers, allowing us to use a normal
            // `RefCell` instead of a `GcCell`.
            let mut write = unsafe { self.0.borrow_mut() };
            match std::mem::replace(&mut write.dirty_state, DirtyState::Clean) {
                DirtyState::GpuModified(sync_handle, bounds) => {
                    renderer
                        .resolve_sync_handle(
                            sync_handle,
                            Box::new(|buffer, buffer_width| {
                                copy_pixels_to_bitmapdata(&mut write, buffer, buffer_width, bounds)
                            }),
                        )
                        .expect("Failed to sync BitmapData");
                    write.dirty_state = DirtyState::Clean;
                    #[cfg(feature = "egui")]
                    write.egui_texture.borrow_mut().take();
                }
                old_state => write.dirty_state = old_state,
            }
            self.0
        }

        /// Provides access to the underlying `BitmapHandle`.
        /// If the CPU pixels are dirty, syncs them to the GPU.
        /// If the GPU pixels are dirty, then handle is returned immediately
        /// without waiting for the sync to complete, as a BitmapHandle can
        /// only be used to access the GPU data. Unlike `overwrite_cpu_pixels_from_gpu`,
        /// this does not cancel the GPU -> CPU sync.
        pub fn bitmap_handle(
            &self,
            gc_context: &Mutation<'gc>,
            renderer: &mut dyn RenderBackend,
        ) -> BitmapHandle {
            let mut bitmap_data = self.0.write(gc_context);
            bitmap_data.update_dirty_texture(renderer);
            bitmap_data.bitmap_handle(renderer).unwrap()
        }

        /// Provides access to the underlying `BitmapData`.
        /// This should only be used when you will be overwriting the entire
        /// `pixels` vec without reading from it. Cancels any in-progress GPU -> CPU sync.
        /// This does not sync from cpu to gpu.
        #[allow(clippy::type_complexity)]
        pub fn overwrite_cpu_pixels_from_gpu(
            &self,
            mc: &Mutation<'gc>,
        ) -> (GcCell<'gc, BitmapData<'gc>>, Option<PixelRegion>) {
            let mut write = self.0.write(mc);
            let dirty_rect = match write.dirty_state {
                DirtyState::GpuModified(_, rect) => {
                    write.dirty_state = DirtyState::Clean;
                    Some(rect)
                }
                DirtyState::CpuModified(_) | DirtyState::Clean => None,
            };
            #[cfg(feature = "egui")]
            write.egui_texture.borrow_mut().take();
            (self.0, dirty_rect)
        }

        /// Provides read access to the BitmapData pixels.
        /// Only the provided region is guaranteed to be up-to-date.
        /// It is an error to access any other pixels outside of that region.
        pub fn read_area(
            &self,
            read_area: PixelRegion,
            renderer: &mut dyn RenderBackend,
        ) -> Ref<'_, BitmapData<'gc>> {
            let needs_update = if let DirtyState::GpuModified(_, area) = self.0.read().dirty_state {
                area.intersects(read_area)
            } else {
                false
            };
            if needs_update {
                self.sync(renderer);
            }
            self.0.read()
        }

        // These methods do not require a sync to complete, as they do not depend on the
        // CPU-side pixels. They are implemented directly on `BitmapDataWrapper`, allowing
        // callers to avoid calling sync()

        pub fn height(&self) -> u32 {
            self.0.read().height
        }

        pub fn width(&self) -> u32 {
            self.0.read().width
        }

        pub fn object2(&self) -> Avm2Value<'gc> {
            self.0.read().object2()
        }

        pub fn disposed(&self) -> bool {
            self.0.read().disposed
        }

        pub fn transparency(&self) -> bool {
            self.0.read().transparency
        }

        pub fn check_valid(
            &self,
            activation: &mut crate::avm2::Activation<'_, 'gc>,
        ) -> Result<(), crate::avm2::Error<'gc>> {
            if self.disposed() {
                return Err(crate::avm2::Error::AvmError(
                    crate::avm2::error::argument_error(
                        activation,
                        "Error #2015: Invalid BitmapData.",
                        2015,
                    )?,
                ));
            }
            Ok(())
        }

        pub fn dispose(&self, mc: &Mutation<'gc>) {
            self.0.write(mc).dispose();
        }

        pub fn init_object2(&self, mc: &Mutation<'gc>, object: Avm2Object<'gc>) {
            self.0.write(mc).avm2_object = Some(object)
        }

        pub fn remove_display_object(&self, mc: &Mutation<'gc>, callback: DisplayObjectWeak<'gc>) {
            // [NA] Removing is a rare operation, whereas insert is often, and iteration is extremely frequent.
            // The list will typically be 0-1 entries long too, so I think retain is fine for quick iteration.
            self.0
                .write(mc)
                .display_objects
                .retain(|c| c.as_ptr() != callback.as_ptr())
        }

        pub fn add_display_object(&self, mc: &Mutation<'gc>, callback: DisplayObjectWeak<'gc>) {
            self.0.write(mc).display_objects.push(callback);
        }

        pub fn render(
            &self,
            smoothing: bool,
            context: &mut RenderContext<'_, 'gc>,
            pixel_snapping: PixelSnapping,
        ) {
            let mut inner_bitmap_data = self.0.write(context.gc());
            if inner_bitmap_data.disposed() {
                return;
            }

            // Note - we do a CPU -> GPU sync, but we do *not* do a GPU -> CPU sync
            // (rendering is done on the GPU, so the CPU pixels don't need to be up-to-date).
            inner_bitmap_data.update_dirty_texture(context.renderer);
            let handle = inner_bitmap_data
                .bitmap_handle(context.renderer)
                .expect("Missing bitmap handle");

            context.commands.render_bitmap(
                handle,
                context.transform_stack.transform(),
                smoothing,
                pixel_snapping,
            );
        }

        pub fn can_read(&self, read_area: PixelRegion) -> bool {
            if let DirtyState::GpuModified(_, area) = self.0.read().dirty_state {
                !area.intersects(read_area)
            } else {
                true
            }
        }

        #[cfg(feature = "egui")]
        pub fn debug_sync_status(&self) -> std::borrow::Cow<'static, str> {
            match self.0.read().dirty_state {
                DirtyState::Clean => std::borrow::Cow::Borrowed("Clean"),
                DirtyState::CpuModified(area) => std::borrow::Cow::Owned(format!(
                    "CPU modified from {}, {} to {}, {}",
                    area.x_min, area.y_min, area.x_max, area.y_max
                )),
                DirtyState::GpuModified(_, area) => std::borrow::Cow::Owned(format!(
                    "GPU modified from {}, {} to {}, {}",
                    area.x_min, area.y_min, area.x_max, area.y_max
                )),
            }
        }

        pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {
            x >= 0 && x < self.width() as i32 && y >= 0 && y < self.height() as i32
        }

        pub fn ptr_eq(&self, other: BitmapDataWrapper<'gc>) -> bool {
            GcCell::ptr_eq(self.0, other.0)
        }
    }
}

pub use wrapper::BitmapDataWrapper;

impl std::fmt::Debug for BitmapData<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("BitmapData")
            .field("dirty_state", &self.dirty_state)
            .field("width", &self.width)
            .field("height", &self.height)
            .field("transparency", &self.transparency)
            .field("disposed", &self.disposed)
            .field("bitmap_handle", &self.bitmap_handle)
            .finish()
    }
}

impl<'gc> BitmapData<'gc> {
    pub fn new(width: u32, height: u32, transparency: bool, fill_color: u32) -> Self {
        Self {
            pixels: vec![
                Color(fill_color).to_premultiplied_alpha(transparency);
                width as usize * height as usize
            ],
            width,
            height,
            transparency,
            disposed: false,
            bitmap_handle: None,
            avm2_object: None,
            display_objects: vec![],
            dirty_state: DirtyState::Clean,
            #[cfg(feature = "egui")]
            egui_texture: Default::default(),
        }
    }

    pub fn new_with_pixels(
        width: u32,
        height: u32,
        transparency: bool,
        pixels: Vec<Color>,
    ) -> Self {
        Self {
            pixels,
            width,
            height,
            transparency,
            bitmap_handle: None,
            avm2_object: None,
            disposed: false,
            dirty_state: DirtyState::Clean,
            display_objects: vec![],
            #[cfg(feature = "egui")]
            egui_texture: Default::default(),
        }
    }

    pub fn disposed(&self) -> bool {
        self.disposed
    }

    pub fn dispose(&mut self) {
        self.width = 0;
        self.height = 0;
        self.pixels.clear();
        self.bitmap_handle = None;
        // There's no longer a handle to update
        self.dirty_state = DirtyState::Clean;
        self.disposed = true;
    }

    pub fn bitmap_handle(&mut self, renderer: &mut dyn RenderBackend) -> Option<BitmapHandle> {
        if self.bitmap_handle.is_none() {
            let bitmap = Bitmap::new(
                self.width(),
                self.height(),
                BitmapFormat::Rgba,
                self.pixels_rgba(),
            );
            let bitmap_handle = renderer.register_bitmap(bitmap);
            if let Err(e) = &bitmap_handle {
                tracing::warn!("Failed to register raw bitmap for BitmapData: {:?}", e);
            } else {
                self.dirty_state = DirtyState::Clean;
            }
            self.bitmap_handle = bitmap_handle.ok();
        }

        self.bitmap_handle.clone()
    }

    pub fn transparency(&self) -> bool {
        self.transparency
    }

    pub fn set_gpu_dirty(
        &mut self,
        gc_context: &Mutation<'gc>,
        sync_handle: Box<dyn SyncHandle>,
        region: PixelRegion,
    ) {
        self.dirty_state = DirtyState::GpuModified(sync_handle, region);
        self.inform_display_objects(gc_context);
    }

    pub fn set_cpu_dirty(&mut self, gc_context: &Mutation<'gc>, region: PixelRegion) {
        debug_assert!(region.x_max <= self.width);
        debug_assert!(region.y_max <= self.height);

        #[cfg(feature = "egui")]
        self.egui_texture.borrow_mut().take();

        let inform_changes = match &mut self.dirty_state {
            DirtyState::CpuModified(old_region) => {
                old_region.union(region);
                false
            }
            DirtyState::Clean => {
                self.dirty_state = DirtyState::CpuModified(region);
                true
            }
            DirtyState::GpuModified(_, _) => {
                panic!("Attempted to modify CPU dirty state while GPU sync is in progress!")
            }
        };
        if inform_changes {
            self.inform_display_objects(gc_context);
        }
    }

    pub fn pixels(&self) -> &[Color] {
        &self.pixels
    }

    pub fn pixels_rgba(&self) -> Vec<u8> {
        // TODO: This could have been implemented as follows:
        //
        // self.pixels
        //     .iter()
        //     .flat_map(|p| [p.red(), p.green(), p.blue(), p.alpha()])
        //     .collect()
        //
        // But currently Rust emits suboptimal code in that case. For now we use
        // `Vec::with_capacity` manually to avoid unnecessary re-allocations.

        let mut output = Vec::with_capacity(self.pixels.len() * 4);
        for p in &self.pixels {
            output.extend_from_slice(&[p.red(), p.green(), p.blue(), p.alpha()])
        }
        output
    }

    pub fn width(&self) -> u32 {
        self.width
    }
    pub fn height(&self) -> u32 {
        self.height
    }

    pub fn is_point_in_bounds(&self, x: i32, y: i32) -> bool {
        x >= 0 && x < self.width() as i32 && y >= 0 && y < self.height() as i32
    }

    #[inline]
    pub fn set_pixel32_raw(&mut self, x: u32, y: u32, color: Color) {
        self.pixels[(x + y * self.width) as usize] = color;
    }

    #[inline]
    pub fn set_pixel32_row_raw(&mut self, x1: u32, x2: u32, y: u32, color: Color) {
        let p1 = (x1 + y * self.width) as usize;
        let p2 = (x2 + y * self.width) as usize;
        let slice = &mut self.pixels[p1..p2];
        slice.fill(color);
    }

    #[inline]
    pub fn fill(&mut self, color: Color) {
        self.pixels.fill(color);
    }

    #[inline]
    pub fn get_pixel32_raw(&self, x: u32, y: u32) -> Color {
        self.pixels[(x + y * self.width()) as usize]
    }

    pub fn raw_pixels_mut(&mut self) -> &mut Vec<Color> {
        &mut self.pixels
    }

    pub fn raw_pixels(&self) -> &[Color] {
        &self.pixels
    }

    // Updates the data stored with our `BitmapHandle` if this `BitmapData`
    // is dirty
    pub fn update_dirty_texture(&mut self, renderer: &mut dyn RenderBackend) {
        let handle = self.bitmap_handle(renderer).unwrap();
        match &self.dirty_state {
            DirtyState::CpuModified(region) => {
                if let Err(e) = renderer.update_texture(
                    &handle,
                    Bitmap::new(
                        self.width(),
                        self.height(),
                        BitmapFormat::Rgba,
                        self.pixels_rgba(),
                    ),
                    *region,
                ) {
                    tracing::error!("Failed to update dirty bitmap {:?}: {:?}", handle, e);
                }
                self.dirty_state = DirtyState::Clean
            }
            DirtyState::Clean | DirtyState::GpuModified(_, _) => {}
        }
    }

    fn inform_display_objects(&self, gc_context: &Mutation<'gc>) {
        for object in &self.display_objects {
            if let Some(object) = object.upgrade(gc_context) {
                object.invalidate_cached_bitmap(gc_context);
            }
        }
    }

    pub fn object2(&self) -> Avm2Value<'gc> {
        self.avm2_object
            .map(|o| o.into())
            .unwrap_or(Avm2Value::Null)
    }

    pub fn init_object2(&mut self, object: Avm2Object<'gc>) {
        self.avm2_object = Some(object)
    }
}

pub enum IBitmapDrawable<'gc> {
    BitmapData(BitmapDataWrapper<'gc>),
    DisplayObject(DisplayObject<'gc>),
}

impl IBitmapDrawable<'_> {
    pub fn bounds(&self) -> Rectangle<Twips> {
        match self {
            IBitmapDrawable::BitmapData(bmd) => Rectangle {
                x_min: Twips::ZERO,
                x_max: Twips::from_pixels(bmd.width() as f64),
                y_min: Twips::ZERO,
                y_max: Twips::from_pixels(bmd.height() as f64),
            },
            IBitmapDrawable::DisplayObject(o) => o.bounds(),
        }
    }
}

#[instrument(level = "debug", skip_all)]
fn copy_pixels_to_bitmapdata(
    write: &mut BitmapData,
    buffer: &[u8],
    buffer_width: u32,
    area: PixelRegion,
) {
    let buffer_width_pixels = buffer_width / 4;

    for y in area.y_min..area.y_max {
        for x in area.x_min..area.x_max {
            // note: this order of conversions helps llvm realize the index is 4-byte-aligned
            let ind = (((x - area.x_min) + (y - area.y_min) * buffer_width_pixels) as usize) * 4;

            // TODO(mid): optimize this A LOT
            let r = buffer[ind];
            let g = buffer[ind + 1usize];
            let b = buffer[ind + 2usize];
            let a = if write.transparency() {
                buffer[ind + 3usize]
            } else {
                255
            };

            // TODO(later): we might want to swap Color storage from argb to rgba, to make it cheaper
            let nc = Color::argb(a, r, g, b);

            // Ignore the original color entirely - the blending (including alpha)
            // was done by the renderer when it wrote over the previous texture contents.
            write.set_pixel32_raw(x, y, nc);
        }
    }
}

#[derive(Copy, Clone, Debug)]
pub enum ThresholdOperation {
    Equals,
    NotEquals,
    LessThan,
    LessThanOrEquals,
    GreaterThan,
    GreaterThanOrEquals,
}

impl ThresholdOperation {
    pub fn from_wstr(str: &WStr) -> Option<Self> {
        if str == b"==" {
            Some(Self::Equals)
        } else if str == b"!=" {
            Some(Self::NotEquals)
        } else if str == b"<" {
            Some(Self::LessThan)
        } else if str == b"<=" {
            Some(Self::LessThanOrEquals)
        } else if str == b">" {
            Some(Self::GreaterThan)
        } else if str == b">=" {
            Some(Self::GreaterThanOrEquals)
        } else {
            None
        }
    }

    pub fn matches(&self, value: u32, masked_threshold: u32) -> bool {
        match self {
            ThresholdOperation::Equals => value == masked_threshold,
            ThresholdOperation::NotEquals => value != masked_threshold,
            ThresholdOperation::LessThan => value < masked_threshold,
            ThresholdOperation::LessThanOrEquals => value <= masked_threshold,
            ThresholdOperation::GreaterThan => value > masked_threshold,
            ThresholdOperation::GreaterThanOrEquals => value >= masked_threshold,
        }
    }
}
