/*
   Copyright (C) 2009 Red Hat, Inc.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/

#include "common.h"
#include "screen.h"
#include "application.h"
#include "screen_layer.h"
#include "utils.h"
#include "debug.h"
#include "monitor.h"
#include "red_pixmap_sw.h"
#include "resource.h"
#include "icon.h"

class UpdateEvent: public Event {
public:
    UpdateEvent(int screen) : _screen (screen) {}

    virtual void response(AbstractProcessLoop& events_loop)
    {
        Application* app = static_cast<Application*>(events_loop.get_owner());
        RedScreen* screen = app->find_screen(_screen);
        if (screen) {
            screen->update();
        }
    }

private:
    int _screen;
};

class LayerChangedEvent: public Event {
public:
    LayerChangedEvent (int screen) : _screen (screen) {}

    virtual void response(AbstractProcessLoop& events_loop)
    {
        Application* app = static_cast<Application*>(events_loop.get_owner());
        RedScreen* screen = app->find_screen(_screen);
        if (screen) {
            Lock lock(screen->_layer_changed_lock);
            screen->_active_layer_change_event = false;
            lock.unlock();
            if (screen->_pointer_on_screen) {
                screen->update_pointer_layer();
            }
        }
    }

private:
    int _screen;
};


void UpdateTimer::response(AbstractProcessLoop& events_loop)
{
    _screen->periodic_update();
}

RedScreen::RedScreen(Application& owner, int id, const std::string& name, int width, int height)
    : _owner (owner)
    , _id (id)
    , _refs (1)
    , _window (*this)
    , _active (false)
    , _full_screen (false)
    , _out_of_sync (false)
    , _frame_area (false)
    , _periodic_update (false)
    , _key_interception (false)
    , _update_by_timer (true)
    , _size_locked (false)
    , _menu_needs_update (false)
    , _forec_update_timer (0)
    , _update_timer (new UpdateTimer(this))
    , _composit_area (NULL)
    , _update_mark (1)
    , _monitor (NULL)
    , _default_cursor (NULL)
    , _inactive_cursor (NULL)
    , _pixel_format_index (0)
    , _update_interrupt_trigger (NULL)
    , _pointer_layer (NULL)
    , _mouse_captured (false)
    , _active_layer_change_event (false)
    , _pointer_on_screen (false)
{
    region_init(&_dirty_region);
    set_name(name);
    _size.x = width;
    _size.y = height;
    _origin.x = _origin.y = 0;
    create_composit_area();
    _window.resize(_size.x, _size.y);
    save_position();
    if ((_default_cursor = Platform::create_default_cursor()) == NULL) {
        THROW("create default cursor failed");
    }
    if ((_inactive_cursor = Platform::create_inactive_cursor()) == NULL) {
        THROW("create inactive cursor failed");
    }
    _window.set_cursor(_default_cursor);
    update_menu();
    AutoRef<Icon> icon(Platform::load_icon(RED_ICON_RES_ID));
    _window.set_icon(*icon);
    _window.start_key_interception();
}

RedScreen::~RedScreen()
{
    bool captured = is_mouse_captured();
    _window.stop_key_interception();
    relase_mouse();
    destroy_composit_area();
    _owner.deactivate_interval_timer(*_update_timer);
    _owner.on_screen_destroyed(_id, captured);
    region_destroy(&_dirty_region);
    if (_default_cursor) {
        _default_cursor->unref();
    }

    if (_inactive_cursor) {
        _inactive_cursor->unref();
    }
}

void RedScreen::show(bool activate, RedScreen* pos)
{
    _window.position_after((pos) ? &pos->_window : NULL);
    show();
    if (activate) {
        _window.activate();
    }
}

RedScreen* RedScreen::ref()
{
    ++_refs;
    return this;
}

void RedScreen::unref()
{
    if (!--_refs) {
        delete this;
    }
}

void RedScreen::destroy_composit_area()
{
    if (_composit_area) {
        delete _composit_area;
        _composit_area = NULL;
    }
}

void RedScreen::create_composit_area()
{
    destroy_composit_area();
    _composit_area = new RedPixmapSw(_size.x, _size.y, _window.get_format(),
                                     false, &_window);
}

void RedScreen::adjust_window_rect(int x, int y)
{
    _window.move_and_resize(x, y, _size.x, _size.y);
}

void RedScreen::resize(int width, int height)
{
    RecurciveLock lock(_update_lock);
    _size.x = width;
    _size.y = height;
    create_composit_area();
    if (_full_screen) {
        __show_full_screen();
    } else {
        bool cuptur = is_mouse_captured();
        if (cuptur) {
            relase_mouse();
        }
        _window.resize(_size.x, _size.y);
        if (_active && cuptur) {
            capture_mouse();
        }
    }
    notify_new_size();
}

void RedScreen::lock_size()
{
    ASSERT(!_size_locked);
    _size_locked = true;
}

void RedScreen::unlock_size()
{
    _size_locked = false;
    _owner.on_screen_unlocked(*this);
}

void RedScreen::set_name(const std::string& name)
{
    if (!name.empty()) {
        string_printf(_name, name.c_str(), _id);
    }
    _window.set_title(_name);
}

void RedScreen::on_layer_changed(ScreenLayer& layer)
{
    Lock lock(_layer_changed_lock);
    if (_active_layer_change_event) {
        return;
    }
    _active_layer_change_event = true;
    AutoRef<LayerChangedEvent> change_event(new LayerChangedEvent(_id));
    _owner.push_event(*change_event);
}

void RedScreen::attach_layer(ScreenLayer& layer)
{
    RecurciveLock lock(_update_lock);
    int order = layer.z_order();

    if ((int)_layes.size() < order + 1) {
        _layes.resize(order + 1);
    }
    if (_layes[order]) {
        THROW("layer in use");
    }
    layer.set_screen(this);
    _layes[order] = &layer;
    ref();
    lock.unlock();
    layer.invalidate();
    if (_pointer_on_screen) {
        update_pointer_layer();
    }
}

void RedScreen::detach_layer(ScreenLayer& layer)
{
    bool need_pointer_layer_update = false;
    if (_pointer_layer == &layer) {
        _pointer_layer->on_pointer_leave();
        _pointer_layer = NULL;
        need_pointer_layer_update = true;
    }

    RecurciveLock lock(_update_lock);

    int order = layer.z_order();

    if ((int)_layes.size() < order + 1 || _layes[order] != &layer) {
        THROW("not found");
    }
    QRegion layer_area;
    region_clone(&layer_area, &layer.area());
    _layes[order]->set_screen(NULL);
    _layes[order] = NULL;
    lock.unlock();
    invalidate(layer_area);
    region_destroy(&layer_area);
    unref();
    if (need_pointer_layer_update && !update_pointer_layer()) {
        _window.set_cursor(_inactive_cursor);
    }
}

void RedScreen::composit_to_screen(RedDrawable& win_dc, const QRegion& region)
{
    pixman_box32_t *rects;
    int num_rects;

    rects = pixman_region32_rectangles((pixman_region32_t *)&region, &num_rects);
    for (int i = 0; i < num_rects; i++) {
        SpiceRect r;

        r.left = rects[i].x1;
        r.top = rects[i].y1;
        r.right = rects[i].x2;
        r.bottom = rects[i].y2;
        win_dc.copy_pixels(*_composit_area, r.left, r.top, r);
    }
}

void RedScreen::notify_new_size()
{
    for (int i = 0; i < (int)_layes.size(); i++) {
        if (!_layes[i]) {
            continue;
        }
        _layes[i]->on_size_changed();
    }
}

inline void RedScreen::begin_update(QRegion& direct_rgn, QRegion& composit_rgn,
                                    QRegion& frame_rgn)
{
    region_init(&composit_rgn);
    RecurciveLock lock(_update_lock);
    region_clone(&direct_rgn, &_dirty_region);
    region_clear(&_dirty_region);
    _update_mark++;
    lock.unlock();

    QRegion rect_rgn;
    SpiceRect r;
    r.top = r.left = 0;
    r.right = _size.x;
    r.bottom = _size.y;
    region_init(&rect_rgn);
    region_add(&rect_rgn, &r);

    if (_frame_area) {
        region_clone(&frame_rgn, &direct_rgn);
        region_exclude(&frame_rgn, &rect_rgn);
    }
    region_and(&direct_rgn, &rect_rgn);
    region_destroy(&rect_rgn);

    for (int i = _layes.size() - 1; i >= 0; i--) {
        ScreenLayer* layer;

        if (!(layer = _layes[i])) {
            continue;
        }
        layer->begin_update(direct_rgn, composit_rgn);
    }
}

inline void RedScreen::update_done()
{
    for (unsigned int i = 0; i < _layes.size(); i++) {
        ScreenLayer* layer;

        if (!(layer = _layes[i])) {
            continue;
        }
        layer->on_update_completion(_update_mark - 1);
    }
}

inline void RedScreen::update_composit(QRegion& composit_rgn)
{
    erase_background(*_composit_area, composit_rgn);
    for (int i = 0; i < (int)_layes.size(); i++) {
        ScreenLayer* layer;

        if (!(layer = _layes[i])) {
            continue;
        }
        QRegion& dest_region = layer->composit_area();
        region_or(&composit_rgn, &dest_region);
        layer->copy_pixels(dest_region, *_composit_area);
    }
}

inline void RedScreen::draw_direct(RedDrawable& win_dc, QRegion& direct_rgn, QRegion& composit_rgn,
                                   QRegion& frame_rgn)
{
    erase_background(win_dc, direct_rgn);

    if (_frame_area) {
        erase_background(win_dc, frame_rgn);
        region_destroy(&frame_rgn);
    }

    for (int i = 0; i < (int)_layes.size(); i++) {
        ScreenLayer* layer;

        if (!(layer = _layes[i])) {
            continue;
        }
        QRegion& dest_region = layer->direct_area();
        layer->copy_pixels(dest_region, win_dc);
    }
}

void RedScreen::periodic_update()
{
    bool need_update;
    RecurciveLock lock(_update_lock);
    if (is_dirty()) {
        need_update = true;
    } else {
        if (!_forec_update_timer) {
            _owner.deactivate_interval_timer(*_update_timer);
            _periodic_update = false;
        }
        need_update = false;
    }
    lock.unlock();

    if (need_update) {
        if (update_by_interrupt()) {
            interrupt_update();
        } else {
            update();
        }
    }
}

void RedScreen::activate_timer()
{
    RecurciveLock lock(_update_lock);
    if (_periodic_update) {
        return;
    }
    _periodic_update = true;
    lock.unlock();
    _owner.activate_interval_timer(*_update_timer, 1000 / 30);
}

void RedScreen::update()
{
    if (is_out_of_sync()) {
        return;
    }

    QRegion direct_rgn;
    QRegion composit_rgn;
    QRegion frame_rgn;

    begin_update(direct_rgn, composit_rgn, frame_rgn);
    update_composit(composit_rgn);
    draw_direct(_window, direct_rgn, composit_rgn, frame_rgn);
    composit_to_screen(_window, composit_rgn);
    update_done();
    region_destroy(&direct_rgn);
    region_destroy(&composit_rgn);

    if (_update_by_timer) {
        activate_timer();
    }
}

bool RedScreen::_invalidate(const SpiceRect& rect, bool urgent, uint64_t& update_mark)
{
    RecurciveLock lock(_update_lock);
    bool update_triger = !is_dirty() && (urgent || !_periodic_update);
    region_add(&_dirty_region, &rect);
    update_mark = _update_mark;
    return update_triger;
}

uint64_t RedScreen::invalidate(const SpiceRect& rect, bool urgent)
{
    uint64_t update_mark;
    if (_invalidate(rect, urgent, update_mark)) {
        if (!urgent && _update_by_timer) {
            activate_timer();
        } else {
            if (update_by_interrupt()) {
                interrupt_update();
            } else {
                AutoRef<UpdateEvent> update_event(new UpdateEvent(_id));
                _owner.push_event(*update_event);
            }
        }
    }
    return update_mark;
}

void RedScreen::invalidate(const QRegion &region)
{
    pixman_box32_t *rects, *end;
    int num_rects;

    rects = pixman_region32_rectangles((pixman_region32_t *)&region, &num_rects);
    end = rects + num_rects;

    while (rects != end) {
        SpiceRect r;

        r.left = rects->x1;
        r.top = rects->y1;
        r.right = rects->x2;
        r.bottom = rects->y2;
        rects++;

        invalidate(r, false);
    }
}

inline void RedScreen::erase_background(RedDrawable& dc, const QRegion& composit_rgn)
{
    pixman_box32_t *rects;
    int num_rects;

    rects = pixman_region32_rectangles((pixman_region32_t *)&composit_rgn, &num_rects);
    for (int i = 0; i < num_rects; i++) {
        SpiceRect r;

        r.left = rects[i].x1;
        r.top = rects[i].y1;
        r.right = rects[i].x2;
        r.bottom = rects[i].y2;

        dc.fill_rect(r, 0);
    }
}

void RedScreen::reset_mouse_pos()
{
    _window.set_mouse_position(_mouse_anchor_point.x, _mouse_anchor_point.y);
}

void RedScreen::capture_mouse()
{
    if (_mouse_captured || !_window.get_mouse_anchor_point(_mouse_anchor_point)) {
        return;
    }

    if (_pointer_layer) {
        _pointer_layer->on_pointer_leave();
        _pointer_layer = NULL;
    }
    _pointer_on_screen = false;
    _mouse_captured = true;
    _window.hide_cursor();
    reset_mouse_pos();
    _window.cupture_mouse();
}

void RedScreen::relase_mouse()
{
    if (!_mouse_captured) {
        return;
    }
    _mouse_captured = false;
    _window.release_mouse();
    update_pointer_layer();
}

void RedScreen::set_cursor(LocalCursor* cursor)
{
    if (_mouse_captured) {
        return;
    }

    _window.set_cursor(cursor);
}

void RedScreen::hide_cursor()
{
    _window.hide_cursor();
}

ScreenLayer* RedScreen::find_pointer_layer()
{
    for (int i = _layes.size() - 1; i >= 0; i--) {
        ScreenLayer* layer;

        if (!(layer = _layes[i])) {
            continue;
        }

        if (layer->pointer_test(_pointer_pos.x, _pointer_pos.y)) {
            return layer;
        }
    }
    return NULL;
}

bool RedScreen::update_pointer_layer()
{
    ASSERT(!_mouse_captured);

    ScreenLayer* now = find_pointer_layer();

    if (now == _pointer_layer) {
        return false;
    }

    if (_pointer_layer) {
        _pointer_layer->on_pointer_leave();
    }

    _pointer_layer = find_pointer_layer();

    if (_pointer_layer) {
        _pointer_layer->on_pointer_enter(_pointer_pos.x, _pointer_pos.y, _mouse_botton_state);
    } else {
        set_cursor(_inactive_cursor);
    }

    return true;
}

void RedScreen::on_pointer_enter(int x, int y, unsigned int buttons_state)
{
    if (_mouse_captured) {
        return;
    }

    _pointer_on_screen = true;
    _pointer_pos.x = x;
    _pointer_pos.y = y;
    _mouse_botton_state = buttons_state;

    ScreenLayer* layer = find_pointer_layer();
    if (!layer) {
        set_cursor(_inactive_cursor);
        return;
    }

    _pointer_layer = layer;
    _pointer_layer->on_pointer_enter(_pointer_pos.x, _pointer_pos.y, buttons_state);

    if (_full_screen) {
        /* allowing enterance to key interception mode without
           requiring the user to press the window
        */
        activate();
    }
}

