use keeshond::renderer::{SpriteSlice, Sheet};
use keeshond::util::BakedRect;

use num::Float;
use toodee::TooDeeOps;

pub struct BoxFitResult<T : Float>
{
    pub x : T,
    pub y : T,
    pub width : T,
    pub height : T,
    pub scale : T
}

pub fn box_fit<T : Float>(base_width : T, base_height : T, parent_width : T, parent_height : T) -> BoxFitResult<T>
{
    let mut result = BoxFitResult::<T>
    {
        x : T::from(0.0).unwrap(),
        y : T::from(0.0).unwrap(),
        width : parent_width,
        height : parent_height,
        scale : T::from(1.0).unwrap(),
    };

    let base_ratio = base_width / base_height;
    let ratio = parent_width / parent_height;

    if ratio > base_ratio
    {
        // Wider than base resolution
        result.scale = parent_height / base_height;
        result.width = (parent_height * base_ratio) as T;
        result.x = ((parent_width) - result.width) / T::from(2.0).unwrap();
    }
    else if ratio < base_ratio
    {
        // Taller than base resolution
        result.scale = parent_width / base_width;
        result.height = (parent_width / base_ratio) as T;
        result.y = ((parent_height) - result.height) / T::from(2.0).unwrap();
    }

    result
}

pub fn color_to_border_color(color : [f32; 4]) -> [f32; 4]
{
    if color[0] + color[1] + color[2] > 1.5
    {
        return [0.0, 0.0, 0.0, 1.0]
    }

    [1.0, 1.0, 1.0, 1.0]
}

pub fn slice_to_uv(sheet : &Sheet, slice : &SpriteSlice) -> BakedRect<f32>
{
    let base_width = sheet.width() as f32;
    let base_height = sheet.height() as f32;

    BakedRect::<f32>
    {
        x1 : slice.texture_x / base_width,
        y1 : slice.texture_y / base_height,
        x2 : (slice.texture_x + slice.texture_w) / base_width,
        y2 : (slice.texture_y + slice.texture_h) / base_height
    }
}

pub fn option_bool_to_double_bool(option_bool : Option<bool>) -> (bool, bool)
{
    if let Some(some_bool) = option_bool
    {
        return (true, some_bool);
    }

    (false, false)
}

pub fn double_bool_to_option_bool(is_some : bool, inner_value : bool) -> Option<bool>
{
    if !is_some
    {
        return None;
    }

    Some(inner_value)
}

#[derive(Debug)]
struct FloodFillInfo
{
    min_x : usize,
    max_x : usize,
    y : usize,
    down : bool,
    extend_left : bool,
    extend_right : bool
}

// Modified from http://will.thimbleby.net/scanline-flood-fill/
pub fn flood_fill<A, T, S>(target : &mut toodee::TooDee<A>, x : usize, y : usize, diagonal : bool, mut test_pixel : T, mut set_pixel : S)
    where T : FnMut(&mut toodee::TooDee<A>, usize, usize) -> bool, S : FnMut(&mut toodee::TooDee<A>, usize, usize)
{
    let width = target.num_cols();
    let height = target.num_rows();
    let mut ranges = Vec::new();

    if !test_pixel(target, x, y)
    {
        return;
    }

    ranges.push(FloodFillInfo
    {
        min_x: x,
        max_x: x,
        y,
        down: false,
        extend_left: true,
        extend_right: true
    });

    while !ranges.is_empty()
    {
        let mut range = ranges.pop().unwrap();

        range.min_x = usize::min(usize::max(0, range.min_x), width - 1);
        range.max_x = usize::max(usize::min(usize::max(0, range.max_x), width - 1), range.min_x);

        for range_x in range.min_x..range.max_x + 1
        {
            set_pixel(target, range_x, range.y);
        }

        let mut extended_min_x = range.min_x;

        if range.extend_left
        {
            while extended_min_x > 0 && test_pixel(target, extended_min_x - 1, range.y)
            {
                extended_min_x -= 1;
                set_pixel(target, extended_min_x, range.y);
            }
        }

        let mut extended_max_x = range.max_x;

        if range.extend_right
        {
            while extended_max_x < width - 1 && test_pixel(target, extended_max_x + 1, range.y)
            {
                extended_max_x += 1;
                set_pixel(target, extended_max_x, range.y);
            }
        }

        if diagonal
        {
            if extended_min_x > 0 { extended_min_x -= 1; }
            if extended_max_x < width - 1 { extended_max_x += 1; }
        }

        let mut add_range = |new_y, down|
            {
                let mut range_active = false;
                let mut range_count : usize = 0;
                let mut range_start : usize = 0;

                for x in extended_min_x..extended_max_x + 1
                {
                    if test_pixel(target, x, new_y)
                    {
                        if !range_active
                        {
                            range_start = x;
                            range_active = true;
                        }
                    }
                    else
                    {
                        if range_active
                        {
                            ranges.push(FloodFillInfo
                            {
                                min_x: range_start,
                                max_x: x - 1,
                                y: new_y,
                                down,
                                extend_left: range_count == 0,
                                extend_right: false
                            });

                            range_active = false;
                            range_count += 1;
                        }
                    }
                }

                if range_active
                {
                    ranges.push(FloodFillInfo
                    {
                        min_x: range_start,
                        max_x: extended_max_x,
                        y : new_y,
                        down,
                        extend_left: range_count == 0,
                        extend_right: false
                    });

                    range_count += 1;
                }

                if range_count > 0
                {
                    if let Some(range) = ranges.last_mut()
                    {
                        range.extend_right = true;
                    }
                }
            };

        if range.y > 0
        {
            add_range(range.y - 1, false);
        }
        if range.y < height - 1
        {
            add_range(range.y + 1, true);
        }
    }
}
