//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2010 Marianne Gagnon
//
//  This program 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 3
//  of the License, or (at your option) any later version.
//
//  This program 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, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#include "guiengine/layout_manager.hpp"

#include <iostream>

#include <IGUIFont.h>
#include <ITexture.h>
using namespace irr;
using namespace gui;
using namespace video;

#include "graphics/irr_driver.hpp"
#include "guiengine/abstract_top_level_container.hpp"
#include "guiengine/engine.hpp"
#include "guiengine/scalable_font.hpp"
#include "guiengine/widget.hpp"
#include "io/file_manager.hpp"
#include "utils/ptr_vector.hpp"
#include "utils/string_utils.hpp"

using namespace GUIEngine;

#ifndef round
# define round(x)  (floor(x+0.5f))
#endif

/** Like atoi, but on error prints an error message to stderr */
int atoi_p(const char* val)
{
    int i;
    if (StringUtils::parseString<int>(val, &i))
    {
        return i;
    }
    else
    {
        fprintf(stderr, "[LayoutManager] WARNING: Invalid value '%s' found in XML file where integer was expected\n", val);
        return 0;
    }
}

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

bool LayoutManager::convertToCoord(std::string& x, int* absolute /* out */, int* percentage /* out */)
{    
    bool is_number;
    int i;
    std::istringstream myStream(x);
    is_number = (myStream >> i)!=0;
    
    if(!is_number) return false;
    
    if( x[x.size()-1] == '%' ) // percentage
    {
        *percentage = i;
        return true;
    }
    else // absolute number
    {
        *absolute = i;
        return true;
    }
}


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