void RedScreen::on_mouse_motion(int x, int y, unsigned int buttons_state)
{
    if (x != _mouse_anchor_point.x || y != _mouse_anchor_point.y) {
        _owner.on_mouse_motion(x - _mouse_anchor_point.x,
                               y - _mouse_anchor_point.y,
                               buttons_state);
        reset_mouse_pos();
    }
}

void RedScreen::on_pointer_motion(int x, int y, unsigned int buttons_state)
{
    if (_mouse_captured) {
        on_mouse_motion(x, y, buttons_state);
        return;
    }

    _pointer_pos.x = x;
    _pointer_pos.y = y;
    _mouse_botton_state = buttons_state;

    if (update_pointer_layer() || !_pointer_layer) {
        return;
    }

    _pointer_layer->on_pointer_motion(x, y, buttons_state);
}

void RedScreen::on_mouse_button_press(SpiceMouseButton button, unsigned int buttons_state)
{
    if (_mouse_captured) {
        _owner.on_mouse_down(button, buttons_state);
        return;
    }

    if (!_pointer_layer) {
        return;
    }

    _pointer_layer->on_mouse_button_press(button, buttons_state);
}

void RedScreen::on_mouse_button_release(SpiceMouseButton button, unsigned int buttons_state)
{
    if (_mouse_captured) {
        _owner.on_mouse_up(button, buttons_state);
        return;
    }

    if (!_pointer_layer) {
        return;
    }
    _pointer_layer->on_mouse_button_release(button, buttons_state);
}

