#include "html.h"
#include "render_inline_context.h"
#include "document.h"
#include "iterators.h"

int litehtml::render_item_inline_context::_render_content(int x, int y, bool second_pass, const containing_block_context &self_size, formatting_context* fmt_ctx)
{
    m_line_boxes.clear();
	m_max_line_width = 0;

    white_space ws = src_el()->css().get_white_space();
    bool skip_spaces = false;
    if (ws == white_space_normal ||
        ws == white_space_nowrap ||
        ws == white_space_pre_line)
    {
        skip_spaces = true;
    }

    bool was_space = false;

    go_inside_inline go_inside_inlines_selector;
    inline_selector select_inlines;
    elements_iterator inlines_iter(true, &go_inside_inlines_selector, &select_inlines);

    inlines_iter.process(shared_from_this(), [&](const std::shared_ptr<render_item>& el, iterator_item_type item_type)
        {
			switch (item_type)
			{
				case iterator_item_type_child:
					{
						// skip spaces to make rendering a bit faster
						if (skip_spaces)
						{
							if (el->src_el()->is_white_space())
							{
								if (was_space)
								{
									el->skip(true);
									return;
								} else
								{
									was_space = true;
								}
							} else
							{
								// skip all spaces after line break
								was_space = el->src_el()->is_break();
							}
						}
						// place element into rendering flow
						place_inline(std::unique_ptr<line_box_item>(new line_box_item(el)), self_size, fmt_ctx);
					}
					break;

				case iterator_item_type_start_parent:
					{
						el->clear_inline_boxes();
						place_inline(std::unique_ptr<lbi_start>(new lbi_start(el)), self_size, fmt_ctx);
					}
					break;

				case iterator_item_type_end_parent:
				{
					place_inline(std::unique_ptr<lbi_end>(new lbi_end(el)), self_size, fmt_ctx);
				}
					break;
			}
        });

    finish_last_box(true, self_size);

    if (!m_line_boxes.empty())
    {
        if (collapse_top_margin())
        {
            int old_top = m_margins.top;
            m_margins.top = std::max(m_line_boxes.front()->top_margin(), m_margins.top);
            if (m_margins.top != old_top)
            {
                fmt_ctx->update_floats(m_margins.top - old_top, shared_from_this());
            }
        }
        if (collapse_bottom_margin())
        {
            m_margins.bottom = std::max(m_line_boxes.back()->bottom_margin(), m_margins.bottom);
            m_pos.height = m_line_boxes.back()->bottom() - m_line_boxes.back()->bottom_margin();
        }
        else
        {
            m_pos.height = m_line_boxes.back()->bottom();
        }
    }

    return m_max_line_width;
}

void litehtml::render_item_inline_context::fix_line_width(element_float flt,
														  const containing_block_context &self_size,
														  formatting_context* fmt_ctx)
{
    if(!m_line_boxes.empty())
    {
		auto el_front = m_line_boxes.back()->get_first_text_part();

        std::vector<std::shared_ptr<render_item>> els;
        bool was_cleared = false;
        if(el_front && el_front->src_el()->css().get_clear() != clear_none)
        {
            if(el_front->src_el()->css().get_clear() == clear_both)
            {
                was_cleared = true;
            } else
            {
                if(	(flt == float_left	&& el_front->src_el()->css().get_clear() == clear_left) ||
                       (flt == float_right	&& el_front->src_el()->css().get_clear() == clear_right) )
                {
                    was_cleared = true;
                }
            }
        }

        if(!was_cleared)
        {
			std::list<std::unique_ptr<line_box_item> > items = std::move(m_line_boxes.back()->items());
            m_line_boxes.pop_back();

            for(auto& item : items)
            {
                place_inline(std::move(item), self_size, fmt_ctx);
            }
        } else
        {
            int line_top = 0;
            line_top = m_line_boxes.back()->top();

            int line_left	= 0;
            int line_right	= self_size.render_width;
            fmt_ctx->get_line_left_right(line_top, self_size.render_width, line_left, line_right);

            if(m_line_boxes.size() == 1)
            {
                if (src_el()->css().get_list_style_type() != list_style_type_none && src_el()->css().get_list_style_position() == list_style_position_inside)
                {
                    int sz_font = src_el()->css().get_font_size();
                    line_left += sz_font;
                }

                if (src_el()->css().get_text_indent().val() != 0)
                {
                    line_left += src_el()->css().get_text_indent().calc_percent(self_size.width);
                }
            
            }

            auto items = m_line_boxes.back()->new_width(line_left, line_right);
            for(auto& item : items)
            {
                place_inline(std::move(item), self_size, fmt_ctx);
            }
        }
    }
}

