// Copyright (C) 2005  Davis E. King (davis@dlib.net), Keita Mochizuki
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_DRAWABLe_CPP_
#define DLIB_DRAWABLe_CPP_

#include "drawable.h"

#include <algorithm>
#include <iostream>

namespace dlib
{

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// -----------  drawable_window object  ------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    rgb_pixel drawable_window::
    background_color (
    ) const
    {
        auto_mutex M(wm);
        return bg_color;
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    set_background_color (
        unsigned long red_,
        unsigned long green_,
        unsigned long blue_
    )
    {
        wm.lock();
        bg_color.red = red_;
        bg_color.green = green_;
        bg_color.blue = blue_;
        wm.unlock();
        // now repaint the window
        unsigned long width,height;
        get_size(width,height);
        rectangle rect(0,0,width-1,height-1);
        invalidate_rectangle(rect);
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    paint (
        const canvas& c
    )
    {
        ++event_id;
        c.fill(bg_color.red,bg_color.green,bg_color.blue);

        widgets.reset();
        while (widgets.move_next())
        {
            widgets.element().value().reset();
            while (widgets.element().value().move_next())
            {
                // only dispatch a draw() call if this widget isn't hidden
                if (widgets.element().value().element()->hidden == false &&
                    widgets.element().value().element()->event_id != event_id)
                {
                    widgets.element().value().element()->event_id = event_id;
                    widgets.element().value().element()->draw(c);
                }
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_user_event (
        void* p,
        int i
    )
    {
        drawable* d = static_cast<drawable*>(p);
        if (widget_set.is_member(d))
        {
            d->on_user_event(i);
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_window_moved(
    )
    {
        ++event_id;
        window_moved.reset();
        while (window_moved.move_next())
        {
            if (window_moved.element()->event_id != event_id)
            {
                window_moved.element()->event_id = event_id;
                window_moved.element()->on_window_moved();
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_window_resized(
    )
    {
        ++event_id;
        window_resized.reset();
        while (window_resized.move_next())
        {
            if (window_resized.element()->event_id != event_id)
            {
                window_resized.element()->event_id = event_id;
                window_resized.element()->on_window_resized();
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_keydown (
        unsigned long key,
        bool is_printable,
        unsigned long state
    )
    {
        ++event_id;
        keyboard.reset();
        while (keyboard.move_next())
        {
            if (keyboard.element()->event_id != event_id)
            {
                keyboard.element()->event_id = event_id;
                keyboard.element()->on_keydown(key,is_printable,state);
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_focus_gained (
    )
    {
        ++event_id;
        focus.reset();
        while (focus.move_next())
        {
            if (focus.element()->event_id != event_id)
            {
                focus.element()->event_id = event_id;
                focus.element()->on_focus_gained();
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_focus_lost (
    )
    {
        ++event_id;
        focus.reset();
        while (focus.move_next())
        {
            if (focus.element()->event_id != event_id)
            {
                focus.element()->event_id = event_id;
                focus.element()->on_focus_lost();
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_mouse_down (
        unsigned long btn,
        unsigned long state,
        long x,
        long y,
        bool is_double_click
    )
    {
        lastx = x;
        lasty = y;

        ++event_id;
        mouse_click.reset();
        while (mouse_click.move_next())
        {
            if (mouse_click.element()->event_id != event_id)
            {
                mouse_click.element()->event_id = event_id;
                mouse_click.element()->on_mouse_down(btn,state,x,y,is_double_click);
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_mouse_up (
        unsigned long btn,
        unsigned long state,
        long x,
        long y
    )
    {
        lastx = x;
        lasty = y;

        ++event_id;
        mouse_click.reset();
        while (mouse_click.move_next())
        {
            if (mouse_click.element()->event_id != event_id)
            {
                mouse_click.element()->event_id = event_id;
                mouse_click.element()->on_mouse_up(btn,state,x,y);
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_mouse_move (
        unsigned long state,
        long x,
        long y
    )
    {
        lastx = x;
        lasty = y;

        ++event_id;
        mouse_move.reset();
        while (mouse_move.move_next())
        {
            if (mouse_move.element()->event_id != event_id)
            {
                mouse_move.element()->event_id = event_id;
                mouse_move.element()->on_mouse_move(state,x,y);
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_mouse_leave (
    )
    {
        lastx = -1;
        lasty = -1;

        ++event_id;
        mouse_move.reset();
        while (mouse_move.move_next())
        {
            if (mouse_move.element()->event_id != event_id)
            {
                mouse_move.element()->event_id = event_id;
                mouse_move.element()->on_mouse_leave();
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_mouse_enter (
    )
    {
        ++event_id;
        mouse_move.reset();
        while (mouse_move.move_next())
        {
            if (mouse_move.element()->event_id != event_id)
            {
                mouse_move.element()->event_id = event_id;
                mouse_move.element()->on_mouse_enter();
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_wheel_up (
        unsigned long state
    )
    {
        ++event_id;
        mouse_wheel.reset();
        while (mouse_wheel.move_next())
        {
            if (mouse_wheel.element()->event_id != event_id)
            {
                mouse_wheel.element()->event_id = event_id;
                mouse_wheel.element()->on_wheel_up(state);
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_wheel_down (
        unsigned long state
    )
    {
        ++event_id;
        mouse_wheel.reset();
        while (mouse_wheel.move_next())
        {
            if (mouse_wheel.element()->event_id != event_id)
            {
                mouse_wheel.element()->event_id = event_id;
                mouse_wheel.element()->on_wheel_down(state);
            }
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable_window::
    on_string_put (
        const std::wstring &str
    )
    {
        ++event_id;
        string_put.reset();
        while (string_put.move_next())
        {
            if (string_put.element()->event_id != event_id)
            {
                string_put.element()->event_id = event_id;
                string_put.element()->on_string_put(str);
            }
        }
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// -----------  drawable object  ----------------------------------------------------------
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    void drawable::
    enable_events (
    )
    {
        auto_mutex M(m);
        if (enabled_events == false)
        {
            enabled_events = true;
            drawable* temp = this;
            long zo = z_order_value;

            drawable_window::set_of_drawables* sod = parent.widgets[zo];
            if (sod == 0)
            {
                // this drawable is the first widget at this z order so we need
                // to make its containing set
                drawable_window::set_of_drawables s;
                s.add(temp);
                parent.widgets.add(zo,s);
            }
            else
            {
                sod->add(temp);
            }

            temp = this;
            parent.widget_set.add(temp);

            if (events & MOUSE_MOVE)
            {
                temp = this;
                parent.mouse_move.add(temp);
            }
            if (events & MOUSE_CLICK)
            {
                temp = this;
                parent.mouse_click.add(temp);
            }
            if (events & MOUSE_WHEEL)
            {
                temp = this;
                parent.mouse_wheel.add(temp);
            }
            if (events & WINDOW_RESIZED)
            {
                temp = this;
                parent.window_resized.add(temp);
            }
            if (events & KEYBOARD_EVENTS)
            {
                temp = this;
                parent.keyboard.add(temp);
            }
            if (events & FOCUS_EVENTS)
            {
                temp = this;
                parent.focus.add(temp);
            }
            if (events & WINDOW_MOVED)
            {
                temp = this;
                parent.window_moved.add(temp);
            }
            if (events & STRING_PUT)
            {
                temp = this;
                parent.string_put.add(temp);
            }
            parent.invalidate_rectangle(rect);
        }
    }

// ----------------------------------------------------------------------------------------

    void drawable::
    set_z_order (
        long order
    )
    {
        auto_mutex M(m);
        if (order != z_order_value && enabled_events)
        {
            // first remove this drawable from widgets
            drawable_window::set_of_drawables* sod = parent.widgets[z_order_value];
            drawable* junk;
            sod->remove(this,junk);

            // if there are no more drawables at this z order then destroy the
            // set for this order
            if (sod->size() == 0)
                parent.widgets.destroy(z_order_value);

            // now add this drawable to its new z order
            sod = parent.widgets[order];                
            if (sod == 0)
            {
                // this drawable is the first widget at this z order so we need
                // to make its containing set
                drawable_window::set_of_drawables s, x;
                s.add(junk);
                long temp_order = order;
                parent.widgets.add(temp_order,s);
            }
            else
            {
                sod->add(junk);
            }
            parent.invalidate_rectangle(rect);

        }
        z_order_value = order;
    }

// ----------------------------------------------------------------------------------------

    void drawable::
    disable_events (
    )
    {
        auto_mutex M(m);
        if (enabled_events)
        {
            enabled_events = false;
            // first remove this drawable from widgets
            drawable_window::set_of_drawables* sod = parent.widgets[z_order_value];
            drawable* junk;
            sod->remove(this,junk);

            // if there are no more drawables at this z order then destroy the
            // set for this order
            if (sod->size() == 0)
                parent.widgets.destroy(z_order_value);

            parent.widget_set.remove(this,junk);

            // now unregister this drawable from all the events it has registered for.
            if (events & MOUSE_MOVE)
                parent.mouse_move.remove(this,junk);
            if (events & MOUSE_CLICK)
                parent.mouse_click.remove(this,junk);
            if (events & MOUSE_WHEEL)
                parent.mouse_wheel.remove(this,junk);
            if (events & WINDOW_RESIZED)
                parent.window_resized.remove(this,junk);
            if (events & KEYBOARD_EVENTS)
                parent.keyboard.remove(this,junk);
            if (events & FOCUS_EVENTS)
                parent.focus.remove(this,junk);
            if (events & WINDOW_MOVED)
                parent.window_moved.remove(this,junk);
            if (events & STRING_PUT)
                parent.string_put.remove(this,junk);
        }
    }

// ----------------------------------------------------------------------------------------

    drawable::
    ~drawable (
    )
    {
        try
        {
            DLIB_ASSERT(events_are_enabled() == false,
                "\tdrawable::~drawable()"
                << "\n\tYou must disable events for drawable objects in their destructor by calling disable_events()."
                << "\n\tthis:     " << this
                );
        }
        catch (std::exception& e)
        {
            std::cerr << e.what() << std::endl;
            assert(false);
            abort();
        }
        disable_events();
    }

// ----------------------------------------------------------------------------------------

}

#endif // DLIB_DRAWABLe_CPP_

