/***************************************************************************
 *   Copyright (C) 2008-2017 by Andrzej Rybczak                            *
 *   electricityispower@gmail.com                                          *
 *                                                                         *
 *   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 2 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.,                                       *
 *   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.              *
 ***************************************************************************/

#include <cassert>
#include <boost/regex.hpp>
#include <iostream>

#include "curses/scrollpad.h"
#include "utility/storage_kind.h"

namespace {

template <typename BeginT, typename EndT>
bool regexSearch(NC::Buffer &buf, const BeginT &begin, const std::string &ws,
                 const EndT &end, boost::regex::flag_type flags, size_t id)
{
	try {
		boost::regex rx(ws, flags);
		auto first = boost::sregex_iterator(buf.str().begin(), buf.str().end(), rx);
		auto last = boost::sregex_iterator();
		bool success = first != last;
		for (; first != last; ++first)
		{
			buf.addProperty(first->position(), begin, id);
			buf.addProperty(first->position() + first->length(), end, id);
		}
		return success;
	} catch (boost::bad_expression &e) {
		std::cerr << "regexSearch: bad_expression: " << e.what() << "\n";
		return false;
	}
}

}

namespace NC {

Scrollpad::Scrollpad(size_t startx,
size_t starty,
size_t width,
size_t height,
const std::string &title,
Color color,
Border border)
: Window(startx, starty, width, height, title, color, border),
m_beginning(0),
m_real_height(height)
{
}

void Scrollpad::refresh()
{
	assert(m_real_height >= m_height);
	size_t max_beginning = m_real_height - m_height;
	m_beginning = std::min(m_beginning, max_beginning);
	prefresh(m_window, m_beginning, 0, m_start_y, m_start_x, m_start_y+m_height-1, m_start_x+m_width-1);
}

void Scrollpad::resize(size_t new_width, size_t new_height)
{
	adjustDimensions(new_width, new_height);
	recreate(new_width, new_height);
	flush();
}

void Scrollpad::scroll(Scroll where)
{
	assert(m_real_height >= m_height);
	size_t max_beginning = m_real_height - m_height;
	switch (where)
	{
		case Scroll::Up:
		{
			if (m_beginning > 0)
				--m_beginning;
			break;
		}
		case Scroll::Down:
		{
			if (m_beginning < max_beginning)
				++m_beginning;
			break;
		}
		case Scroll::PageUp:
		{
			if (m_beginning > m_height)
				m_beginning -= m_height;
			else
				m_beginning = 0;
			break;
		}
		case Scroll::PageDown:
		{
			m_beginning = std::min(m_beginning + m_height, max_beginning);
			break;
		}
		case Scroll::Home:
		{
			m_beginning = 0;
			break;
		}
		case Scroll::End:
		{
			m_beginning = max_beginning;
			break;
		}
	}
}

void Scrollpad::clear()
{
	m_real_height = m_height;
	m_buffer.clear();
	Window::clear();
}

const std::string &Scrollpad::buffer()
{
	return m_buffer.str();
}

void Scrollpad::flush()
{
	auto &w = static_cast<Window &>(*this);
	const auto &s = m_buffer.str();
	const auto &ps = m_buffer.properties();
	auto p = ps.begin();
	size_t i = 0;
	
	auto load_properties = [&]() {
		for (; p != ps.end() && p->first == i; ++p)
			w << p->second;
	};
	auto write_whitespace = [&]() {
		for (; i < s.length() && iswspace(s[i]); ++i)
		{
			load_properties();
			w << s[i];
		}
	};
	auto write_word = [&](bool load_properties_) {
		for (; i < s.length() && !iswspace(s[i]); ++i)
		{
			if (load_properties_)
				load_properties();
			w << s[i];
		}
	};
	auto write_buffer = [&](bool generate_height_only) -> size_t {
		int new_y;
		size_t height = 1;
		size_t old_i;
		auto old_p = p;
		int x, y;
		i = 0;
		p = ps.begin();
		y = getY();
		while (i < s.length())
		{
			// write all whitespaces.
			write_whitespace();
			
			// if we are generating height, check difference
			// between previous Y coord and current one and
			// update height accordingly.
			if (generate_height_only)
			{
				new_y = getY();
				height += new_y - y;
				y = new_y;
			}
			
			if (i == s.length())
				break;
			
			// save current string position state and get current
			// coordinates as we are before the beginning of a word.
			old_i = i;
			old_p = p;
			x = getX();
			y = getY();
			
			// write word to test if it overflows, but do not load properties
			// yet since if it overflows, we do not want to load them twice.
			write_word(false);
			
			// restore previous indexes state
			i = old_i;
			p = old_p;
			
			// get new Y coord to see if word overflew into next line.
			new_y = getY();
			if (new_y != y)
			{
				if (generate_height_only)
				{
					// if it did, let's update height...
					++height;
				}
				else
				{
					// ...or go to old coordinates, erase
					// part of the string from previous line...
					goToXY(x, y);
					wclrtoeol(m_window);
				}
				
				// ...start at the beginning of next line...
				++y;
				goToXY(0, y);
				
				// ...write word again, this time with properties...
				write_word(true);
				
				if (generate_height_only)
				{
					// ... and check for potential
					// difference in Y coordinates again.
					new_y = getY();
					height += new_y - y;
				}
			}
			else
			{
				// word didn't overflow, rewrite it with properties.
				goToXY(x, y);
				write_word(true);
			}
			
			if (generate_height_only)
			{
				// move to the first line, since when we do
				// generation, m_real_height = m_height and we
				// don't have many lines to use.
				goToXY(getX(), 0);
				y = 0;
			}
		}
		// load remaining properties if there are any
		for (; p != ps.end(); ++p)
			w << p->second;
		return height;
	};
	m_real_height = std::max(write_buffer(true), m_height);
	if (m_real_height > m_height)
		recreate(m_width, m_real_height);
	else
		werase(m_window);
	write_buffer(false);
}

void Scrollpad::reset()
{
	m_beginning = 0;
}

bool Scrollpad::setProperties(const Color &begin, const std::string &s,
                              const Color &end, size_t flags, size_t id)
{
	return regexSearch(m_buffer, std::move(begin), s, std::move(end), id, flags);
}

bool Scrollpad::setProperties(const Format &begin, const std::string &s,
                              const Format &end, size_t flags, size_t id)
{
	return regexSearch(m_buffer, begin, s, end, flags, id);
}

bool Scrollpad::setProperties(const FormattedColor &fc, const std::string &s,
                              size_t flags, size_t id)
{
	return regexSearch(m_buffer,
	                   fc,
	                   s,
	                   FormattedColor::End<StorageKind::Value>(fc),
	                   flags,
	                   id);
}

void Scrollpad::removeProperties(size_t id)
{
	m_buffer.removeProperties(id);
}

}