std::list<std::unique_ptr<litehtml::line_box_item> > litehtml::render_item_inline_context::finish_last_box(bool end_of_render, const containing_block_context &self_size)
{
	std::list<std::unique_ptr<line_box_item> > ret;

    if(!m_line_boxes.empty())
    {
		ret = m_line_boxes.back()->finish(end_of_render, self_size);

        if(m_line_boxes.back()->is_empty() && end_of_render)
        {
			// remove the last empty line
            m_line_boxes.pop_back();
        } else
		{
			m_max_line_width = std::max(m_max_line_width, m_line_boxes.back()->min_width());
		}
    }
    return ret;
}

int litehtml::render_item_inline_context::new_box(const std::unique_ptr<line_box_item>& el, line_context& line_ctx, const containing_block_context &self_size, formatting_context* fmt_ctx)
{
	auto items = finish_last_box(false, self_size);
	int line_top = 0;
	if(!m_line_boxes.empty())
	{
		line_top = m_line_boxes.back()->bottom();
	}
    line_ctx.top = fmt_ctx->get_cleared_top(el->get_el(), line_top);

    line_ctx.left = 0;
    line_ctx.right = self_size.render_width;
    line_ctx.fix_top();
	fmt_ctx->get_line_left_right(line_ctx.top, self_size.render_width, line_ctx.left, line_ctx.right);

    if(el->get_el()->src_el()->is_inline() || el->get_el()->src_el()->is_block_formatting_context())
    {
        if (el->get_el()->width() > line_ctx.right - line_ctx.left)
        {
            line_ctx.top = fmt_ctx->find_next_line_top(line_ctx.top, el->get_el()->width(), self_size.render_width);
            line_ctx.left = 0;
            line_ctx.right = self_size.render_width;
            line_ctx.fix_top();
			fmt_ctx->get_line_left_right(line_ctx.top, self_size.render_width, line_ctx.left, line_ctx.right);
        }
    }

    int first_line_margin = 0;
    int text_indent = 0;
    if(m_line_boxes.empty())
    {
        if(src_el()->css().get_list_style_type() != list_style_type_none && src_el()->css().get_list_style_position() == list_style_position_inside)
        {
            int sz_font = src_el()->css().get_font_size();
            first_line_margin = sz_font;
        }
        if(src_el()->css().get_text_indent().val() != 0)
        {
            text_indent = src_el()->css().get_text_indent().calc_percent(self_size.width);
        }
    }

    m_line_boxes.emplace_back(std::unique_ptr<line_box>(new line_box(
			line_ctx.top,
			line_ctx.left + first_line_margin + text_indent, line_ctx.right,
			css().get_line_height(),
			css().get_font_metrics(),
			css().get_text_align())));

	// Add items returned by finish_last_box function into the new line
	for(auto& it : items)
	{
		m_line_boxes.back()->add_item(std::move(it));
	}

    return line_ctx.top;
}