void RedScreen::on_pointer_leave()
{
//    ASSERT(!_mouse_captured);

    if (_pointer_layer) {
        _pointer_layer->on_pointer_leave();
        _pointer_layer = NULL;
    }
    _pointer_on_screen = false;
}

void RedScreen::on_key_press(RedKey key)
{
    _owner.on_key_down(key);
}

void RedScreen::on_key_release(RedKey key)
{
    _owner.on_key_up(key);
}

void RedScreen::on_char(uint32_t ch)
{
    _owner.on_char(ch);
}

void RedScreen::on_deactivate()
{
    relase_mouse();
    _active = false;
    _owner.on_deactivate_screen(this);
}

void RedScreen::on_activate()
{
    _active = true;
    _owner.on_activate_screen(this);
}

void RedScreen::on_start_key_interception()
{
    _key_interception = true;
    _owner.on_start_screen_key_interception(this);
}

void RedScreen::on_stop_key_interception()
{
    _key_interception = false;
    _owner.on_stop_screen_key_interception(this);
}

void RedScreen::enter_modal_loop()
{
    _forec_update_timer++;
    activate_timer();
}

void RedScreen::exit_modal_loop()
{
    ASSERT(_forec_update_timer > 0)
    _forec_update_timer--;
}

void RedScreen::pre_migrate()
{
    for (int i = 0; i < (int)_layes.size(); i++) {
        if (!_layes[i]) {
            continue;
        }
        _layes[i]->pre_migrate();
    }
}