void LayoutManager::readCoords(Widget* self)
{    
    // determine widget position and size if not already done by sizers
    std::string x      = self->m_properties[PROP_X];
    std::string y      = self->m_properties[PROP_Y];
    std::string width  = self->m_properties[PROP_WIDTH];
    std::string height = self->m_properties[PROP_HEIGHT];
    
    /*
    // retrieve parent size (or screen size if none). Will be useful for layout
    // and especially for percentages.
    unsigned int parent_w, parent_h, parent_x, parent_y;
    if(parent == NULL)
    {
        //core::dimension2d<u32> frame_size = GUIEngine::getDriver()->getCurrentRenderTargetSize();
        //parent_w = frame_size.Width;
        //parent_h = frame_size.Height;
        parent_w = topLevelContainer->getWidth();
        parent_h = topLevelContainer->getHeight();
        parent_x = 0;
        parent_y = 0;
    }
    else
    {
        parent_w = parent->m_w;
        parent_h = parent->m_h;
        parent_x = parent->m_x;
        parent_y = parent->m_y;
    }
    */
    
    // ---- try converting to number
    // x coord
    {
        int abs_x = 0x7FFFFFFF, percent_x = 0x7FFFFFFF;
        if (convertToCoord(x, &abs_x, &percent_x ))
        {
            if      (abs_x >= 0 && abs_x != 0x7FFFFFFF)
            {
                self->m_absolute_x = abs_x;
            }
            else if (abs_x < 0)
            {
                self->m_absolute_reverse_x = abs(abs_x);
            }
            else if (percent_x >= 0 && percent_x != 0x7FFFFFFF)
            {
                self->m_relative_x = (float)percent_x;
            }
        }
    }
    
    // y coord
    {
        int abs_y = 0x7FFFFFFF, percent_y = 0x7FFFFFFF;
        if (convertToCoord(y, &abs_y, &percent_y ))
        {
            if      (abs_y >= 0 && abs_y != 0x7FFFFFFF)
            {
                self->m_absolute_y = abs_y;
            }
            else if (abs_y < 0)
            {
                self->m_absolute_reverse_y = abs(abs_y);
            }
            else if (percent_y >= 0 && percent_y != 0x7FFFFFFF)
            {
                self->m_relative_y = (float)percent_y;
            }
        }
    }
    
    // ---- if this widget has an icon, get icon size. this can helpful determine its optimal size
    int texture_w = -1, texture_h = -1;
    
    if (self->m_properties[PROP_ICON].size() > 0)
    {
        ITexture* texture = irr_driver->getTexture((file_manager->getDataDir() + "/" +
                                                    self->m_properties[PROP_ICON]).c_str());
        
        if (texture != NULL)
        {
            texture_w = texture->getSize().Width;
            texture_h = texture->getSize().Height;
        }
    }
    
    // ---- if this widget has a label, get text size. this can helpful determine its optimal size
    int label_w = -1, label_h = -1;
    if (self->m_text.size() > 0)
    {
        IGUIFont* font = (self->m_title_font ? GUIEngine::getTitleFont() : GUIEngine::getFont());
        core::dimension2d< u32 > dim = font->getDimension( self->m_text.c_str() );
        label_w = dim.Width + self->getWidthNeededAroundLabel();
        // FIXME - won't work with multiline labels. thus, for now, when multiple
        // lines are required, we need to specify a height explicitely
        label_h = dim.Height + self->getHeightNeededAroundLabel();
    }
    else if (self->getType() == WTYPE_CHECKBOX)
    {
        // User text height to guess checkbox size
        IGUIFont* font = (self->m_title_font ? GUIEngine::getTitleFont() : GUIEngine::getFont());
        core::dimension2d< u32 > dim = font->getDimension( L"X" );
        label_h = dim.Height + self->getHeightNeededAroundLabel();   
        label_w = label_h; // a checkbox is square
    }
    
    if (label_h == -1)
    {
        if (self->getType() == WTYPE_TEXTBOX ||
            self->getType() == WTYPE_BUTTON  ||
            self->getType() == WTYPE_LABEL   ||
            self->getType() == WTYPE_SPINNER)
        {
            IGUIFont* font = (self->m_title_font ? GUIEngine::getTitleFont() : GUIEngine::getFont());

            // get text height, a text box, button or label is always as high as the text it could contain
            core::dimension2d< u32 > dim = font->getDimension( L"X" );
            label_h = dim.Height + self->getHeightNeededAroundLabel();
        }
    }
    
    // ---- if this widget has children, get their size size. this can helpful determine its optimal size
    if (self->m_properties[PROP_WIDTH] == "fit" || self->m_properties[PROP_HEIGHT] == "fit")
    {
        bool is_vertical_row = (self->m_properties[PROP_LAYOUT] == "vertical-row");
        bool is_horizontal_row = (self->m_properties[PROP_LAYOUT] == "horizontal-row");

        int child_max_width = -1, child_max_height = -1;
        int total_width = 0, total_height = 0;
        
        for (int child=0; child<self->m_children.size(); child++)
        {
            if (self->m_children[child].m_absolute_w > -1)
            {
                if (is_horizontal_row)
                {
                    total_width += self->m_children[child].m_absolute_w ;
                }
                else
                {
                    if (child_max_width == -1 || self->m_children[child].m_absolute_w > child_max_width)
                    {
                        child_max_width = self->m_children[child].m_absolute_w;
                    }
                }
            }
            
            if (self->m_children[child].m_absolute_h > -1)
            {
                if (is_vertical_row)
                {
                    total_height += self->m_children[child].m_absolute_h;
                }
                else
                {
                    if (child_max_height == -1 || self->m_children[child].m_absolute_h > child_max_height)
                    {
                        child_max_height = self->m_children[child].m_absolute_h;
                    }
                }
            }
        }
        
        if (self->m_properties[PROP_WIDTH] == "fit")
        {
            self->m_absolute_w = (is_horizontal_row ? total_width : child_max_width);
        }
        if (self->m_properties[PROP_HEIGHT] == "fit")
        {
            self->m_absolute_h = (is_vertical_row ? total_height : child_max_height);
        }
    }
    
    // ---- read dimension
    // width
    {
        int abs_w = -1, percent_w = -1;
        if (width == "font") self->m_absolute_w = GUIEngine::getFontHeight();
        else if (convertToCoord(width, &abs_w, &percent_w ))
        {
            if      (abs_w > -1)     self->m_absolute_w = abs_w;
            else if (percent_w > -1) self->m_relative_w = (float)percent_w;
        }
        else if(texture_w > -1) self->m_absolute_w = texture_w;
        else if(label_w > -1)   self->m_absolute_w = label_w;
    }
    
    // height
    {
        int abs_h = -1, percent_h = -1;
        if (height == "font") self->m_absolute_h = GUIEngine::getFontHeight();
        else if (convertToCoord(height, &abs_h, &percent_h ))
        {
            if      (abs_h > -1)     self->m_absolute_h = abs_h;
            else if (percent_h > -1) self->m_relative_h = (float)percent_h;
        }
        else if (texture_h > -1 && label_h > -1) self->m_absolute_h = texture_h + label_h; // label + icon
        else if (texture_h > -1)                 self->m_absolute_h = texture_h;
        else if (label_h > -1)                   self->m_absolute_h = label_h;
    }
}

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