void litehtml::render_item_inline_context::place_inline(std::unique_ptr<line_box_item> item, const containing_block_context &self_size, formatting_context* fmt_ctx)
{
    if(item->get_el()->src_el()->css().get_display() == display_none) return;

    if(item->get_el()->src_el()->is_float())
    {
        int line_top = 0;
        if(!m_line_boxes.empty())
        {
            line_top = m_line_boxes.back()->top();
        }
        int ret = place_float(item->get_el(), line_top, self_size, fmt_ctx);
		if(ret > m_max_line_width)
		{
			m_max_line_width = ret;
		}
		return;
    }

    line_context line_ctx = {0};
    line_ctx.top = 0;
    if (!m_line_boxes.empty())
    {
        line_ctx.top = m_line_boxes.back().get()->top();
    }
    line_ctx.left = 0;
    line_ctx.right = self_size.render_width;
    line_ctx.fix_top();
	fmt_ctx->get_line_left_right(line_ctx.top, self_size.render_width, line_ctx.left, line_ctx.right);

	if(item->get_type() == line_box_item::type_text_part)
	{
		if(item->get_el()->src_el()->is_inline_box())
		{
			int min_rendered_width = item->get_el()->render(line_ctx.left, line_ctx.top, self_size.new_width(line_ctx.right), fmt_ctx);
			if(min_rendered_width < item->get_el()->width() && item->get_el()->src_el()->css().get_width().is_predefined())
			{
				item->get_el()->render(line_ctx.left, line_ctx.top, self_size.new_width(min_rendered_width), fmt_ctx);
			}
			item->set_rendered_min_width(min_rendered_width);
		} else if(item->get_el()->src_el()->css().get_display() == display_inline_text)
		{
			litehtml::size sz;
			item->get_el()->src_el()->get_content_size(sz, line_ctx.right);
			item->get_el()->pos() = sz;
			item->set_rendered_min_width(sz.width);
		}
	}

    bool add_box = true;
    if(!m_line_boxes.empty())
    {
        if(m_line_boxes.back()->can_hold(item, src_el()->css().get_white_space()))
        {
            add_box = false;
        }
    }
    if(add_box)
    {
        new_box(item, line_ctx, self_size, fmt_ctx);
    } else if(!m_line_boxes.empty())
    {
        line_ctx.top = m_line_boxes.back()->top();
    }

    if (line_ctx.top != line_ctx.calculatedTop)
    {
        line_ctx.left = 0;
        line_ctx.right = self_size.render_width;
        line_ctx.fix_top();
		fmt_ctx->get_line_left_right(line_ctx.top, self_size.render_width, line_ctx.left, line_ctx.right);
    }

    if(!item->get_el()->src_el()->is_inline())
    {
        if(m_line_boxes.size() == 1)
        {
            if(collapse_top_margin())
            {
                int shift = item->get_el()->margin_top();
                if(shift >= 0)
                {
                    line_ctx.top -= shift;
                    m_line_boxes.back()->y_shift(-shift);
                }
            }
        } else
        {
            int shift = 0;
            int prev_margin = m_line_boxes[m_line_boxes.size() - 2]->bottom_margin();

            if(prev_margin > item->get_el()->margin_top())
            {
                shift = item->get_el()->margin_top();
            } else
            {
                shift = prev_margin;
            }
            if(shift >= 0)
            {
                line_ctx.top -= shift;
                m_line_boxes.back()->y_shift(-shift);
            }
        }
    }

	m_line_boxes.back()->add_item(std::move(item));
}

void litehtml::render_item_inline_context::apply_vertical_align()
{
    if(!m_line_boxes.empty())
    {
        int add = 0;
        int content_height	= m_line_boxes.back()->bottom();

        if(m_pos.height > content_height)
        {
            switch(src_el()->css().get_vertical_align())
            {
                case va_middle:
                    add = (m_pos.height - content_height) / 2;
                    break;
                case va_bottom:
                    add = m_pos.height - content_height;
                    break;
                default:
                    add = 0;
                    break;
            }
        }

        if(add)
        {
            for(auto & box : m_line_boxes)
            {
                box->y_shift(add);
            }
        }
    }
}

int litehtml::render_item_inline_context::get_first_baseline()
{
	int bl;
	if(!m_line_boxes.empty())
	{
		const auto &line = m_line_boxes.front();
		bl = line->bottom() - line->baseline() + content_offset_top();
	} else
	{
		bl = height() - margin_bottom();
	}
	return bl;
}

int litehtml::render_item_inline_context::get_last_baseline()
{
	int bl;
	if(!m_line_boxes.empty())
	{
		const auto &line = m_line_boxes.back();
		bl = line->bottom() - line->baseline() + content_offset_top();
	} else
	{
		bl = height() - margin_bottom();
	}
	return bl;
}