void RedScreen::post_migrate()
{
    for (int i = 0; i < (int)_layes.size(); i++) {
        if (!_layes[i]) {
            continue;
        }
        _layes[i]->post_migrate();
    }
}

void RedScreen::exit_full_screen()
{
    if (!_full_screen) {
        return;
    }
    RecurciveLock lock(_update_lock);
    _window.hide();
    region_clear(&_dirty_region);
    _window.set_type(RedWindow::TYPE_NORMAL);
    adjust_window_rect(_save_pos.x, _save_pos.y);
    _origin.x = _origin.y = 0;
    _window.set_origin(0, 0);
    show();
    if (_menu_needs_update) {
        update_menu();
    }
    _full_screen = false;
    _out_of_sync = false;
    _frame_area = false;
}

void RedScreen::save_position()
{
    _save_pos = _window.get_position();
}

void RedScreen::__show_full_screen()
{
    if (!_monitor) {
        hide();
        return;
    }
    SpicePoint position = _monitor->get_position();
    SpicePoint monitor_size = _monitor->get_size();
    _frame_area = false;
    region_clear(&_dirty_region);
    _window.set_type(RedWindow::TYPE_FULLSCREEN);
    _window.move_and_resize(position.x, position.y, monitor_size.x, monitor_size.y);

    if (!(_out_of_sync = _monitor->is_out_of_sync())) {
        ASSERT(monitor_size.x >= _size.x);
        ASSERT(monitor_size.y >= _size.y);
        _origin.x = (monitor_size.x - _size.x) / 2;
        _origin.y = (monitor_size.y - _size.y) / 2;
        _frame_area = monitor_size.x != _size.x || monitor_size.y != _size.y;
    } else {
        _origin.x = _origin.y = 0;
    }
    _window.set_origin(_origin.x, _origin.y);
    show();
}