void LayoutManager::applyCoords(Widget* self, AbstractTopLevelContainer* topLevelContainer, Widget* parent)
{
    // retrieve parent size (or screen size if none). Will be useful for layout
    // and especially for percentages.
    unsigned int parent_w, parent_h, parent_x, parent_y;
    if(parent == NULL)
    {
        //core::dimension2d<u32> frame_size = GUIEngine::getDriver()->getCurrentRenderTargetSize();
        //parent_w = frame_size.Width;
        //parent_h = frame_size.Height;
        parent_w = topLevelContainer->getWidth();
        parent_h = topLevelContainer->getHeight();
        parent_x = 0;
        parent_y = 0;
    }
    else
    {
        parent_w = parent->m_w;
        parent_h = parent->m_h;
        parent_x = parent->m_x;
        parent_y = parent->m_y;
    }
    
    if      (self->m_absolute_x > -1)         self->m_x = parent_x + self->m_absolute_x;
    else if (self->m_absolute_reverse_x > -1) self->m_x = parent_x + (parent_w - self->m_absolute_reverse_x);
    else if (self->m_relative_x > -1)         self->m_x = (int)(parent_x + parent_w*self->m_relative_x/100);
    
    if      (self->m_absolute_y > -1)         self->m_y = parent_y + self->m_absolute_y;
    else if (self->m_absolute_reverse_y > -1) self->m_y = parent_y + (parent_h - self->m_absolute_reverse_y);
    else if (self->m_relative_y > -1)         self->m_y = (int)(parent_y + parent_h*self->m_relative_y/100);
    
    if (self->m_absolute_w > -1)      self->m_w = self->m_absolute_w;
    else if (self->m_relative_w > -1) self->m_w = (int)round(parent_w*self->m_relative_w/100.0);
    
    if (self->m_absolute_h > -1)      self->m_h = self->m_absolute_h;
    else if (self->m_relative_h > -1) self->m_h = (int)round(parent_h*self->m_relative_h/100.0);
    
    // ---- can't make widget bigger than parent
    if (self->m_h > (int)parent_h)
    {
        float ratio = (float)parent_h / self->m_h;
        
        self->m_w = (int)(self->m_w*ratio);
        self->m_h = (int)(self->m_h*ratio);
    }
    if (self->m_w > (int)parent_w)
    {
        // scale down while keeping aspect ratio (don't do this for text widgets though...)
        float ratio = (float)parent_w / self->m_w;
        
        self->m_w = (int)(self->m_w * ratio);
        
        // FIXME: ugly to hardcode widgets types here
        if (self->m_type != WTYPE_LABEL && self->m_type != WTYPE_BUBBLE)
        {
            self->m_h = (int)(self->m_h * ratio);
        }
    }
    
    // ------ check for given max size
    if (self->m_properties[PROP_MAX_WIDTH].size() > 0)
    {
        const int max_width = atoi_p( self->m_properties[PROP_MAX_WIDTH].c_str() );
        if (self->m_w > max_width) self->m_w = max_width;
    }
    
    if (self->m_properties[PROP_MAX_HEIGHT].size() > 0)
    {
        const int max_height = atoi_p( self->m_properties[PROP_MAX_HEIGHT].c_str() );
        if (self->m_h > max_height) self->m_h = max_height;
    }
}

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

