/*
 * File Name: images_renderer.cpp
 */

/*
 * This file is part of uds-plugin-images.
 *
 * uds-plugin-images is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * uds-plugin-images 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Copyright (C) 2008 iRex Technologies B.V.
 * All rights reserved.
 */

#include <cassert>
#include <iostream>
#include "images_renderer.h"
#include "image_render_task.h"
#include "log.h"

namespace images
{

ImagesRenderer::ImagesRenderer(ImagesDocument * document)
: doc(document)
, render_thread()
, pages_cache()
, last_render_page(0)    
{
    LOGPRINTF("%p", document);

    render_thread.start();
    
    //  Pre-render policy
    set_prerender_policy(2, 2);
}

ImagesRenderer::~ImagesRenderer(void)
{
    LOGPRINTF("entry");

    render_thread.stop();
    // pages_cache.clear();
}

ImageRenderStatus ImagesRenderer::render(const std::string & anchor,
                                         const ImagePageAttrs & attrs,
                                         int ref_id,
                                         void * user_data)
{
    LOGPRINTF("%s, zoom[%f], rotation[%d], ref_id[%d]",
              anchor.c_str(), attrs.zoom, attrs.rotation, ref_id);
    
    // Return code.
    ImageRenderStatus ret = IMG_RENDER_OK;
    
    // Clear the old tasks.
    render_thread.clear_all();

    ImageRenderTask * task = 0;
    
    // Get the render result from 'pages cache'
    ImagePage * page = get_page(anchor, attrs);
    if (!page)
    {
        ret = can_render(anchor, attrs);
        if (ret == IMG_RENDER_OK)
        {
            // Prepend 'render task' to 'render_thread'
            task = new ImageRenderTask(anchor, attrs, ref_id, this, user_data);
            // Notes, The flag 'true' means aborting the current task.
            // But for images, the implementation of the task 
            // doesn't support aborting feature.
            render_thread.prepend_task(task, true);
        }
        else
        {
            // Notify renderer this page is not ready because of what. e.g OOM.
            notify_page_ready(0, ref_id, ret, user_data);
        }
    }
    else
    {
        WARNPRINTF("Page %s already in pages_cache!", anchor.c_str());

        // This requested page is already in 'pages_cache'.
        // Notify page is ready now.
        notify_page_ready(page, ref_id, IMG_RENDER_OK, user_data);
    }

    // Do pre_rendering.
    assert(doc);
    int page_num = doc->get_position(anchor);

    bool page_down = (last_render_page <= page_num) ? true : false;
    last_render_page = page_num;
    pre_render(page_num, attrs, page_down);
    
    return ret;
}

ImagePage * ImagesRenderer::get_page(const std::string & anchor,
                                     const ImagePageAttrs & attrs)
{
    LOGPRINTF("%s zoom[%f] rotation[%d]", 
              anchor.c_str(), attrs.zoom, attrs.rotation);

    size_t key = ImagePage::calc_key(anchor, 
                                     attrs.desired_width, 
                                     attrs.desired_height,
                                     attrs.zoom,
                                     attrs.rotation);
    
    ImagePage * page = pages_cache.get_page(key);
   
    LOGPRINTF("return %p", page);

    return page;
}

void ImagesRenderer::add_page(ImagePage * page)
{
    LOGPRINTF("%p", page);
    
    if (page)
    {
        pages_cache.add_page(page);
    }
}

// (1)Check whether the image is valid or not.
// (2)Check whether there is enough memory to render. 
ImageRenderStatus ImagesRenderer::can_render(const std::string & anchor, 
                                             const ImagePageAttrs & attrs)
{
    ImagePageAttrs tmp = attrs;
    bool is_valid_image = false;
    ImagesScanner::get_size(anchor, 
                            &tmp.original_width, 
                            &tmp.original_height);
    if ((tmp.original_width > 0) && (tmp.original_height > 0))
    {
        is_valid_image = true;
    }        

    // Check whether the image is valid or not.
    if (!is_valid_image)
    {
        WARNPRINTF("Invalid image page %s.", anchor.c_str());            
        return IMG_RENDER_INVALID;
    }
    // Check whether there is enough memory to render.
    int size = ImagePage::length(anchor, tmp);
    if (!pages_cache.make_enough_memory(size))
    {
        WARNPRINTF("No enough memory to render page %s", anchor.c_str());
        return IMG_RENDER_OOM;
    }
    
    return IMG_RENDER_OK;
    
}

void ImagesRenderer::pre_render(int page_num, 
                                const ImagePageAttrs & attrs,
                                bool page_down)
{
    LOGPRINTF("%d page_down = %d", page_num, page_down);

    // Append 'pre-render task' to 'render_thread' if neccessary.

    // According to the direction of turning page, 
    // render the previous pages or the next pages firstly.
    if (page_down)
    {
        LOGPRINTF("Render the next pages firstly.");
        render_next_pages(page_num, attrs);
        render_prev_pages(page_num, attrs);
    }
    else
    {
        LOGPRINTF("Render the previous pages firstly.");
        render_prev_pages(page_num, attrs);
        render_next_pages(page_num, attrs);
    }
}

void ImagesRenderer::render_prev_pages(int page_num, 
                        const ImagePageAttrs & attrs)
{
    // Previous range pages.
    int prev = page_num - prev_range_pages;
    if (prev < 1) { prev = 1; }
    
    ImagePage * page = 0;
    ImageRenderTask * task = 0;

    for (int i = page_num - 1; i >= prev; i--)
    {
        std::string anchor;
        if (doc->get_anchor_of_page(i, anchor))
        {        
            page = get_page(anchor, attrs);
            if (!page)
            {
                if (can_render(anchor, attrs) == IMG_RENDER_OK)
                {
                    task = new ImageRenderTask(anchor, attrs, -1, this, 0);
                    render_thread.append_task(task);
                }
            }
            else
            {
                WARNPRINTF("Page %s already in pages_cache!", anchor.c_str());
                notify_page_ready(page, -1, IMG_RENDER_OK, 0);
            }
        }
    }

}

void ImagesRenderer::render_next_pages(int page_num,
                        const ImagePageAttrs & attrs)
{
    // Next range pages
    int next = page_num + next_range_pages;
    if (next > ((int)doc->page_count())) 
    { 
        next = ((int)doc->page_count()); 
    }

    ImagePage * page = 0;
    ImageRenderTask * task = 0;

    for (int i = page_num + 1; i <= next; i++)
    {
        std::string anchor;
        if (doc->get_anchor_of_page(i, anchor))
        {                
            page = get_page(anchor, attrs);
            if (!page)
            {
                if (can_render(anchor, attrs) == IMG_RENDER_OK)
                {
                    task = new ImageRenderTask(anchor, attrs, -1, this, 0);
                    render_thread.append_task(task);
                }
            }
            else
            {
                WARNPRINTF("Page %s already in pages_cache!", anchor.c_str());
                notify_page_ready(page, -1, IMG_RENDER_OK, 0);
            }
        }
    }
}

void ImagesRenderer::notify_page_ready(ImagePage * page, 
                                       int ref_id,
                                       ImageRenderStatus status,
                                       void * user_data)
{
    LOGPRINTF("%p %d", page, ref_id);

    pages_cache.lock();

    if (page)
    {
        page->update_timestamp();
    }
    
    if (ref_id != -1)
    {
        sig_page_ready.broadcast(page, ref_id, status, user_data);
    }

    pages_cache.unlock();
}

void ImagesRenderer::set_prerender_policy(int prev_range, int next_range)
{
    LOGPRINTF("range[%d, %d]", prev_range, next_range);

    prev_range_pages = prev_range;
    next_range_pages = next_range;
}

bool ImagesRenderer::set_memory_limit(const int bytes)
{
    LOGPRINTF("entry");

    return pages_cache.set_memory_limit(bytes);
}

void ImagesRenderer::stop(void)
{
    render_thread.stop();    
}

};