void RedScreen::show_full_screen()
{
    if (_full_screen) {
        return;
    }
    RecurciveLock lock(_update_lock);
#ifndef WIN32
    /* performing hide during resolution changes resulted in
       missing WM_KEYUP events */
    hide();
#endif
    save_position();
    _full_screen = true;
    __show_full_screen();
}

void RedScreen::minimize()
{
    _window.minimize();
}

void RedScreen::position_full_screen(const SpicePoint& position)
{
    if (!_full_screen) {
        return;
    }

    _window.move(position.x, position.y);
}

void RedScreen::hide()
{
    _window.hide();
}

void RedScreen::show()
{
    RecurciveLock lock(_update_lock);
    _window.show(_monitor ? _monitor->get_screen_id() : 0);
}

void RedScreen::activate()
{
    _window.activate();
}

void RedScreen::external_show()
{
    DBG(0, "Entry");
    _window.external_show();
}

void RedScreen::update_menu()
{
    AutoRef<Menu> menu(_owner.get_app_menu());
    int ret = _window.set_menu(*menu);
    _menu_needs_update = (ret != 0); /* try again if menu update failed */
}

void RedScreen::on_exposed_rect(const SpiceRect& area)
{
    if (is_out_of_sync()) {
        _window.fill_rect(area, rgb32_make(0xff, 0xff, 0xff));
        return;
    }
    invalidate(area, false);
}

int RedScreen::get_screen_id()
{
    return _monitor ? _monitor->get_screen_id() : 0;
}

#ifdef USE_OGL
void RedScreen::untouch_context()
{
    _window.untouch_context();
}

bool RedScreen::need_recreate_context_gl()
{
    if (_full_screen) {
        return true;
    }
    return false;
}

#endif

void RedScreen::set_update_interrupt_trigger(EventSources::Trigger *trigger)
{
    _update_interrupt_trigger = trigger;
}

bool RedScreen::update_by_interrupt()
{
    return _update_interrupt_trigger != NULL;
}

void RedScreen::interrupt_update()
{
    _update_interrupt_trigger->trigger();
}

#ifdef USE_OGL
void RedScreen::set_type_gl()
{
    _window.set_type_gl();
}

void RedScreen::unset_type_gl()
{
    _window.unset_type_gl();
}
#endif // USE_OGL