void LayoutManager::recursivelyReadCoords(PtrVector<Widget>& widgets)
{
    const unsigned short widgets_amount = widgets.size();
    
    // ----- deal with containers' children
    for (int n=0; n<widgets_amount; n++)
    {
        if (widgets[n].m_type == WTYPE_DIV) recursivelyReadCoords(widgets[n].m_children);
    }
    
    // ----- read x/y/size parameters
    for (unsigned short n=0; n<widgets_amount; n++)
    {
        readCoords(widgets.get(n));
    }//next widget     
}
    
// ----------------------------------------------------------------------------

void LayoutManager::calculateLayout(PtrVector<Widget>& widgets, AbstractTopLevelContainer* topLevelContainer)
{
    recursivelyReadCoords(widgets);
    doCalculateLayout(widgets, topLevelContainer, NULL);
}

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

void LayoutManager::doCalculateLayout(PtrVector<Widget>& widgets, AbstractTopLevelContainer* topLevelContainer,
                                      Widget* parent)
{
    const unsigned short widgets_amount = widgets.size();
 
    for (int n=0; n<widgets_amount; n++)
    {
        applyCoords(widgets.get(n), topLevelContainer, parent);
    }
    
    // ----- manage 'layout's if relevant
    do // i'm using 'while false' here just to be able to 'break' ...
    {
        if(parent == NULL) break;
        
        std::string layout_name = parent->m_properties[PROP_LAYOUT];
        if(layout_name.size() < 1) break;
        
        bool horizontal = false;
        
        if (!strcmp("horizontal-row", layout_name.c_str()))
            horizontal = true;
        else if(!strcmp("vertical-row", layout_name.c_str()))
            horizontal = false;
        else
        {
            std::cerr << "Unknown layout name : " << layout_name.c_str() << std::endl;
            break;
        }
        
        const int w = parent->m_w, h = parent->m_h;
        
        // find space left after placing all absolutely-sized widgets in a row
        // (the space left will be divided between remaining widgets later)
        int left_space = (horizontal ? w : h);
        unsigned short total_proportion = 0;
        for(int n=0; n<widgets_amount; n++)
        {
            // relatively-sized widget
            std::string prop = widgets[n].m_properties[ PROP_PROPORTION ];
            if(prop.size() != 0)
            {
                total_proportion += atoi_p( prop.c_str() );
                continue;
            }
            
            // absolutely-sized widgets
            left_space -= (horizontal ? widgets[n].m_w : widgets[n].m_h);
        } // next widget
        
        if (left_space < 0)
        {
            fprintf(stderr, "[LayoutManager] WARNING: statically sized widgets took all the place!!\n");
            left_space = 0;
        }
        
        // ---- lay widgets in row
        int x = parent->m_x;
        int y = parent->m_y;
        for (int n=0; n<widgets_amount; n++)
        {
            std::string prop = widgets[n].m_properties[ PROP_PROPORTION ];
            if(prop.size() != 0)
            {
                // proportional size
                int proportion = 1;
                std::istringstream myStream(prop);
                if (!(myStream >> proportion))
                {
                    std::cerr << "/!\\ Warning /!\\ : proportion  '" << prop.c_str()
                              << "' is not a number for widget " << widgets[n].m_properties[PROP_ID].c_str()
                              << std::endl;
                }
                
                const float fraction = (float)proportion/(float)total_proportion;
                
                if (horizontal)
                {
                    widgets[n].m_x = x;
                    
                    std::string align = widgets[n].m_properties[ PROP_ALIGN ];
                    if (align.size() < 1)
                    {
                        std::string prop_y = widgets[n].m_properties[ PROP_Y ];
                        if (prop_y.size() > 0)
                        {
                            if (prop_y[ prop_y.size()-1 ] == '%')
                            {
                                prop_y = prop_y.substr(0, prop_y.size() - 1);
                                widgets[n].m_y = (int)(y + atoi_p(prop_y.c_str())/100.0f * h);
                            }
                            else
                            {
                                widgets[n].m_y = y + atoi_p(prop_y.c_str());
                            }
                        }
                        else
                        {
                            widgets[n].m_y = y;
                        }
                    }
                    else if (align == "top")
                    {
                        widgets[n].m_y = y;
                    }
                    else if (align == "center")
                    {
                        widgets[n].m_y = y + h/2 - widgets[n].m_h/2;
                    }
                    else if (align == "bottom")
                    {
                        widgets[n].m_y = y + h - widgets[n].m_h;
                    }
                    else
                    {
                        std::cerr  << "/!\\ Warning /!\\ : alignment  '" << align.c_str()
                                   <<  "' is unknown (widget '" << widgets[n].m_properties[PROP_ID].c_str()
                                   << "', in a horiozntal-row layout)\n";
                    }
                    
                    widgets[n].m_w = (int)(left_space*fraction);
                    if (widgets[n].m_properties[PROP_MAX_WIDTH].size() > 0)
                    {
                        const int max_width = atoi_p( widgets[n].m_properties[PROP_MAX_WIDTH].c_str() );
                        if (widgets[n].m_w > max_width) widgets[n].m_w = max_width;
                    }
                    
                    if (widgets[n].m_w <= 0)
                    {
                        fprintf(stderr, "WARNING: widget '%s' has a width of %i (left_space = %i, "
                                "fraction = %f, max_width = %s)\n", widgets[n].m_properties[PROP_ID].c_str(),
                                widgets[n].m_w, left_space, fraction, widgets[n].m_properties[PROP_MAX_WIDTH].c_str());
                        widgets[n].m_w = 1;
                    }
                    
                    x += widgets[n].m_w;
                }
                else
                {    
                    widgets[n].m_h = (int)(left_space*fraction);
                    
                    if (widgets[n].m_properties[PROP_MAX_HEIGHT].size() > 0)
                    {
                        const int max_height = atoi_p( widgets[n].m_properties[PROP_MAX_HEIGHT].c_str() );
                        if (widgets[n].m_h > max_height) widgets[n].m_h = max_height;
                    }
                    
                    if (widgets[n].m_h <= 0)
                    {
                        fprintf(stderr, "WARNING: widget '%s' has a height of %i (left_space = %i, "
                                "fraction = %f, max_width = %s)\n", widgets[n].m_properties[PROP_ID].c_str(),
                                widgets[n].m_h, left_space, fraction, widgets[n].m_properties[PROP_MAX_WIDTH].c_str());
                        widgets[n].m_h = 1;
                    }
                    
                    std::string align = widgets[n].m_properties[ PROP_ALIGN ];
                    if (align.size() < 1)
                    {
                        std::string prop_x = widgets[n].m_properties[ PROP_X ];
                        if (prop_x.size() > 0)
                        {
                            if (prop_x[ prop_x.size()-1 ] == '%')
                            {
                                prop_x = prop_x.substr(0, prop_x.size() - 1);
                                widgets[n].m_x = (int)(x + atoi_p(prop_x.c_str())/100.0f * w);
                            }
                            else
                            {
                                widgets[n].m_x = x + atoi_p(prop_x.c_str());
                            }
                        }
                        else
                        {
                            widgets[n].m_x = x;
                        }
                    }
                    else if (align == "left")
                    {
                        widgets[n].m_x = x;
                    }
                    else if (align == "center")
                    {
                        widgets[n].m_x = x + w/2 - widgets[n].m_w/2;
                    }
                    else if (align == "right") 
                    {
                        widgets[n].m_x = x + w - widgets[n].m_w;
                    }
                    else
                    {
                        std::cerr << "/!\\ Warning /!\\ : alignment  '" << align.c_str()
                                  <<  "' is unknown (widget '" << widgets[n].m_properties[PROP_ID].c_str()
                                  << "', in a vertical-row layout)\n";
                    }
                    widgets[n].m_y = y;
                    
                    y += widgets[n].m_h;
                }
            }
            else
            {
                // absolute size
                
                if (horizontal)
                {
                    widgets[n].m_x = x;
                    
                    std::string align = widgets[n].m_properties[ PROP_ALIGN ];
                    
                    if (align.size() < 1)
                    {
                        std::string prop_y = widgets[n].m_properties[ PROP_Y ];
                        
                        if (prop_y.size() > 0)
                        {
                            if (prop_y[ prop_y.size()-1 ] == '%')
                            {
                                prop_y = prop_y.substr(0, prop_y.size() - 1);
                                widgets[n].m_y = (int)(y + atoi_p(prop_y.c_str())/100.0f * h);
                            }
                            else
                            {
                                widgets[n].m_y = y + atoi_p(prop_y.c_str());
                            }
                        }
                        else
                        {
                            widgets[n].m_y = y;
                        }
                    }
                    else if (align == "top")
                    {
                        widgets[n].m_y = y;
                    }
                    else if (align == "center")
                    {
                        widgets[n].m_y = y + h/2 - widgets[n].m_h/2;
                    }
                    else if (align == "bottom")
                    {
                        widgets[n].m_y = y + h - widgets[n].m_h;
                    }
                    else
                    {
                        std::cerr << "/!\\ Warning /!\\ : alignment  '" << align.c_str()
                                  << "' is unknown in widget " << widgets[n].m_properties[PROP_ID].c_str()
                                  << std::endl;
                    }
                    
                    x += widgets[n].m_w;
                }
                else
                {                    
                    std::string align = widgets[n].m_properties[ PROP_ALIGN ];
                    
                    if (align.size() < 1)
                    {
                        std::string prop_x = widgets[n].m_properties[ PROP_X ];
                        
                        if (prop_x.size() > 0)
                        {
                            if (prop_x[ prop_x.size()-1 ] == '%')
                            {
                                prop_x = prop_x.substr(0, prop_x.size() - 1);
                                widgets[n].m_x = (int)(x + atoi_p(prop_x.c_str())/100.0f * w);
                            }
                            else
                            {
                                widgets[n].m_x = x + atoi_p(prop_x.c_str());
                            }
                        }
                        else
                        {
                            widgets[n].m_x = x;
                        }
                    }
                    else if (align == "left")
                    {
                        widgets[n].m_x = x;
                    }
                    else if (align == "center")
                    {
                        widgets[n].m_x = x + w/2 - widgets[n].m_w/2;
                    }
                    else if (align == "right")
                    {
                        widgets[n].m_x = x + w - widgets[n].m_w;
                    }
                    else
                    {
                        std::cerr << "/!\\ Warning /!\\ : alignment  '" << align.c_str()
                                  << "' is unknown in widget " << widgets[n].m_properties[PROP_ID].c_str() << std::endl;
                    }
                    widgets[n].m_y = y;
                    
                    y += widgets[n].m_h;
                }
            } // end if property or absolute size
            
        } // next widget
        
    } while(false);
    
    // ----- also deal with containers' children
    for (int n=0; n<widgets_amount; n++)
    {
        if (widgets[n].m_type == WTYPE_DIV) doCalculateLayout(widgets[n].m_children, topLevelContainer, &widgets[n]);
    }
}   // calculateLayout