code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program 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 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "socket.h" // for select()
#include "porting.h" // for sleep_ms(), get_sysinfo(), secure_rand_fill_buf()
#include "httpfetch.h"
#include <iostream>
#include <sstream>
#include <list>
#include <map>
#include <errno.h>
#include "threading/event.h"
#include "config.h"
#include "exceptions.h"
#include "debug.h"
#include "log.h"
#include "util/container.h"
#include "util/thread.h"
#include "version.h"
#include "settings.h"
#include "noise.h"

Mutex g_httpfetch_mutex;
std::map<unsigned long, std::queue<HTTPFetchResult> > g_httpfetch_results;
PcgRandom g_callerid_randomness;

HTTPFetchRequest::HTTPFetchRequest() :
	url(""),
	caller(HTTPFETCH_DISCARD),
	request_id(0),
	timeout(g_settings->getS32("curl_timeout")),
	connect_timeout(timeout),
	multipart(false),
	useragent(std::string(PROJECT_NAME_C "/") + g_version_hash + " (" + porting::get_sysinfo() + ")")
{
}

static void httpfetch_deliver_result(const HTTPFetchResult &fetch_result)
{
	unsigned long caller = fetch_result.caller;
	if (caller != HTTPFETCH_DISCARD) {
		MutexAutoLock lock(g_httpfetch_mutex);
		g_httpfetch_results[caller].push(fetch_result);
	}
}

static void httpfetch_request_clear(unsigned long caller);

unsigned long httpfetch_caller_alloc()
{
	MutexAutoLock lock(g_httpfetch_mutex);

	// Check each caller ID except HTTPFETCH_DISCARD
	const unsigned long discard = HTTPFETCH_DISCARD;
	for (unsigned long caller = discard + 1; caller != discard; ++caller) {
		std::map<unsigned long, std::queue<HTTPFetchResult> >::iterator
			it = g_httpfetch_results.find(caller);
		if (it == g_httpfetch_results.end()) {
			verbosestream << "httpfetch_caller_alloc: allocating "
					<< caller << std::endl;
			// Access element to create it
			g_httpfetch_results[caller];
			return caller;
		}
	}

	FATAL_ERROR("httpfetch_caller_alloc: ran out of caller IDs");
	return discard;
}

unsigned long httpfetch_caller_alloc_secure()
{
	MutexAutoLock lock(g_httpfetch_mutex);

	// Generate random caller IDs and make sure they're not
	// already used or equal to HTTPFETCH_DISCARD
	// Give up after 100 tries to prevent infinite loop
	u8 tries = 100;
	unsigned long caller;

	do {
		caller = (((u64) g_callerid_randomness.next()) << 32) |
				g_callerid_randomness.next();

		if (--tries < 1) {
			FATAL_ERROR("httpfetch_caller_alloc_secure: ran out of caller IDs");
			return HTTPFETCH_DISCARD;
		}
	} while (g_httpfetch_results.find(caller) != g_httpfetch_results.end());

	verbosestream << "httpfetch_caller_alloc_secure: allocating "
		<< caller << std::endl;

	// Access element to create it
	g_httpfetch_results[caller];
	return caller;
}

void httpfetch_caller_free(unsigned long caller)
{
	verbosestream<<"httpfetch_caller_free: freeing "
			<<caller<<std::endl;

	httpfetch_request_clear(caller);
	if (caller != HTTPFETCH_DISCARD) {
		MutexAutoLock lock(g_httpfetch_mutex);
		g_httpfetch_results.erase(caller);
	}
}

bool httpfetch_async_get(unsigned long caller, HTTPFetchResult &fetch_result)
{
	MutexAutoLock lock(g_httpfetch_mutex);

	// Check that caller exists
	std::map<unsigned long, std::queue<HTTPFetchResult> >::iterator
		it = g_httpfetch_results.find(caller);
	if (it == g_httpfetch_results.end())
		return false;

	// Check that result queue is nonempty
	std::queue<HTTPFetchResult> &caller_results = it->second;
	if (caller_results.empty())
		return false;

	// Pop first result
	fetch_result = caller_results.front();
	caller_results.pop();
	return true;
}

#if USE_CURL
#include <curl/curl.h>

/*
	USE_CURL is on: use cURL based httpfetch implementation
*/

static size_t httpfetch_writefunction(
		char *ptr, size_t size, size_t nmemb, void *userdata)
{
	std::ostringstream *stream = (std::ostringstream*)userdata;
	size_t count = size * nmemb;
	stream->write(ptr, count);
	return count;
}

static size_t httpfetch_discardfunction(
		char *ptr, size_t size, size_t nmemb, void *userdata)
{
	return size * nmemb;
}

class CurlHandlePool
{
	std::list<CURL*> handles;

public:
	CurlHandlePool() {}
	~CurlHandlePool()
	{
		for (std::list<CURL*>::iterator it = handles.begin();
				it != handles.end(); ++it) {
			curl_easy_cleanup(*it);
		}
	}
	CURL * alloc()
	{
		CURL *curl;
		if (handles.empty()) {
			curl = curl_easy_init();
			if (curl == NULL) {
				errorstream<<"curl_easy_init returned NULL"<<std::endl;
			}
		}
		else {
			curl = handles.front();
			handles.pop_front();
		}
		return curl;
	}
	void free(CURL *handle)
	{
		if (handle)
			handles.push_back(handle);
	}
};

class HTTPFetchOngoing
{
public:
	HTTPFetchOngoing(const HTTPFetchRequest &request, CurlHandlePool *pool);
	~HTTPFetchOngoing();

	CURLcode start(CURLM *multi);
	const HTTPFetchResult * complete(CURLcode res);

	const HTTPFetchRequest &getRequest()    const { return request; };
	const CURL             *getEasyHandle() const { return curl; };

private:
	CurlHandlePool *pool;
	CURL *curl;
	CURLM *multi;
	HTTPFetchRequest request;
	HTTPFetchResult result;
	std::ostringstream oss;
	struct curl_slist *http_header;
	curl_httppost *post;
};

HTTPFetchOngoing::HTTPFetchOngoing(const HTTPFetchRequest &request_,
		CurlHandlePool *pool_):
	pool(pool_),
	curl(NULL),
	multi(NULL),
	request(request_),
	result(request_),
	oss(std::ios::binary),
	http_header(NULL),
	post(NULL)
{
	curl = pool->alloc();
	if (curl == NULL) {
		return;
	}

	// Set static cURL options
	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
	curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1);
	curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");

	std::string bind_address = g_settings->get("bind_address");
	if (!bind_address.empty()) {
		curl_easy_setopt(curl, CURLOPT_INTERFACE, bind_address.c_str());
	}

#if LIBCURL_VERSION_NUM >= 0x071304
	// Restrict protocols so that curl vulnerabilities in
	// other protocols don't affect us.
	// These settings were introduced in curl 7.19.4.
	long protocols =
		CURLPROTO_HTTP |
		CURLPROTO_HTTPS |
		CURLPROTO_FTP |
		CURLPROTO_FTPS;
	curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols);
	curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols);
#endif

	// Set cURL options based on HTTPFetchRequest
	curl_easy_setopt(curl, CURLOPT_URL,
			request.url.c_str());
	curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS,
			request.timeout);
	curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS,
			request.connect_timeout);

	if (request.useragent != "")
		curl_easy_setopt(curl, CURLOPT_USERAGENT, request.useragent.c_str());

	// Set up a write callback that writes to the
	// ostringstream ongoing->oss, unless the data
	// is to be discarded
	if (request.caller == HTTPFETCH_DISCARD) {
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
				httpfetch_discardfunction);
		curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
	} else {
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
				httpfetch_writefunction);
		curl_easy_setopt(curl, CURLOPT_WRITEDATA, &oss);
	}

	// Set POST (or GET) data
	if (request.post_fields.empty() && request.post_data.empty()) {
		curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
	} else if (request.multipart) {
		curl_httppost *last = NULL;
		for (StringMap::iterator it = request.post_fields.begin();
				it != request.post_fields.end(); ++it) {
			curl_formadd(&post, &last,
					CURLFORM_NAMELENGTH, it->first.size(),
					CURLFORM_PTRNAME, it->first.c_str(),
					CURLFORM_CONTENTSLENGTH, it->second.size(),
					CURLFORM_PTRCONTENTS, it->second.c_str(),
					CURLFORM_END);
		}
		curl_easy_setopt(curl, CURLOPT_HTTPPOST, post);
		// request.post_fields must now *never* be
		// modified until CURLOPT_HTTPPOST is cleared
	} else if (request.post_data.empty()) {
		curl_easy_setopt(curl, CURLOPT_POST, 1);
		std::string str;
		for (StringMap::iterator it = request.post_fields.begin();
				it != request.post_fields.end(); ++it) {
			if (str != "")
				str += "&";
			str += urlencode(it->first);
			str += "=";
			str += urlencode(it->second);
		}
		curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
				str.size());
		curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS,
				str.c_str());
	} else {
		curl_easy_setopt(curl, CURLOPT_POST, 1);
		curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
				request.post_data.size());
		curl_easy_setopt(curl, CURLOPT_POSTFIELDS,
				request.post_data.c_str());
		// request.post_data must now *never* be
		// modified until CURLOPT_POSTFIELDS is cleared
	}
	// Set additional HTTP headers
	for (std::vector<std::string>::iterator it = request.extra_headers.begin();
			it != request.extra_headers.end(); ++it) {
		http_header = curl_slist_append(http_header, it->c_str());
	}
	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, http_header);

	if (!g_settings->getBool("curl_verify_cert")) {
		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
	}
}

CURLcode HTTPFetchOngoing::start(CURLM *multi_)
{
	if (!curl)
		return CURLE_FAILED_INIT;

	if (!multi_) {
		// Easy interface (sync)
		return curl_easy_perform(curl);
	}

	// Multi interface (async)
	CURLMcode mres = curl_multi_add_handle(multi_, curl);
	if (mres != CURLM_OK) {
		errorstream << "curl_multi_add_handle"
			<< " returned error code " << mres
			<< std::endl;
		return CURLE_FAILED_INIT;
	}
	multi = multi_; // store for curl_multi_remove_handle
	return CURLE_OK;
}

const HTTPFetchResult * HTTPFetchOngoing::complete(CURLcode res)
{
	result.succeeded = (res == CURLE_OK);
	result.timeout = (res == CURLE_OPERATION_TIMEDOUT);
	result.data = oss.str();

	// Get HTTP/FTP response code
	result.response_code = 0;
	if (curl && (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE,
				&result.response_code) != CURLE_OK)) {
		// We failed to get a return code, make sure it is still 0
		result.response_code = 0;
	}

	if (res != CURLE_OK) {
		errorstream << request.url << " not found ("
			<< curl_easy_strerror(res) << ")"
			<< " (response code " << result.response_code << ")"
			<< std::endl;
	}

	return &result;
}

HTTPFetchOngoing::~HTTPFetchOngoing()
{
	if (multi) {
		CURLMcode mres = curl_multi_remove_handle(multi, curl);
		if (mres != CURLM_OK) {
			errorstream << "curl_multi_remove_handle"
				<< " returned error code " << mres
				<< std::endl;
		}
	}

	// Set safe options for the reusable cURL handle
	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
			httpfetch_discardfunction);
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
	if (http_header) {
		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
		curl_slist_free_all(http_header);
	}
	if (post) {
		curl_easy_setopt(curl, CURLOPT_HTTPPOST, NULL);
		curl_formfree(post);
	}

	// Store the cURL handle for reuse
	pool->free(curl);
}

class CurlFetchThread : public Thread
{
protected:
	enum RequestType {
		RT_FETCH,
		RT_CLEAR,
		RT_WAKEUP,
	};

	struct Request {
		RequestType type;
		HTTPFetchRequest fetch_request;
		Event *event;
	};

	CURLM *m_multi;
	MutexedQueue<Request> m_requests;
	size_t m_parallel_limit;

	// Variables exclusively used within thread
	std::vector<HTTPFetchOngoing*> m_all_ongoing;
	std::list<HTTPFetchRequest> m_queued_fetches;

public:
	CurlFetchThread(int parallel_limit) :
		Thread("CurlFetch")
	{
		if (parallel_limit >= 1)
			m_parallel_limit = parallel_limit;
		else
			m_parallel_limit = 1;
	}

	void requestFetch(const HTTPFetchRequest &fetch_request)
	{
		Request req;
		req.type = RT_FETCH;
		req.fetch_request = fetch_request;
		req.event = NULL;
		m_requests.push_back(req);
	}

	void requestClear(unsigned long caller, Event *event)
	{
		Request req;
		req.type = RT_CLEAR;
		req.fetch_request.caller = caller;
		req.event = event;
		m_requests.push_back(req);
	}

	void requestWakeUp()
	{
		Request req;
		req.type = RT_WAKEUP;
		req.event = NULL;
		m_requests.push_back(req);
	}

protected:
	// Handle a request from some other thread
	// E.g. new fetch; clear fetches for one caller; wake up
	void processRequest(const Request &req)
	{
		if (req.type == RT_FETCH) {
			// New fetch, queue until there are less
			// than m_parallel_limit ongoing fetches
			m_queued_fetches.push_back(req.fetch_request);

			// see processQueued() for what happens next

		}
		else if (req.type == RT_CLEAR) {
			unsigned long caller = req.fetch_request.caller;

			// Abort all ongoing fetches for the caller
			for (std::vector<HTTPFetchOngoing*>::iterator
					it = m_all_ongoing.begin();
					it != m_all_ongoing.end();) {
				if ((*it)->getRequest().caller == caller) {
					delete (*it);
					it = m_all_ongoing.erase(it);
				} else {
					++it;
				}
			}

			// Also abort all queued fetches for the caller
			for (std::list<HTTPFetchRequest>::iterator
					it = m_queued_fetches.begin();
					it != m_queued_fetches.end();) {
				if ((*it).caller == caller)
					it = m_queued_fetches.erase(it);
				else
					++it;
			}
		}
		else if (req.type == RT_WAKEUP) {
			// Wakeup: Nothing to do, thread is awake at this point
		}

		if (req.event != NULL)
			req.event->signal();
	}

	// Start new ongoing fetches if m_parallel_limit allows
	void processQueued(CurlHandlePool *pool)
	{
		while (m_all_ongoing.size() < m_parallel_limit &&
				!m_queued_fetches.empty()) {
			HTTPFetchRequest request = m_queued_fetches.front();
			m_queued_fetches.pop_front();

			// Create ongoing fetch data and make a cURL handle
			// Set cURL options based on HTTPFetchRequest
			HTTPFetchOngoing *ongoing =
				new HTTPFetchOngoing(request, pool);

			// Initiate the connection (curl_multi_add_handle)
			CURLcode res = ongoing->start(m_multi);
			if (res == CURLE_OK) {
				m_all_ongoing.push_back(ongoing);
			}
			else {
				httpfetch_deliver_result(*ongoing->complete(res));
				delete ongoing;
			}
		}
	}

	// Process CURLMsg (indicates completion of a fetch)
	void processCurlMessage(CURLMsg *msg)
	{
		// Determine which ongoing fetch the message pertains to
		size_t i = 0;
		bool found = false;
		for (i = 0; i < m_all_ongoing.size(); ++i) {
			if (m_all_ongoing[i]->getEasyHandle() == msg->easy_handle) {
				found = true;
				break;
			}
		}
		if (msg->msg == CURLMSG_DONE && found) {
			// m_all_ongoing[i] succeeded or failed.
			HTTPFetchOngoing *ongoing = m_all_ongoing[i];
			httpfetch_deliver_result(*ongoing->complete(msg->data.result));
			delete ongoing;
			m_all_ongoing.erase(m_all_ongoing.begin() + i);
		}
	}

	// Wait for a request from another thread, or timeout elapses
	void waitForRequest(long timeout)
	{
		if (m_queued_fetches.empty()) {
			try {
				Request req = m_requests.pop_front(timeout);
				processRequest(req);
			}
			catch (ItemNotFoundException &e) {}
		}
	}

	// Wait until some IO happens, or timeout elapses
	void waitForIO(long timeout)
	{
		fd_set read_fd_set;
		fd_set write_fd_set;
		fd_set exc_fd_set;
		int max_fd;
		long select_timeout = -1;
		struct timeval select_tv;
		CURLMcode mres;

		FD_ZERO(&read_fd_set);
		FD_ZERO(&write_fd_set);
		FD_ZERO(&exc_fd_set);

		mres = curl_multi_fdset(m_multi, &read_fd_set,
				&write_fd_set, &exc_fd_set, &max_fd);
		if (mres != CURLM_OK) {
			errorstream<<"curl_multi_fdset"
				<<" returned error code "<<mres
				<<std::endl;
			select_timeout = 0;
		}

		mres = curl_multi_timeout(m_multi, &select_timeout);
		if (mres != CURLM_OK) {
			errorstream<<"curl_multi_timeout"
				<<" returned error code "<<mres
				<<std::endl;
			select_timeout = 0;
		}

		// Limit timeout so new requests get through
		if (select_timeout < 0 || select_timeout > timeout)
			select_timeout = timeout;

		if (select_timeout > 0) {
			// in Winsock it is forbidden to pass three empty
			// fd_sets to select(), so in that case use sleep_ms
			if (max_fd != -1) {
				select_tv.tv_sec = select_timeout / 1000;
				select_tv.tv_usec = (select_timeout % 1000) * 1000;
				int retval = select(max_fd + 1, &read_fd_set,
						&write_fd_set, &exc_fd_set,
						&select_tv);
				if (retval == -1) {
					#ifdef _WIN32
					errorstream<<"select returned error code "
						<<WSAGetLastError()<<std::endl;
					#else
					errorstream<<"select returned error code "
						<<errno<<std::endl;
					#endif
				}
			}
			else {
				sleep_ms(select_timeout);
			}
		}
	}

	void *run()
	{
		DSTACK(FUNCTION_NAME);

		CurlHandlePool pool;

		m_multi = curl_multi_init();
		if (m_multi == NULL) {
			errorstream<<"curl_multi_init returned NULL\n";
			return NULL;
		}

		FATAL_ERROR_IF(!m_all_ongoing.empty(), "Expected empty");

		while (!stopRequested()) {
			BEGIN_DEBUG_EXCEPTION_HANDLER

			/*
				Handle new async requests
			*/

			while (!m_requests.empty()) {
				Request req = m_requests.pop_frontNoEx();
				processRequest(req);
			}
			processQueued(&pool);

			/*
				Handle ongoing async requests
			*/

			int still_ongoing = 0;
			while (curl_multi_perform(m_multi, &still_ongoing) ==
					CURLM_CALL_MULTI_PERFORM)
				/* noop */;

			/*
				Handle completed async requests
			*/
			if (still_ongoing < (int) m_all_ongoing.size()) {
				CURLMsg *msg;
				int msgs_in_queue;
				msg = curl_multi_info_read(m_multi, &msgs_in_queue);
				while (msg != NULL) {
					processCurlMessage(msg);
					msg = curl_multi_info_read(m_multi, &msgs_in_queue);
				}
			}

			/*
				If there are ongoing requests, wait for data
				(with a timeout of 100ms so that new requests
				can be processed).

				If no ongoing requests, wait for a new request.
				(Possibly an empty request that signals
				that the thread should be stopped.)
			*/
			if (m_all_ongoing.empty())
				waitForRequest(100000000);
			else
				waitForIO(100);

			END_DEBUG_EXCEPTION_HANDLER
		}

		// Call curl_multi_remove_handle and cleanup easy handles
		for (size_t i = 0; i < m_all_ongoing.size(); ++i) {
			delete m_all_ongoing[i];
		}
		m_all_ongoing.clear();

		m_queued_fetches.clear();

		CURLMcode mres = curl_multi_cleanup(m_multi);
		if (mres != CURLM_OK) {
			errorstream<<"curl_multi_cleanup"
				<<" returned error code "<<mres
				<<std::endl;
		}

		return NULL;
	}
};

CurlFetchThread *g_httpfetch_thread = NULL;

void httpfetch_init(int parallel_limit)
{
	verbosestream<<"httpfetch_init: parallel_limit="<<parallel_limit
			<<std::endl;

	CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT);
	FATAL_ERROR_IF(res != CURLE_OK, "CURL init failed");

	g_httpfetch_thread = new CurlFetchThread(parallel_limit);

	// Initialize g_callerid_randomness for httpfetch_caller_alloc_secure
	u64 randbuf[2];
	porting::secure_rand_fill_buf(randbuf, sizeof(u64) * 2);
	g_callerid_randomness = PcgRandom(randbuf[0], randbuf[1]);
}

void httpfetch_cleanup()
{
	verbosestream<<"httpfetch_cleanup: cleaning up"<<std::endl;

	g_httpfetch_thread->stop();
	g_httpfetch_thread->requestWakeUp();
	g_httpfetch_thread->wait();
	delete g_httpfetch_thread;

	curl_global_cleanup();
}

void httpfetch_async(const HTTPFetchRequest &fetch_request)
{
	g_httpfetch_thread->requestFetch(fetch_request);
	if (!g_httpfetch_thread->isRunning())
		g_httpfetch_thread->start();
}

static void httpfetch_request_clear(unsigned long caller)
{
	if (g_httpfetch_thread->isRunning()) {
		Event event;
		g_httpfetch_thread->requestClear(caller, &event);
		event.wait();
	} else {
		g_httpfetch_thread->requestClear(caller, NULL);
	}
}

void httpfetch_sync(const HTTPFetchRequest &fetch_request,
		HTTPFetchResult &fetch_result)
{
	// Create ongoing fetch data and make a cURL handle
	// Set cURL options based on HTTPFetchRequest
	CurlHandlePool pool;
	HTTPFetchOngoing ongoing(fetch_request, &pool);
	// Do the fetch (curl_easy_perform)
	CURLcode res = ongoing.start(NULL);
	// Update fetch result
	fetch_result = *ongoing.complete(res);
}

#else  // USE_CURL

/*
	USE_CURL is off:

	Dummy httpfetch implementation that always returns an error.
*/

void httpfetch_init(int parallel_limit)
{
}

void httpfetch_cleanup()
{
}

void httpfetch_async(const HTTPFetchRequest &fetch_request)
{
	errorstream << "httpfetch_async: unable to fetch " << fetch_request.url
			<< " because USE_CURL=0" << std::endl;

	HTTPFetchResult fetch_result(fetch_request); // sets succeeded = false etc.
	httpfetch_deliver_result(fetch_result);
}

static void httpfetch_request_clear(unsigned long caller)
{
}

void httpfetch_sync(const HTTPFetchRequest &fetch_request,
		HTTPFetchResult &fetch_result)
{
	errorstream << "httpfetch_sync: unable to fetch " << fetch_request.url
			<< " because USE_CURL=0" << std::endl;

	fetch_result = HTTPFetchResult(fetch_request); // sets succeeded = false etc.
}

#endif  // USE_CURL


code:
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program 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 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef HTTPFETCH_HEADER
#define HTTPFETCH_HEADER

#include <vector>
#include "util/string.h"
#include "config.h"

// Can be used in place of "caller" in asynchronous transfers to discard result
// (used as default value of "caller")
#define HTTPFETCH_DISCARD 0
#define HTTPFETCH_SYNC 1

struct HTTPFetchRequest
{
	std::string url;

	// Identifies the caller (for asynchronous requests)
	// Ignored by httpfetch_sync
	unsigned long caller;

	// Some number that identifies the request
	// (when the same caller issues multiple httpfetch_async calls)
	unsigned long request_id;

	// Timeout for the whole transfer, in milliseconds
	long timeout;

	// Timeout for the connection phase, in milliseconds
	long connect_timeout;

	// Indicates if this is multipart/form-data or
	// application/x-www-form-urlencoded.  POST-only.
	bool multipart;

	// POST fields.  Fields are escaped properly.
	// If this is empty a GET request is done instead.
	StringMap post_fields;

	// Raw POST data, overrides post_fields.
	std::string post_data;

	// If not empty, should contain entries such as "Accept: text/html"
	std::vector<std::string> extra_headers;

	// useragent to use
	std::string useragent;

	HTTPFetchRequest();
};

struct HTTPFetchResult
{
	bool succeeded;
	bool timeout;
	long response_code;
	std::string data;
	// The caller and request_id from the corresponding HTTPFetchRequest.
	unsigned long caller;
	unsigned long request_id;

	HTTPFetchResult()
	    : succeeded(false), timeout(false), response_code(0), data(""),
	      caller(HTTPFETCH_DISCARD), request_id(0)
	{
	}

	HTTPFetchResult(const HTTPFetchRequest &fetch_request)
	    : succeeded(false), timeout(false), response_code(0), data(""),
	      caller(fetch_request.caller), request_id(fetch_request.request_id)
	{
	}
};

// Initializes the httpfetch module
void httpfetch_init(int parallel_limit);

// Stops the httpfetch thread and cleans up resources
void httpfetch_cleanup();

// Starts an asynchronous HTTP fetch request
void httpfetch_async(const HTTPFetchRequest &fetch_request);

// If any fetch for the given caller ID is complete, removes it from the
// result queue, sets the fetch result and returns true. Otherwise returns false.
bool httpfetch_async_get(unsigned long caller, HTTPFetchResult &fetch_result);

// Allocates a caller ID for httpfetch_async
// Not required if you want to set caller = HTTPFETCH_DISCARD
unsigned long httpfetch_caller_alloc();

// Allocates a non-predictable caller ID for httpfetch_async
unsigned long httpfetch_caller_alloc_secure();

// Frees a caller ID allocated with httpfetch_caller_alloc
// Note: This can be expensive, because the httpfetch thread is told
// to stop any ongoing fetches for the given caller.
void httpfetch_caller_free(unsigned long caller);

// Performs a synchronous HTTP request. This blocks and therefore should
// only be used from background threads.
void httpfetch_sync(const HTTPFetchRequest &fetch_request, HTTPFetchResult &fetch_result);

#endif // !HTTPFETCH_HEADER

repo_name: pgimeno/minetest

license: unknown


code:
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2010-2013 blue42u, Jonathon Anderson <anderjon@umail.iu.edu>
Copyright (C) 2010-2013 kwolekr, Ryan Kwolek <kwolekr@minetest.net>

This program 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 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "hud.h"
#include "settings.h"
#include "util/numeric.h"
#include "log.h"
#include "client.h"
#include "inventory.h"
#include "client/tile.h"
#include "localplayer.h"
#include "camera.h"
#include "porting.h"
#include "fontengine.h"
#include "guiscalingfilter.h"
#include "mesh.h"
#include "wieldmesh.h"
#include <IGUIStaticText.h>

#ifdef HAVE_TOUCHSCREENGUI
#include "touchscreengui.h"
#endif

Hud::Hud(video::IVideoDriver *driver, scene::ISceneManager* smgr,
		gui::IGUIEnvironment* guienv, Client *client, LocalPlayer *player,
		Inventory *inventory)
{
	this->driver      = driver;
	this->smgr        = smgr;
	this->guienv      = guienv;
	this->client      = client;
	this->player      = player;
	this->inventory   = inventory;

	m_hud_scaling      = g_settings->getFloat("hud_scaling");
	m_screensize       = v2u32(0, 0);
	m_displaycenter    = v2s32(0, 0);
	m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE * porting::getDisplayDensity() + 0.5);
	m_hotbar_imagesize *= m_hud_scaling;
	m_padding = m_hotbar_imagesize / 12;

	for (unsigned int i = 0; i < 4; i++)
		hbar_colors[i] = video::SColor(255, 255, 255, 255);

	tsrc = client->getTextureSource();

	v3f crosshair_color = g_settings->getV3F("crosshair_color");
	u32 cross_r = rangelim(myround(crosshair_color.X), 0, 255);
	u32 cross_g = rangelim(myround(crosshair_color.Y), 0, 255);
	u32 cross_b = rangelim(myround(crosshair_color.Z), 0, 255);
	u32 cross_a = rangelim(g_settings->getS32("crosshair_alpha"), 0, 255);
	crosshair_argb = video::SColor(cross_a, cross_r, cross_g, cross_b);

	v3f selectionbox_color = g_settings->getV3F("selectionbox_color");
	u32 sbox_r = rangelim(myround(selectionbox_color.X), 0, 255);
	u32 sbox_g = rangelim(myround(selectionbox_color.Y), 0, 255);
	u32 sbox_b = rangelim(myround(selectionbox_color.Z), 0, 255);
	selectionbox_argb = video::SColor(255, sbox_r, sbox_g, sbox_b);

	use_crosshair_image = tsrc->isKnownSourceImage("crosshair.png");

	hotbar_image = "";
	use_hotbar_image = false;
	hotbar_selected_image = "";
	use_hotbar_selected_image = false;

	m_selection_mesh = NULL;
	m_selection_boxes.clear();
	m_halo_boxes.clear();

	m_selection_pos = v3f(0.0, 0.0, 0.0);
	std::string mode_setting = g_settings->get("node_highlighting");

	if (mode_setting == "halo") {
		m_mode = HIGHLIGHT_HALO;
	} else if (mode_setting == "none") {
		m_mode = HIGHLIGHT_NONE;
	} else {
		m_mode = HIGHLIGHT_BOX;
	}

	m_selection_material.Lighting = false;

	if (g_settings->getBool("enable_shaders")) {
		IShaderSource *shdrsrc = client->getShaderSource();
		u16 shader_id = shdrsrc->getShader(
			m_mode == HIGHLIGHT_HALO ? "selection_shader" : "default_shader", 1, 1);
		m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material;
	} else {
		m_selection_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
	}

	if (m_mode == HIGHLIGHT_BOX) {
		m_selection_material.Thickness =
			rangelim(g_settings->getS16("selectionbox_width"), 1, 5);
	} else if (m_mode == HIGHLIGHT_HALO) {
		m_selection_material.setTexture(0, tsrc->getTextureForMesh("halo.png"));
		m_selection_material.setFlag(video::EMF_BACK_FACE_CULLING, true);
	} else {
		m_selection_material.MaterialType = video::EMT_SOLID;
	}
}

Hud::~Hud()
{
	if (m_selection_mesh)
		m_selection_mesh->drop();
}

void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect,
		bool selected)
{
	if (selected) {
			/* draw hihlighting around selected item */
			if (use_hotbar_selected_image) {
				core::rect<s32> imgrect2 = rect;
				imgrect2.UpperLeftCorner.X  -= (m_padding*2);
				imgrect2.UpperLeftCorner.Y  -= (m_padding*2);
				imgrect2.LowerRightCorner.X += (m_padding*2);
				imgrect2.LowerRightCorner.Y += (m_padding*2);
					video::ITexture *texture = tsrc->getTexture(hotbar_selected_image);
					core::dimension2di imgsize(texture->getOriginalSize());
				draw2DImageFilterScaled(driver, texture, imgrect2,
						core::rect<s32>(core::position2d<s32>(0,0), imgsize),
						NULL, hbar_colors, true);
			} else {
				video::SColor c_outside(255,255,0,0);
				//video::SColor c_outside(255,0,0,0);
				//video::SColor c_inside(255,192,192,192);
				s32 x1 = rect.UpperLeftCorner.X;
				s32 y1 = rect.UpperLeftCorner.Y;
				s32 x2 = rect.LowerRightCorner.X;
				s32 y2 = rect.LowerRightCorner.Y;
				// Black base borders
				driver->draw2DRectangle(c_outside,
					core::rect<s32>(
					v2s32(x1 - m_padding, y1 - m_padding),
					v2s32(x2 + m_padding, y1)
					), NULL);
				driver->draw2DRectangle(c_outside,
					core::rect<s32>(
					v2s32(x1 - m_padding, y2),
					v2s32(x2 + m_padding, y2 + m_padding)
					), NULL);
				driver->draw2DRectangle(c_outside,
					core::rect<s32>(
					v2s32(x1 - m_padding, y1),
						v2s32(x1, y2)
					), NULL);
				driver->draw2DRectangle(c_outside,
					core::rect<s32>(
						v2s32(x2, y1),
					v2s32(x2 + m_padding, y2)
					), NULL);
				/*// Light inside borders
				driver->draw2DRectangle(c_inside,
					core::rect<s32>(
						v2s32(x1 - padding/2, y1 - padding/2),
						v2s32(x2 + padding/2, y1)
					), NULL);
				driver->draw2DRectangle(c_inside,
					core::rect<s32>(
						v2s32(x1 - padding/2, y2),
						v2s32(x2 + padding/2, y2 + padding/2)
					), NULL);
				driver->draw2DRectangle(c_inside,
					core::rect<s32>(
						v2s32(x1 - padding/2, y1),
						v2s32(x1, y2)
					), NULL);
				driver->draw2DRectangle(c_inside,
					core::rect<s32>(
						v2s32(x2, y1),
						v2s32(x2 + padding/2, y2)
					), NULL);
				*/
			}
		}

		video::SColor bgcolor2(128, 0, 0, 0);
		if (!use_hotbar_image)
			driver->draw2DRectangle(bgcolor2, rect, NULL);
		drawItemStack(driver, g_fontengine->getFont(), item, rect, NULL,
			client, selected ? IT_ROT_SELECTED : IT_ROT_NONE);
	}

//NOTE: selectitem = 0 -> no selected; selectitem 1-based
void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
		s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction)
{
#ifdef HAVE_TOUCHSCREENGUI
	if (g_touchscreengui && inv_offset == 0)
		g_touchscreengui->resetHud();
#endif

	s32 height  = m_hotbar_imagesize + m_padding * 2;
	s32 width   = (itemcount - inv_offset) * (m_hotbar_imagesize + m_padding * 2);

	if (direction == HUD_DIR_TOP_BOTTOM || direction == HUD_DIR_BOTTOM_TOP) {
		s32 tmp = height;
		height = width;
		width = tmp;
	}

	// Position of upper left corner of bar
	v2s32 pos = screen_offset;
	pos.X *= m_hud_scaling * porting::getDisplayDensity();
	pos.Y *= m_hud_scaling * porting::getDisplayDensity();
	pos += upperleftpos;

	// Store hotbar_image in member variable, used by drawItem()
	if (hotbar_image != player->hotbar_image) {
		hotbar_image = player->hotbar_image;
		if (hotbar_image != "")
			use_hotbar_image = tsrc->isKnownSourceImage(hotbar_image);
		else
			use_hotbar_image = false;
	}

	// Store hotbar_selected_image in member variable, used by drawItem()
	if (hotbar_selected_image != player->hotbar_selected_image) {
		hotbar_selected_image = player->hotbar_selected_image;
		if (hotbar_selected_image != "")
			use_hotbar_selected_image = tsrc->isKnownSourceImage(hotbar_selected_image);
		else
			use_hotbar_selected_image = false;
	}

	// draw customized item background
	if (use_hotbar_image) {
		core::rect<s32> imgrect2(-m_padding/2, -m_padding/2,
			width+m_padding/2, height+m_padding/2);
		core::rect<s32> rect2 = imgrect2 + pos;
		video::ITexture *texture = tsrc->getTexture(hotbar_image);
		core::dimension2di imgsize(texture->getOriginalSize());
		draw2DImageFilterScaled(driver, texture, rect2,
			core::rect<s32>(core::position2d<s32>(0,0), imgsize),
			NULL, hbar_colors, true);
	}

	// Draw items
	core::rect<s32> imgrect(0, 0, m_hotbar_imagesize, m_hotbar_imagesize);
	for (s32 i = inv_offset; i < itemcount && (size_t)i < mainlist->getSize(); i++) {
		s32 fullimglen = m_hotbar_imagesize + m_padding * 2;

		v2s32 steppos;
		switch (direction) {
		case HUD_DIR_RIGHT_LEFT:
			steppos = v2s32(-(m_padding + (i - inv_offset) * fullimglen), m_padding);
			break;
		case HUD_DIR_TOP_BOTTOM:
			steppos = v2s32(m_padding, m_padding + (i - inv_offset) * fullimglen);
			break;
		case HUD_DIR_BOTTOM_TOP:
			steppos = v2s32(m_padding, -(m_padding + (i - inv_offset) * fullimglen));
			break;
		default:
			steppos = v2s32(m_padding + (i - inv_offset) * fullimglen, m_padding);
			break;
		}

		drawItem(mainlist->getItem(i), (imgrect + pos + steppos), (i + 1) == selectitem);

#ifdef HAVE_TOUCHSCREENGUI
		if (g_touchscreengui)
			g_touchscreengui->registerHudItem(i, (imgrect + pos + steppos));
#endif
	}
}

void Hud::drawLuaElements(const v3s16 &camera_offset)
{
	u32 text_height = g_fontengine->getTextHeight();
	irr::gui::IGUIFont* font = g_fontengine->getFont();
	for (size_t i = 0; i != player->maxHudId(); i++) {
		HudElement *e = player->getHud(i);
		if (!e)
			continue;

		v2s32 pos(floor(e->pos.X * (float) m_screensize.X + 0.5),
				floor(e->pos.Y * (float) m_screensize.Y + 0.5));
		switch (e->type) {
			case HUD_ELEM_IMAGE: {
				video::ITexture *texture = tsrc->getTexture(e->text);
				if (!texture)
					continue;

				const video::SColor color(255, 255, 255, 255);
				const video::SColor colors[] = {color, color, color, color};
				core::dimension2di imgsize(texture->getOriginalSize());
				v2s32 dstsize(imgsize.Width * e->scale.X,
				              imgsize.Height * e->scale.Y);
				if (e->scale.X < 0)
					dstsize.X = m_screensize.X * (e->scale.X * -0.01);
				if (e->scale.Y < 0)
					dstsize.Y = m_screensize.Y * (e->scale.Y * -0.01);
				v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
				             (e->align.Y - 1.0) * dstsize.Y / 2);
				core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
				rect += pos + offset + v2s32(e->offset.X, e->offset.Y);
				draw2DImageFilterScaled(driver, texture, rect,
					core::rect<s32>(core::position2d<s32>(0,0), imgsize),
					NULL, colors, true);
				break; }
			case HUD_ELEM_TEXT: {
				video::SColor color(255, (e->number >> 16) & 0xFF,
										 (e->number >> 8)  & 0xFF,
										 (e->number >> 0)  & 0xFF);
				core::rect<s32> size(0, 0, e->scale.X, text_height * e->scale.Y);
				std::wstring text = unescape_enriched(utf8_to_wide(e->text));
				core::dimension2d<u32> textsize = font->getDimension(text.c_str());
				v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2),
				             (e->align.Y - 1.0) * (textsize.Height / 2));
				v2s32 offs(e->offset.X, e->offset.Y);
				font->draw(text.c_str(), size + pos + offset + offs, color);
				break; }
			case HUD_ELEM_STATBAR: {
				v2s32 offs(e->offset.X, e->offset.Y);
				drawStatbar(pos, HUD_CORNER_UPPER, e->dir, e->text, e->number, offs, e->size);
				break; }
			case HUD_ELEM_INVENTORY: {
				InventoryList *inv = inventory->getList(e->text);
				drawItems(pos, v2s32(e->offset.X, e->offset.Y), e->number, 0,
					inv, e->item, e->dir);
				break; }
			case HUD_ELEM_WAYPOINT: {
				v3f p_pos = player->getPosition() / BS;
				v3f w_pos = e->world_pos * BS;
				float distance = floor(10 * p_pos.getDistanceFrom(e->world_pos)) / 10;
				scene::ICameraSceneNode* camera = smgr->getActiveCamera();
				w_pos -= intToFloat(camera_offset, BS);
				core::matrix4 trans = camera->getProjectionMatrix();
				trans *= camera->getViewMatrix();
				f32 transformed_pos[4] = { w_pos.X, w_pos.Y, w_pos.Z, 1.0f };
				trans.multiplyWith1x4Matrix(transformed_pos);
				if (transformed_pos[3] < 0)
					break;
				f32 zDiv = transformed_pos[3] == 0.0f ? 1.0f :
					core::reciprocal(transformed_pos[3]);
				pos.X = m_screensize.X * (0.5 * transformed_pos[0] * zDiv + 0.5);
				pos.Y = m_screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5);
				video::SColor color(255, (e->number >> 16) & 0xFF,
										 (e->number >> 8)  & 0xFF,
										 (e->number >> 0)  & 0xFF);
				core::rect<s32> size(0, 0, 200, 2 * text_height);
				std::wstring text = unescape_enriched(utf8_to_wide(e->name));
				font->draw(text.c_str(), size + pos, color);
				std::ostringstream os;
				os << distance << e->text;
				text = unescape_enriched(utf8_to_wide(os.str()));
				pos.Y += text_height;
				font->draw(text.c_str(), size + pos, color);
				break; }
			default:
				infostream << "Hud::drawLuaElements: ignoring drawform " << e->type <<
					" of hud element ID " << i << " due to unrecognized type" << std::endl;
		}
	}
}

void Hud::drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture,
		s32 count, v2s32 offset, v2s32 size)
{
	const video::SColor color(255, 255, 255, 255);
	const video::SColor colors[] = {color, color, color, color};

	video::ITexture *stat_texture = tsrc->getTexture(texture);
	if (!stat_texture)
		return;

	core::dimension2di srcd(stat_texture->getOriginalSize());
	core::dimension2di dstd;
	if (size == v2s32()) {
		dstd = srcd;
	} else {
		float size_factor = m_hud_scaling * porting::getDisplayDensity();
		dstd.Height = size.Y * size_factor;
		dstd.Width  = size.X * size_factor;
		offset.X *= size_factor;
		offset.Y *= size_factor;
	}

	v2s32 p = pos;
	if (corner & HUD_CORNER_LOWER)
		p -= dstd.Height;

	p += offset;

	v2s32 steppos;
	core::rect<s32> srchalfrect, dsthalfrect;
	switch (drawdir) {
		case HUD_DIR_RIGHT_LEFT:
			steppos = v2s32(-1, 0);
			srchalfrect = core::rect<s32>(srcd.Width / 2, 0, srcd.Width, srcd.Height);
			dsthalfrect = core::rect<s32>(dstd.Width / 2, 0, dstd.Width, dstd.Height);
			break;
		case HUD_DIR_TOP_BOTTOM:
			steppos = v2s32(0, 1);
			srchalfrect = core::rect<s32>(0, 0, srcd.Width, srcd.Height / 2);
			dsthalfrect = core::rect<s32>(0, 0, dstd.Width, dstd.Height / 2);
			break;
		case HUD_DIR_BOTTOM_TOP:
			steppos = v2s32(0, -1);
			srchalfrect = core::rect<s32>(0, srcd.Height / 2, srcd.Width, srcd.Height);
			dsthalfrect = core::rect<s32>(0, dstd.Height / 2, dstd.Width, dstd.Height);
			break;
		default:
			steppos = v2s32(1, 0);
			srchalfrect = core::rect<s32>(0, 0, srcd.Width / 2, srcd.Height);
			dsthalfrect = core::rect<s32>(0, 0, dstd.Width / 2, dstd.Height);
	}
	steppos.X *= dstd.Width;
	steppos.Y *= dstd.Height;

	for (s32 i = 0; i < count / 2; i++) {
		core::rect<s32> srcrect(0, 0, srcd.Width, srcd.Height);
		core::rect<s32> dstrect(0,0, dstd.Width, dstd.Height);

		dstrect += p;
		draw2DImageFilterScaled(driver, stat_texture, dstrect, srcrect, NULL, colors, true);
		p += steppos;
	}

	if (count % 2 == 1) {
		dsthalfrect += p;
		draw2DImageFilterScaled(driver, stat_texture, dsthalfrect, srchalfrect, NULL, colors, true);
	}
}

void Hud::drawHotbar(u16 playeritem) {

	v2s32 centerlowerpos(m_displaycenter.X, m_screensize.Y);

	InventoryList *mainlist = inventory->getList("main");
	if (mainlist == NULL) {
		//silently ignore this we may not be initialized completely
		return;
	}

	s32 hotbar_itemcount = player->hud_hotbar_itemcount;
	s32 width = hotbar_itemcount * (m_hotbar_imagesize + m_padding * 2);
	v2s32 pos = centerlowerpos - v2s32(width / 2, m_hotbar_imagesize + m_padding * 3);

	if ( (float) width / (float) porting::getWindowSize().X <=
			g_settings->getFloat("hud_hotbar_max_width")) {
		if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
			drawItems(pos, v2s32(0, 0), hotbar_itemcount, 0, mainlist, playeritem + 1, 0);
		}
	} else {
		pos.X += width/4;

		v2s32 secondpos = pos;
		pos = pos - v2s32(0, m_hotbar_imagesize + m_padding);

		if (player->hud_flags & HUD_FLAG_HOTBAR_VISIBLE) {
			drawItems(pos, v2s32(0, 0), hotbar_itemcount / 2, 0,
				mainlist, playeritem + 1, 0);
			drawItems(secondpos, v2s32(0, 0), hotbar_itemcount,
				hotbar_itemcount / 2, mainlist, playeritem + 1, 0);
		}
	}

	//////////////////////////// compatibility code to be removed //////////////
	// this is ugly as hell but there's no other way to keep compatibility to
	// old servers
	if ((player->hud_flags & HUD_FLAG_HEALTHBAR_VISIBLE)) {
		drawStatbar(v2s32(floor(0.5 * (float)m_screensize.X + 0.5),
			floor(1 * (float) m_screensize.Y + 0.5)),
			HUD_CORNER_UPPER, 0, "heart.png",
			player->hp, v2s32((-10*24)-25,-(48+24+10)), v2s32(24,24));
	}

	if ((player->hud_flags & HUD_FLAG_BREATHBAR_VISIBLE) &&
			(player->getBreath() < 11)) {
		drawStatbar(v2s32(floor(0.5 * (float)m_screensize.X + 0.5),
			floor(1 * (float) m_screensize.Y + 0.5)),
			HUD_CORNER_UPPER, 0, "bubble.png",
			player->getBreath(), v2s32(25,-(48+24+10)), v2s32(24,24));
	}
	////////////////////////////////////////////////////////////////////////////
}

void Hud::drawCrosshair()
{
	if (use_crosshair_image) {
		video::ITexture *crosshair = tsrc->getTexture("crosshair.png");
		v2u32 size  = crosshair->getOriginalSize();
		v2s32 lsize = v2s32(m_displaycenter.X - (size.X / 2),
				m_displaycenter.Y - (size.Y / 2));
		driver->draw2DImage(crosshair, lsize,
				core::rect<s32>(0, 0, size.X, size.Y),
				0, crosshair_argb, true);
	} else {
		driver->draw2DLine(m_displaycenter - v2s32(10, 0),
				m_displaycenter + v2s32(10, 0), crosshair_argb);
		driver->draw2DLine(m_displaycenter - v2s32(0, 10),
				m_displaycenter + v2s32(0, 10), crosshair_argb);
	}
}

void Hud::setSelectionPos(const v3f &pos, const v3s16 &camera_offset)
{
	m_camera_offset = camera_offset;
	m_selection_pos = pos;
	m_selection_pos_with_offset = pos - intToFloat(camera_offset, BS);
}

void Hud::drawSelectionMesh()
{
	if (m_mode == HIGHLIGHT_BOX) {
		// Draw 3D selection boxes
		video::SMaterial oldmaterial = driver->getMaterial2D();
		driver->setMaterial(m_selection_material);
		for (std::vector<aabb3f>::const_iterator
				i = m_selection_boxes.begin();
				i != m_selection_boxes.end(); ++i) {
			aabb3f box = aabb3f(
				i->MinEdge + m_selection_pos_with_offset,
				i->MaxEdge + m_selection_pos_with_offset);

			u32 r = (selectionbox_argb.getRed() *
					m_selection_mesh_color.getRed() / 255);
			u32 g = (selectionbox_argb.getGreen() *
					m_selection_mesh_color.getGreen() / 255);
			u32 b = (selectionbox_argb.getBlue() *
					m_selection_mesh_color.getBlue() / 255);
			driver->draw3DBox(box, video::SColor(255, r, g, b));
		}
		driver->setMaterial(oldmaterial);
	} else if (m_mode == HIGHLIGHT_HALO && m_selection_mesh) {
		// Draw selection mesh
		video::SMaterial oldmaterial = driver->getMaterial2D();
		driver->setMaterial(m_selection_material);
		setMeshColor(m_selection_mesh, m_selection_mesh_color);
		video::SColor face_color(0,
			MYMIN(255, m_selection_mesh_color.getRed() * 1.5),
			MYMIN(255, m_selection_mesh_color.getGreen() * 1.5),
			MYMIN(255, m_selection_mesh_color.getBlue() * 1.5));
		setMeshColorByNormal(m_selection_mesh, m_selected_face_normal,
			face_color);
		scene::IMesh* mesh = cloneMesh(m_selection_mesh);
		translateMesh(mesh, m_selection_pos_with_offset);
		u32 mc = m_selection_mesh->getMeshBufferCount();
		for (u32 i = 0; i < mc; i++) {
			scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
			driver->drawMeshBuffer(buf);
		}
		mesh->drop();
		driver->setMaterial(oldmaterial);
	}
}

void Hud::updateSelectionMesh(const v3s16 &camera_offset)
{
	m_camera_offset = camera_offset;
	if (m_mode != HIGHLIGHT_HALO)
		return;

	if (m_selection_mesh) {
		m_selection_mesh->drop();
		m_selection_mesh = NULL;
	}

	if (!m_selection_boxes.size()) {
		// No pointed object
		return;
	}

	// New pointed object, create new mesh.

	// Texture UV coordinates for selection boxes
	static f32 texture_uv[24] = {
		0,0,1,1,
		0,0,1,1,
		0,0,1,1,
		0,0,1,1,
		0,0,1,1,
		0,0,1,1
	};

	// Use single halo box instead of multiple overlapping boxes.
	// Temporary solution - problem can be solved with multiple
	// rendering targets, or some method to remove inner surfaces.
	// Thats because of halo transparency.

	aabb3f halo_box(100.0, 100.0, 100.0, -100.0, -100.0, -100.0);
	m_halo_boxes.clear();

	for (std::vector<aabb3f>::iterator
			i = m_selection_boxes.begin();
			i != m_selection_boxes.end(); ++i) {
		halo_box.addInternalBox(*i);
	}

	m_halo_boxes.push_back(halo_box);
	m_selection_mesh = convertNodeboxesToMesh(
		m_halo_boxes, texture_uv, 0.5);
}

void Hud::resizeHotbar() {
	if (m_screensize != porting::getWindowSize()) {
		m_hotbar_imagesize = floor(HOTBAR_IMAGE_SIZE * porting::getDisplayDensity() + 0.5);
		m_hotbar_imagesize *= m_hud_scaling;
		m_padding = m_hotbar_imagesize / 12;
		m_screensize = porting::getWindowSize();
		m_displaycenter = v2s32(m_screensize.X/2,m_screensize.Y/2);
	}
}

struct MeshTimeInfo {
	u64 time;
	scene::IMesh *mesh;
};

void drawItemStack(video::IVideoDriver *driver,
		gui::IGUIFont *font,
		const ItemStack &item,
		const core::rect<s32> &rect,
		const core::rect<s32> *clip,
		Client *client,
		ItemRotationKind rotation_kind)
{
	static MeshTimeInfo rotation_time_infos[IT_ROT_NONE];
	static bool enable_animations =
		g_settings->getBool("inventory_items_animations");

	if (item.empty()) {
		if (rotation_kind < IT_ROT_NONE) {
			rotation_time_infos[rotation_kind].mesh = NULL;
		}
		return;
	}

	const ItemDefinition &def = item.getDefinition(client->idef());
	ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);

	if (imesh && imesh->mesh) {
		scene::IMesh *mesh = imesh->mesh;
		driver->clearZBuffer();
		s32 delta = 0;
		if (rotation_kind < IT_ROT_NONE) {
			MeshTimeInfo &ti = rotation_time_infos[rotation_kind];
			if (mesh != ti.mesh) {
				ti.mesh = mesh;
				ti.time = porting::getTimeMs();
			} else {
				delta = porting::getDeltaMs(ti.time, porting::getTimeMs()) % 100000;
			}
		}
		core::rect<s32> oldViewPort = driver->getViewPort();
		core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
		core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
		core::matrix4 ProjMatrix;
		ProjMatrix.buildProjectionMatrixOrthoLH(2, 2, -1, 100);
		driver->setTransform(video::ETS_PROJECTION, ProjMatrix);
		driver->setTransform(video::ETS_VIEW, ProjMatrix);
		core::matrix4 matrix;
		matrix.makeIdentity();

		if (enable_animations) {
			float timer_f = (float) delta / 5000.0;
			matrix.setRotationDegrees(core::vector3df(0, 360 * timer_f, 0));
		}

		driver->setTransform(video::ETS_WORLD, matrix);
		driver->setViewPort(rect);

		video::SColor basecolor =
			client->idef()->getItemstackColor(item, client);

		u32 mc = mesh->getMeshBufferCount();
		for (u32 j = 0; j < mc; ++j) {
			scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
			// we can modify vertices relatively fast,
			// because these meshes are not buffered.
			assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
			video::SColor c = basecolor;
			if (imesh->buffer_colors.size() > j) {
				ItemPartColor *p = &imesh->buffer_colors[j];
				if (p->override_base)
					c = p->color;
			}
			if (imesh->needs_shading)
				colorizeMeshBuffer(buf, &c);
			else
				setMeshBufferColor(buf, c);
			video::SMaterial &material = buf->getMaterial();
			material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
			material.Lighting = false;
			driver->setMaterial(material);
			driver->drawMeshBuffer(buf);
		}

		driver->setTransform(video::ETS_VIEW, oldViewMat);
		driver->setTransform(video::ETS_PROJECTION, oldProjMat);
		driver->setViewPort(oldViewPort);
	}

	if(def.type == ITEM_TOOL && item.wear != 0)
	{
		// Draw a progressbar
		float barheight = rect.getHeight()/16;
		float barpad_x = rect.getWidth()/16;
		float barpad_y = rect.getHeight()/16;
		core::rect<s32> progressrect(
			rect.UpperLeftCorner.X + barpad_x,
			rect.LowerRightCorner.Y - barpad_y - barheight,
			rect.LowerRightCorner.X - barpad_x,
			rect.LowerRightCorner.Y - barpad_y);

		// Shrink progressrect by amount of tool damage
		float wear = item.wear / 65535.0;
		int progressmid =
			wear * progressrect.UpperLeftCorner.X +
			(1-wear) * progressrect.LowerRightCorner.X;

		// Compute progressbar color
		//   wear = 0.0: green
		//   wear = 0.5: yellow
		//   wear = 1.0: red
		video::SColor color(255,255,255,255);
		int wear_i = MYMIN(floor(wear * 600), 511);
		wear_i = MYMIN(wear_i + 10, 511);
		if(wear_i <= 255)
			color.set(255, wear_i, 255, 0);
		else
			color.set(255, 255, 511-wear_i, 0);

		core::rect<s32> progressrect2 = progressrect;
		progressrect2.LowerRightCorner.X = progressmid;
		driver->draw2DRectangle(color, progressrect2, clip);

		color = video::SColor(255,0,0,0);
		progressrect2 = progressrect;
		progressrect2.UpperLeftCorner.X = progressmid;
		driver->draw2DRectangle(color, progressrect2, clip);
	}

	if(font != NULL && item.count >= 2)
	{
		// Get the item count as a string
		std::string text = itos(item.count);
		v2u32 dim = font->getDimension(utf8_to_wide(text).c_str());
		v2s32 sdim(dim.X,dim.Y);

		core::rect<s32> rect2(
			/*rect.UpperLeftCorner,
			core::dimension2d<u32>(rect.getWidth(), 15)*/
			rect.LowerRightCorner - sdim,
			sdim
		);

		video::SColor bgcolor(128,0,0,0);
		driver->draw2DRectangle(bgcolor, rect2, clip);

		video::SColor color(255,255,255,255);
		font->draw(text.c_str(), rect2, color, false, false, clip);
	}
}


code:
/*
Minetest
Copyright (C) 2010-2013 kwolekr, Ryan Kwolek <kwolekr@minetest.net>

This program 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 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef HUD_HEADER
#define HUD_HEADER

#include "irrlichttypes_extrabloated.h"
#include <string>

#define HUD_DIR_LEFT_RIGHT 0
#define HUD_DIR_RIGHT_LEFT 1
#define HUD_DIR_TOP_BOTTOM 2
#define HUD_DIR_BOTTOM_TOP 3

#define HUD_CORNER_UPPER  0
#define HUD_CORNER_LOWER  1
#define HUD_CORNER_CENTER 2

// Note that these visibility flags do not determine if the hud items are
// actually drawn, but rather, whether to draw the item should the rest
// of the game state permit it.
#define HUD_FLAG_HOTBAR_VISIBLE    (1 << 0)
#define HUD_FLAG_HEALTHBAR_VISIBLE (1 << 1)
#define HUD_FLAG_CROSSHAIR_VISIBLE (1 << 2)
#define HUD_FLAG_WIELDITEM_VISIBLE (1 << 3)
#define HUD_FLAG_BREATHBAR_VISIBLE (1 << 4)
#define HUD_FLAG_MINIMAP_VISIBLE   (1 << 5)

#define HUD_PARAM_HOTBAR_ITEMCOUNT 1
#define HUD_PARAM_HOTBAR_IMAGE 2
#define HUD_PARAM_HOTBAR_SELECTED_IMAGE 3

#define HUD_HOTBAR_ITEMCOUNT_DEFAULT 8
#define HUD_HOTBAR_ITEMCOUNT_MAX     23

#define HOTBAR_IMAGE_SIZE 48

enum HudElementType {
	HUD_ELEM_IMAGE     = 0,
	HUD_ELEM_TEXT      = 1,
	HUD_ELEM_STATBAR   = 2,
	HUD_ELEM_INVENTORY = 3,
	HUD_ELEM_WAYPOINT  = 4,
};

enum HudElementStat {
	HUD_STAT_POS = 0,
	HUD_STAT_NAME,
	HUD_STAT_SCALE,
	HUD_STAT_TEXT,
	HUD_STAT_NUMBER,
	HUD_STAT_ITEM,
	HUD_STAT_DIR,
	HUD_STAT_ALIGN,
	HUD_STAT_OFFSET,
	HUD_STAT_WORLD_POS,
	HUD_STAT_SIZE
};

struct HudElement {
	HudElementType type;
	v2f pos;
	std::string name;
	v2f scale;
	std::string text;
	u32 number;
	u32 item;
	u32 dir;
	v2f align;
	v2f offset;
	v3f world_pos;
	v2s32 size;
};

#ifndef SERVER

#include <vector>
#include <IGUIFont.h>
#include "irr_aabb3d.h"

class Client;
class ITextureSource;
class Inventory;
class InventoryList;
class LocalPlayer;
struct ItemStack;

class Hud {
public:
	video::IVideoDriver *driver;
	scene::ISceneManager* smgr;
	gui::IGUIEnvironment *guienv;
	Client *client;
	LocalPlayer *player;
	Inventory *inventory;
	ITextureSource *tsrc;

	video::SColor crosshair_argb;
	video::SColor selectionbox_argb;
	bool use_crosshair_image;
	std::string hotbar_image;
	bool use_hotbar_image;
	std::string hotbar_selected_image;
	bool use_hotbar_selected_image;

	Hud(video::IVideoDriver *driver,scene::ISceneManager* smgr,
		gui::IGUIEnvironment* guienv, Client *client, LocalPlayer *player,
		Inventory *inventory);
	~Hud();

	void drawHotbar(u16 playeritem);
	void resizeHotbar();
	void drawCrosshair();
	void drawSelectionMesh();
	void updateSelectionMesh(const v3s16 &camera_offset);

	std::vector<aabb3f> *getSelectionBoxes()
	{ return &m_selection_boxes; }

	void setSelectionPos(const v3f &pos, const v3s16 &camera_offset);

	v3f getSelectionPos() const
	{ return m_selection_pos; }

	void setSelectionMeshColor(const video::SColor &color)
	{ m_selection_mesh_color = color; }

	void setSelectedFaceNormal(const v3f &face_normal)
	{ m_selected_face_normal = face_normal; }

	void drawLuaElements(const v3s16 &camera_offset);

private:
	void drawStatbar(v2s32 pos, u16 corner, u16 drawdir, std::string texture,
			s32 count, v2s32 offset, v2s32 size=v2s32());

	void drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
		s32 inv_offset, InventoryList *mainlist, u16 selectitem, u16 direction);

	void drawItem(const ItemStack &item, const core::rect<s32>& rect,
		bool selected);

	float m_hud_scaling; // cached minetest setting
	v3s16 m_camera_offset;
	v2u32 m_screensize;
	v2s32 m_displaycenter;
	s32 m_hotbar_imagesize; // Takes hud_scaling into account, updated by resizeHotbar()
	s32 m_padding;  // Takes hud_scaling into account, updated by resizeHotbar()
	video::SColor hbar_colors[4];

	std::vector<aabb3f> m_selection_boxes;
	std::vector<aabb3f> m_halo_boxes;
	v3f m_selection_pos;
	v3f m_selection_pos_with_offset;

	scene::IMesh* m_selection_mesh;
	video::SColor m_selection_mesh_color;
	v3f m_selected_face_normal;

	video::SMaterial m_selection_material;

	enum {
		HIGHLIGHT_BOX,
		HIGHLIGHT_HALO,
		HIGHLIGHT_NONE } m_mode;
};

enum ItemRotationKind {
	IT_ROT_SELECTED,
	IT_ROT_HOVERED,
	IT_ROT_DRAGGED,
	IT_ROT_NONE, // Must be last, also serves as number
};

void drawItemStack(video::IVideoDriver *driver,
		gui::IGUIFont *font,
		const ItemStack &item,
		const core::rect<s32> &rect,
		const core::rect<s32> *clip,
		Client *client,
		ItemRotationKind rotation_kind);

#endif

#endif


code:
/*
Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>

This program 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 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "imagefilters.h"
#include "util/numeric.h"
#include <math.h>

/* Fill in RGB values for transparent pixels, to correct for odd colors
 * appearing at borders when blending.  This is because many PNG optimizers
 * like to discard RGB values of transparent pixels, but when blending then
 * with non-transparent neighbors, their RGB values will shpw up nonetheless.
 *
 * This function modifies the original image in-place.
 *
 * Parameter "threshold" is the alpha level below which pixels are considered
 * transparent.  Should be 127 for 3d where alpha is threshold, but 0 for
 * 2d where alpha is blended.
 */
void imageCleanTransparent(video::IImage *src, u32 threshold)
{
	core::dimension2d<u32> dim = src->getDimension();

	// Walk each pixel looking for fully transparent ones.
	// Note: loop y around x for better cache locality.
	for (u32 ctry = 0; ctry < dim.Height; ctry++)
	for (u32 ctrx = 0; ctrx < dim.Width; ctrx++) {

		// Ignore opaque pixels.
		irr::video::SColor c = src->getPixel(ctrx, ctry);
		if (c.getAlpha() > threshold)
			continue;

		// Sample size and total weighted r, g, b values.
		u32 ss = 0, sr = 0, sg = 0, sb = 0;

		// Walk each neighbor pixel (clipped to image bounds).
		for (u32 sy = (ctry < 1) ? 0 : (ctry - 1);
				sy <= (ctry + 1) && sy < dim.Height; sy++)
		for (u32 sx = (ctrx < 1) ? 0 : (ctrx - 1);
				sx <= (ctrx + 1) && sx < dim.Width; sx++) {

			// Ignore transparent pixels.
			irr::video::SColor d = src->getPixel(sx, sy);
			if (d.getAlpha() <= threshold)
				continue;

			// Add RGB values weighted by alpha.
			u32 a = d.getAlpha();
			ss += a;
			sr += a * d.getRed();
			sg += a * d.getGreen();
			sb += a * d.getBlue();
		}

		// If we found any neighbor RGB data, set pixel to average
		// weighted by alpha.
		if (ss > 0) {
			c.setRed(sr / ss);
			c.setGreen(sg / ss);
			c.setBlue(sb / ss);
			src->setPixel(ctrx, ctry, c);
		}
	}
}

/* Scale a region of an image into another image, using nearest-neighbor with
 * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
 * to prevent non-integer scaling ratio artifacts.  Note that this may cause
 * some blending at the edges where pixels don't line up perfectly, but this
 * filter is designed to produce the most accurate results for both upscaling
 * and downscaling.
 */
void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest)
{
	double sx, sy, minsx, maxsx, minsy, maxsy, area, ra, ga, ba, aa, pw, ph, pa;
	u32 dy, dx;
	video::SColor pxl;

	// Cache rectsngle boundaries.
	double sox = srcrect.UpperLeftCorner.X * 1.0;
	double soy = srcrect.UpperLeftCorner.Y * 1.0;
	double sw = srcrect.getWidth() * 1.0;
	double sh = srcrect.getHeight() * 1.0;

	// Walk each destination image pixel.
	// Note: loop y around x for better cache locality.
	core::dimension2d<u32> dim = dest->getDimension();
	for (dy = 0; dy < dim.Height; dy++)
	for (dx = 0; dx < dim.Width; dx++) {

		// Calculate floating-point source rectangle bounds.
		// Do some basic clipping, and for mirrored/flipped rects,
		// make sure min/max are in the right order.
		minsx = sox + (dx * sw / dim.Width);
		minsx = rangelim(minsx, 0, sw);
		maxsx = minsx + sw / dim.Width;
		maxsx = rangelim(maxsx, 0, sw);
		if (minsx > maxsx)
			SWAP(double, minsx, maxsx);
		minsy = soy + (dy * sh / dim.Height);
		minsy = rangelim(minsy, 0, sh);
		maxsy = minsy + sh / dim.Height;
		maxsy = rangelim(maxsy, 0, sh);
		if (minsy > maxsy)
			SWAP(double, minsy, maxsy);

		// Total area, and integral of r, g, b values over that area,
		// initialized to zero, to be summed up in next loops.
		area = 0;
		ra = 0;
		ga = 0;
		ba = 0;
		aa = 0;

		// Loop over the integral pixel positions described by those bounds.
		for (sy = floor(minsy); sy < maxsy; sy++)
		for (sx = floor(minsx); sx < maxsx; sx++) {

			// Calculate width, height, then area of dest pixel
			// that's covered by this source pixel.
			pw = 1;
			if (minsx > sx)
				pw += sx - minsx;
			if (maxsx < (sx + 1))
				pw += maxsx - sx - 1;
			ph = 1;
			if (minsy > sy)
				ph += sy - minsy;
			if (maxsy < (sy + 1))
				ph += maxsy - sy - 1;
			pa = pw * ph;

			// Get source pixel and add it to totals, weighted
			// by covered area and alpha.
			pxl = src->getPixel((u32)sx, (u32)sy);
			area += pa;
			ra += pa * pxl.getRed();
			ga += pa * pxl.getGreen();
			ba += pa * pxl.getBlue();
			aa += pa * pxl.getAlpha();
		}

		// Set the destination image pixel to the average color.
		if (area > 0) {
			pxl.setRed(ra / area + 0.5);
			pxl.setGreen(ga / area + 0.5);
			pxl.setBlue(ba / area + 0.5);
			pxl.setAlpha(aa / area + 0.5);
		} else {
			pxl.setRed(0);
			pxl.setGreen(0);
			pxl.setBlue(0);
			pxl.setAlpha(0);
		}
		dest->setPixel(dx, dy, pxl);
	}
}


code:
/*
Copyright (C) 2015 Aaron Suen <warr1024@gmail.com>

This program 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 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef _IMAGE_FILTERS_H_
#define _IMAGE_FILTERS_H_

#include "irrlichttypes_extrabloated.h"

/* Fill in RGB values for transparent pixels, to correct for odd colors
 * appearing at borders when blending.  This is because many PNG optimizers
 * like to discard RGB values of transparent pixels, but when blending then
 * with non-transparent neighbors, their RGB values will shpw up nonetheless.
 *
 * This function modifies the original image in-place.
 *
 * Parameter "threshold" is the alpha level below which pixels are considered
 * transparent.  Should be 127 for 3d where alpha is threshold, but 0 for
 * 2d where alpha is blended.
 */
void imageCleanTransparent(video::IImage *src, u32 threshold);

/* Scale a region of an image into another image, using nearest-neighbor with
 * anti-aliasing; treat pixels as crisp rectangles, but blend them at boundaries
 * to prevent non-integer scaling ratio artifacts.  Note that this may cause
 * some blending at the edges where pixels don't line up perfectly, but this
 * filter is designed to produce the most accurate results for both upscaling
 * and downscaling.
 */
void imageScaleNNAA(video::IImage *src, const core::rect<s32> &srcrect, video::IImage *dest);

#endif


code:
// 11.11.2011 11:11 ValkaTR
//
// This is a copy of intlGUIEditBox from the irrlicht, but with a
// fix in the OnEvent function, which doesn't allowed input of
// other keyboard layouts than latin-1
//
// Characters like: ä ö ü õ ы й ю я ъ № € ° ...
//
// This fix is only needed for linux, because of a bug
// in the CIrrDeviceLinux.cpp:1014-1015 of the irrlicht
//
// Also locale in the programm should not be changed to
// a "C", "POSIX" or whatever, it should be set to "",
// or XLookupString will return nothing for the international
// characters.
//
// From the "man setlocale":
//
// On startup of the main program, the portable "C" locale
// is selected as default.  A  program  may  be  made
// portable to all locales by calling:
//
//           setlocale(LC_ALL, "");
//
//       after  program initialization....
//

// Copyright (C) 2002-2013 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include <util/numeric.h>
#include "intlGUIEditBox.h"

#if defined(_IRR_COMPILE_WITH_GUI_) && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9

#include "IGUISkin.h"
#include "IGUIEnvironment.h"
#include "IGUIFont.h"
#include "IVideoDriver.h"
//#include "rect.h"
//#include "irrlicht/os.cpp"
#include "porting.h"
//#include "Keycodes.h"
#include "log.h"

/*
	todo:
	optional scrollbars
	ctrl+left/right to select word
	double click/ctrl click: word select + drag to select whole words, triple click to select line
	optional? dragging selected text
	numerical
*/

namespace irr
{
namespace gui
{

//! constructor
intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border,
		IGUIEnvironment* environment, IGUIElement* parent, s32 id,
		const core::rect<s32>& rectangle)
	: IGUIEditBox(environment, parent, id, rectangle), MouseMarking(false),
	Border(border), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0),
	OverrideColor(video::SColor(101,255,255,255)), OverrideFont(0), LastBreakFont(0),
	Operator(0), BlinkStartTime(0), CursorPos(0), HScrollPos(0), VScrollPos(0), Max(0),
	WordWrap(false), MultiLine(false), AutoScroll(true), PasswordBox(false),
	PasswordChar(L'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER),
	CurrentTextRect(0,0,1,1), FrameRect(rectangle)
{
	#ifdef _DEBUG
	setDebugName("intlintlGUIEditBox");
	#endif

	Text = text;

	if (Environment)
		Operator = Environment->getOSOperator();

	if (Operator)
		Operator->grab();

	// this element can be tabbed to
	setTabStop(true);
	setTabOrder(-1);

	IGUISkin *skin = 0;
	if (Environment)
		skin = Environment->getSkin();
	if (Border && skin)
	{
		FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
		FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
		FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
		FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
	}

	breakText();

	calculateScrollPos();
}

//! destructor
intlGUIEditBox::~intlGUIEditBox()
{
	if (OverrideFont)
		OverrideFont->drop();

	if (Operator)
		Operator->drop();
}

//! Sets another skin independent font.
void intlGUIEditBox::setOverrideFont(IGUIFont* font)
{
	if (OverrideFont == font)
		return;

	if (OverrideFont)
		OverrideFont->drop();

	OverrideFont = font;

	if (OverrideFont)
		OverrideFont->grab();

	breakText();
}

IGUIFont * intlGUIEditBox::getOverrideFont() const
{
	return OverrideFont;
}

//! Get the font which is used right now for drawing
IGUIFont* intlGUIEditBox::getActiveFont() const
{
	if ( OverrideFont )
		return OverrideFont;
	IGUISkin* skin = Environment->getSkin();
	if (skin)
		return skin->getFont();
	return 0;
}

//! Sets another color for the text.
void intlGUIEditBox::setOverrideColor(video::SColor color)
{
	OverrideColor = color;
	OverrideColorEnabled = true;
}

video::SColor intlGUIEditBox::getOverrideColor() const
{
	return OverrideColor;
}

//! Turns the border on or off
void intlGUIEditBox::setDrawBorder(bool border)
{
	Border = border;
}

//! Sets whether to draw the background
void intlGUIEditBox::setDrawBackground(bool draw)
{
}

//! Sets if the text should use the overide color or the color in the gui skin.
void intlGUIEditBox::enableOverrideColor(bool enable)
{
	OverrideColorEnabled = enable;
}

bool intlGUIEditBox::isOverrideColorEnabled() const
{
	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
	return OverrideColorEnabled;
}

//! Enables or disables word wrap
void intlGUIEditBox::setWordWrap(bool enable)
{
	WordWrap = enable;
	breakText();
}

void intlGUIEditBox::updateAbsolutePosition()
{
    core::rect<s32> oldAbsoluteRect(AbsoluteRect);
	IGUIElement::updateAbsolutePosition();
	if ( oldAbsoluteRect != AbsoluteRect )
	{
        breakText();
	}
}

//! Checks if word wrap is enabled
bool intlGUIEditBox::isWordWrapEnabled() const
{
	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
	return WordWrap;
}

//! Enables or disables newlines.
void intlGUIEditBox::setMultiLine(bool enable)
{
	MultiLine = enable;
}

//! Checks if multi line editing is enabled
bool intlGUIEditBox::isMultiLineEnabled() const
{
	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
	return MultiLine;
}

void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar)
{
	PasswordBox = passwordBox;
	if (PasswordBox)
	{
		PasswordChar = passwordChar;
		setMultiLine(false);
		setWordWrap(false);
		BrokenText.clear();
	}
}

bool intlGUIEditBox::isPasswordBox() const
{
	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
	return PasswordBox;
}

//! Sets text justification
void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
{
	HAlign = horizontal;
	VAlign = vertical;
}

//! called if an event happened.
bool intlGUIEditBox::OnEvent(const SEvent& event)
{
	if (IsEnabled)
	{

		switch(event.EventType)
		{
		case EET_GUI_EVENT:
			if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
			{
				if (event.GUIEvent.Caller == this)
				{
					MouseMarking = false;
					setTextMarkers(0,0);
				}
			}
			break;
		case EET_KEY_INPUT_EVENT:
        {
#if (defined(__linux__) || defined(__FreeBSD__))
            // ################################################################
			// ValkaTR:
            // This part is the difference from the original intlGUIEditBox
            // It converts UTF-8 character into a UCS-2 (wchar_t)
            wchar_t wc = L'_';
            mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );

            //printf( "char: %lc (%u)  \r\n", wc, wc );

            SEvent irrevent(event);
            irrevent.KeyInput.Char = wc;
            // ################################################################

			if (processKey(irrevent))
				return true;
#else
			if (processKey(event))
				return true;
#endif // defined(linux)

			break;
        }
		case EET_MOUSE_INPUT_EVENT:
			if (processMouse(event))
				return true;
			break;
		default:
			break;
		}
	}

	return IGUIElement::OnEvent(event);
}

bool intlGUIEditBox::processKey(const SEvent& event)
{
	if (!event.KeyInput.PressedDown)
		return false;

	bool textChanged = false;
	s32 newMarkBegin = MarkBegin;
	s32 newMarkEnd = MarkEnd;

	// control shortcut handling

	if (event.KeyInput.Control)
	{
		// german backlash '\' entered with control + '?'
		if ( event.KeyInput.Char == '\\' )
		{
			inputChar(event.KeyInput.Char);
			return true;
		}

		switch(event.KeyInput.Key)
		{
		case KEY_KEY_A:
			// select all
			newMarkBegin = 0;
			newMarkEnd = Text.size();
			break;
		case KEY_KEY_C:
			// copy to clipboard
			if (!PasswordBox && Operator && MarkBegin != MarkEnd)
			{
				const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				core::stringc s;
				s = Text.subString(realmbgn, realmend - realmbgn).c_str();
				Operator->copyToClipboard(s.c_str());
			}
			break;
		case KEY_KEY_X:
			// cut to the clipboard
			if (!PasswordBox && Operator && MarkBegin != MarkEnd)
			{
				const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				// copy
				core::stringc sc;
				sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
				Operator->copyToClipboard(sc.c_str());

				if (IsEnabled)
				{
					// delete
					core::stringw s;
					s = Text.subString(0, realmbgn);
					s.append( Text.subString(realmend, Text.size()-realmend) );
					Text = s;

					CursorPos = realmbgn;
					newMarkBegin = 0;
					newMarkEnd = 0;
					textChanged = true;
				}
			}
			break;
		case KEY_KEY_V:
			if ( !IsEnabled )
				break;

			// paste from the clipboard
			if (Operator)
			{
				const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				// add new character
				const c8* p = Operator->getTextFromClipboard();
				if (p)
				{
					if (MarkBegin == MarkEnd)
					{
						// insert text
						core::stringw s = Text.subString(0, CursorPos);
						s.append(p);
						s.append( Text.subString(CursorPos, Text.size()-CursorPos) );

						if (!Max || s.size()<=Max) // thx to Fish FH for fix
						{
							Text = s;
							s = p;
							CursorPos += s.size();
						}
					}
					else
					{
						// replace text

						core::stringw s = Text.subString(0, realmbgn);
						s.append(p);
						s.append( Text.subString(realmend, Text.size()-realmend) );

						if (!Max || s.size()<=Max)  // thx to Fish FH for fix
						{
							Text = s;
							s = p;
							CursorPos = realmbgn + s.size();
						}
					}
				}

				newMarkBegin = 0;
				newMarkEnd = 0;
				textChanged = true;
			}
			break;
		case KEY_HOME:
			// move/highlight to start of text
			if (event.KeyInput.Shift)
			{
				newMarkEnd = CursorPos;
				newMarkBegin = 0;
				CursorPos = 0;
			}
			else
			{
				CursorPos = 0;
				newMarkBegin = 0;
				newMarkEnd = 0;
			}
			break;
		case KEY_END:
			// move/highlight to end of text
			if (event.KeyInput.Shift)
			{
				newMarkBegin = CursorPos;
				newMarkEnd = Text.size();
				CursorPos = 0;
			}
			else
			{
				CursorPos = Text.size();
				newMarkBegin = 0;
				newMarkEnd = 0;
			}
			break;
		default:
			return false;
		}
	}
	// default keyboard handling
	else
	switch(event.KeyInput.Key)
	{
	case KEY_END:
		{
			s32 p = Text.size();
			if (WordWrap || MultiLine)
			{
				p = getLineFromPos(CursorPos);
				p = BrokenTextPositions[p] + (s32)BrokenText[p].size();
				if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' ))
					p-=1;
			}

			if (event.KeyInput.Shift)
			{
				if (MarkBegin == MarkEnd)
					newMarkBegin = CursorPos;

				newMarkEnd = p;
			}
			else
			{
				newMarkBegin = 0;
				newMarkEnd = 0;
			}
			CursorPos = p;
			BlinkStartTime = porting::getTimeMs();
		}
		break;
	case KEY_HOME:
		{

			s32 p = 0;
			if (WordWrap || MultiLine)
			{
				p = getLineFromPos(CursorPos);
				p = BrokenTextPositions[p];
			}

			if (event.KeyInput.Shift)
			{
				if (MarkBegin == MarkEnd)
					newMarkBegin = CursorPos;
				newMarkEnd = p;
			}
			else
			{
				newMarkBegin = 0;
				newMarkEnd = 0;
			}
			CursorPos = p;
			BlinkStartTime = porting::getTimeMs();
		}
		break;
	case KEY_RETURN:
		if (MultiLine)
		{
			inputChar(L'\n');
			return true;
		}
		else
		{
		    sendGuiEvent( EGET_EDITBOX_ENTER );
		}
		break;
	case KEY_LEFT:

		if (event.KeyInput.Shift)
		{
			if (CursorPos > 0)
			{
				if (MarkBegin == MarkEnd)
					newMarkBegin = CursorPos;

				newMarkEnd = CursorPos-1;
			}
		}
		else
		{
			newMarkBegin = 0;
			newMarkEnd = 0;
		}

		if (CursorPos > 0) CursorPos--;
		BlinkStartTime = porting::getTimeMs();
		break;

	case KEY_RIGHT:
		if (event.KeyInput.Shift)
		{
			if (Text.size() > (u32)CursorPos)
			{
				if (MarkBegin == MarkEnd)
					newMarkBegin = CursorPos;

				newMarkEnd = CursorPos+1;
			}
		}
		else
		{
			newMarkBegin = 0;
			newMarkEnd = 0;
		}

		if (Text.size() > (u32)CursorPos) CursorPos++;
		BlinkStartTime = porting::getTimeMs();
		break;
	case KEY_UP:
		if (MultiLine || (WordWrap && BrokenText.size() > 1) )
		{
			s32 lineNo = getLineFromPos(CursorPos);
			s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd);
			if (lineNo > 0)
			{
				s32 cp = CursorPos - BrokenTextPositions[lineNo];
				if ((s32)BrokenText[lineNo-1].size() < cp)
					CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1;
				else
					CursorPos = BrokenTextPositions[lineNo-1] + cp;
			}

			if (event.KeyInput.Shift)
			{
				newMarkBegin = mb;
				newMarkEnd = CursorPos;
			}
			else
			{
				newMarkBegin = 0;
				newMarkEnd = 0;
			}

		}
		else
		{
			return false;
		}
		break;
	case KEY_DOWN:
		if (MultiLine || (WordWrap && BrokenText.size() > 1) )
		{
			s32 lineNo = getLineFromPos(CursorPos);
			s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd);
			if (lineNo < (s32)BrokenText.size()-1)
			{
				s32 cp = CursorPos - BrokenTextPositions[lineNo];
				if ((s32)BrokenText[lineNo+1].size() < cp)
					CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1;
				else
					CursorPos = BrokenTextPositions[lineNo+1] + cp;
			}

			if (event.KeyInput.Shift)
			{
				newMarkBegin = mb;
				newMarkEnd = CursorPos;
			}
			else
			{
				newMarkBegin = 0;
				newMarkEnd = 0;
			}

		}
		else
		{
			return false;
		}
		break;

	case KEY_BACK:
		if ( !this->IsEnabled )
			break;

		if (Text.size())
		{
			core::stringw s;

			if (MarkBegin != MarkEnd)
			{
				// delete marked text
				const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				s = Text.subString(0, realmbgn);
				s.append( Text.subString(realmend, Text.size()-realmend) );
				Text = s;

				CursorPos = realmbgn;
			}
			else
			{
				// delete text behind cursor
				if (CursorPos>0)
					s = Text.subString(0, CursorPos-1);
				else
					s = L"";
				s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
				Text = s;
				--CursorPos;
			}

			if (CursorPos < 0)
				CursorPos = 0;
			BlinkStartTime = porting::getTimeMs();
			newMarkBegin = 0;
			newMarkEnd = 0;
			textChanged = true;
		}
		break;
	case KEY_DELETE:
		if ( !this->IsEnabled )
			break;

		if (Text.size() != 0)
		{
			core::stringw s;

			if (MarkBegin != MarkEnd)
			{
				// delete marked text
				const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				s = Text.subString(0, realmbgn);
				s.append( Text.subString(realmend, Text.size()-realmend) );
				Text = s;

				CursorPos = realmbgn;
			}
			else
			{
				// delete text before cursor
				s = Text.subString(0, CursorPos);
				s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) );
				Text = s;
			}

			if (CursorPos > (s32)Text.size())
				CursorPos = (s32)Text.size();

			BlinkStartTime = porting::getTimeMs();
			newMarkBegin = 0;
			newMarkEnd = 0;
			textChanged = true;
		}
		break;

	case KEY_ESCAPE:
	case KEY_TAB:
	case KEY_SHIFT:
	case KEY_F1:
	case KEY_F2:
	case KEY_F3:
	case KEY_F4:
	case KEY_F5:
	case KEY_F6:
	case KEY_F7:
	case KEY_F8:
	case KEY_F9:
	case KEY_F10:
	case KEY_F11:
	case KEY_F12:
	case KEY_F13:
	case KEY_F14:
	case KEY_F15:
	case KEY_F16:
	case KEY_F17:
	case KEY_F18:
	case KEY_F19:
	case KEY_F20:
	case KEY_F21:
	case KEY_F22:
	case KEY_F23:
	case KEY_F24:
		// ignore these keys
		return false;

	default:
		inputChar(event.KeyInput.Char);
		return true;
	}

    // Set new text markers
    setTextMarkers( newMarkBegin, newMarkEnd );

	// break the text if it has changed
	if (textChanged)
	{
		breakText();
		sendGuiEvent(EGET_EDITBOX_CHANGED);
	}

	calculateScrollPos();

	return true;
}

//! draws the element and its children
void intlGUIEditBox::draw()
{
	if (!IsVisible)
		return;

	const bool focus = Environment->hasFocus(this);

	IGUISkin* skin = Environment->getSkin();
	if (!skin)
		return;

	FrameRect = AbsoluteRect;

	// draw the border

	if (Border)
	{
		skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW),
			false, true, FrameRect, &AbsoluteClippingRect);

		FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
		FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
		FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
		FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
	}
	core::rect<s32> localClipRect = FrameRect;
	localClipRect.clipAgainst(AbsoluteClippingRect);

	// draw the text

	IGUIFont* font = OverrideFont;
	if (!OverrideFont)
		font = skin->getFont();

	s32 cursorLine = 0;
	s32 charcursorpos = 0;

	if (font)
	{
		if (LastBreakFont != font)
		{
			breakText();
		}

		// calculate cursor pos

		core::stringw *txtLine = &Text;
		s32 startPos = 0;

		core::stringw s, s2;

		// get mark position
		const bool ml = (!PasswordBox && (WordWrap || MultiLine));
		const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
		const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
		const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0;
		const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1;
		const s32 lineCount = ml ? BrokenText.size() : 1;

		// Save the override color information.
		// Then, alter it if the edit box is disabled.
		const bool prevOver = OverrideColorEnabled;
		const video::SColor prevColor = OverrideColor;

		if (Text.size())
		{
			if (!IsEnabled && !OverrideColorEnabled)
			{
				OverrideColorEnabled = true;
				OverrideColor = skin->getColor(EGDC_GRAY_TEXT);
			}

			for (s32 i=0; i < lineCount; ++i)
			{
				setTextRect(i);

				// clipping test - don't draw anything outside the visible area
				core::rect<s32> c = localClipRect;
				c.clipAgainst(CurrentTextRect);
				if (!c.isValid())
					continue;

				// get current line
				if (PasswordBox)
				{
					if (BrokenText.size() != 1)
					{
						BrokenText.clear();
						BrokenText.push_back(core::stringw());
					}
					if (BrokenText[0].size() != Text.size())
					{
						BrokenText[0] = Text;
						for (u32 q = 0; q < Text.size(); ++q)
						{
							BrokenText[0] [q] = PasswordChar;
						}
					}
					txtLine = &BrokenText[0];
					startPos = 0;
				}
				else
				{
					txtLine = ml ? &BrokenText[i] : &Text;
					startPos = ml ? BrokenTextPositions[i] : 0;
				}

				// draw normal text
				font->draw(txtLine->c_str(), CurrentTextRect,
					OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
					false, true, &localClipRect);

				// draw mark and marked text
				if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount)
				{

					s32 mbegin = 0, mend = 0;
					s32 lineStartPos = 0, lineEndPos = txtLine->size();

					if (i == hlineStart)
					{
						// highlight start is on this line
						s = txtLine->subString(0, realmbgn - startPos);
						mbegin = font->getDimension(s.c_str()).Width;

						// deal with kerning
						mbegin += font->getKerningWidth(
							&((*txtLine)[realmbgn - startPos]),
							realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0);

						lineStartPos = realmbgn - startPos;
					}
					if (i == hlineStart + hlineCount - 1)
					{
						// highlight end is on this line
						s2 = txtLine->subString(0, realmend - startPos);
						mend = font->getDimension(s2.c_str()).Width;
						lineEndPos = (s32)s2.size();
					}
					else
						mend = font->getDimension(txtLine->c_str()).Width;

					CurrentTextRect.UpperLeftCorner.X += mbegin;
					CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin;

					// draw mark
					skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect);

					// draw marked text
					s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos);

					if (s.size())
						font->draw(s.c_str(), CurrentTextRect,
							OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
							false, true, &localClipRect);

				}
			}

			// Return the override color information to its previous settings.
			OverrideColorEnabled = prevOver;
			OverrideColor = prevColor;
		}

		// draw cursor

		if (WordWrap || MultiLine)
		{
			cursorLine = getLineFromPos(CursorPos);
			txtLine = &BrokenText[cursorLine];
			startPos = BrokenTextPositions[cursorLine];
		}
		s = txtLine->subString(0,CursorPos-startPos);
		charcursorpos = font->getDimension(s.c_str()).Width +
			font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0);

		if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < 350)
		{
			setTextRect(cursorLine);
			CurrentTextRect.UpperLeftCorner.X += charcursorpos;

			font->draw(L"_", CurrentTextRect,
				OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
				false, true, &localClipRect);
		}
	}

	// draw children
	IGUIElement::draw();
}

//! Sets the new caption of this element.
void intlGUIEditBox::setText(const wchar_t* text)
{
	Text = text;
	if (u32(CursorPos) > Text.size())
		CursorPos = Text.size();
	HScrollPos = 0;
	breakText();
}

//! Enables or disables automatic scrolling with cursor position
//! \param enable: If set to true, the text will move around with the cursor position
void intlGUIEditBox::setAutoScroll(bool enable)
{
	AutoScroll = enable;
}

//! Checks to see if automatic scrolling is enabled
//! \return true if automatic scrolling is enabled, false if not
bool intlGUIEditBox::isAutoScrollEnabled() const
{
	_IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
	return AutoScroll;
}

//! Gets the area of the text in the edit box
//! \return Returns the size in pixels of the text
core::dimension2du intlGUIEditBox::getTextDimension()
{
	core::rect<s32> ret;

	setTextRect(0);
	ret = CurrentTextRect;

	for (u32 i=1; i < BrokenText.size(); ++i)
	{
		setTextRect(i);
		ret.addInternalPoint(CurrentTextRect.UpperLeftCorner);
		ret.addInternalPoint(CurrentTextRect.LowerRightCorner);
	}

	return core::dimension2du(ret.getSize());
}

//! Sets the maximum amount of characters which may be entered in the box.
//! \param max: Maximum amount of characters. If 0, the character amount is
//! infinity.
void intlGUIEditBox::setMax(u32 max)
{
	Max = max;

	if (Text.size() > Max && Max != 0)
		Text = Text.subString(0, Max);
}

//! Returns maximum amount of characters, previously set by setMax();
u32 intlGUIEditBox::getMax() const
{
	return Max;
}

bool intlGUIEditBox::processMouse(const SEvent& event)
{
	switch(event.MouseInput.Event)
	{
	case irr::EMIE_LMOUSE_LEFT_UP:
		if (Environment->hasFocus(this))
		{
			CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
			if (MouseMarking)
			{
			    setTextMarkers( MarkBegin, CursorPos );
			}
			MouseMarking = false;
			calculateScrollPos();
			return true;
		}
		break;
	case irr::EMIE_MOUSE_MOVED:
		{
			if (MouseMarking)
			{
				CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
				setTextMarkers( MarkBegin, CursorPos );
				calculateScrollPos();
				return true;
			}
		}
		break;
	case EMIE_LMOUSE_PRESSED_DOWN:
		if (!Environment->hasFocus(this))
		{
			BlinkStartTime = porting::getTimeMs();
			MouseMarking = true;
			CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
			setTextMarkers(CursorPos, CursorPos );
			calculateScrollPos();
			return true;
		}
		else
		{
			if (!AbsoluteClippingRect.isPointInside(
				core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y)))
			{
				return false;
			}
			else
			{
				// move cursor
				CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);

                s32 newMarkBegin = MarkBegin;
				if (!MouseMarking)
					newMarkBegin = CursorPos;

				MouseMarking = true;
				setTextMarkers( newMarkBegin, CursorPos);
				calculateScrollPos();
				return true;
			}
		}
	default:
		break;
	}

	return false;
}

s32 intlGUIEditBox::getCursorPos(s32 x, s32 y)
{
	IGUIFont* font = OverrideFont;
	IGUISkin* skin = Environment->getSkin();
	if (!OverrideFont)
		font = skin->getFont();

	const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;

	core::stringw *txtLine = NULL;
	s32 startPos = 0;
	u32 curr_line_idx = 0;
	x += 3;

	for (; curr_line_idx < lineCount; ++curr_line_idx) {
		setTextRect(curr_line_idx);
		if (curr_line_idx == 0 && y < CurrentTextRect.UpperLeftCorner.Y)
			y = CurrentTextRect.UpperLeftCorner.Y;
		if (curr_line_idx == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y)
			y = CurrentTextRect.LowerRightCorner.Y;

		// is it inside this region?
		if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) {
			// we've found the clicked line
			txtLine = (WordWrap || MultiLine) ? &BrokenText[curr_line_idx] : &Text;
			startPos = (WordWrap || MultiLine) ? BrokenTextPositions[curr_line_idx] : 0;
			break;
		}
	}

	if (x < CurrentTextRect.UpperLeftCorner.X)
		x = CurrentTextRect.UpperLeftCorner.X;
	else if (x > CurrentTextRect.LowerRightCorner.X)
		x = CurrentTextRect.LowerRightCorner.X;

	s32 idx = font->getCharacterFromPos(txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X);
	// Special handling for last line, if we are on limits, add 1 extra shift because idx
	// will be the last char, not null char of the wstring
	if (curr_line_idx == lineCount - 1 && x == CurrentTextRect.LowerRightCorner.X)
		idx++;

	return rangelim(idx + startPos, 0, S32_MAX);
}

//! Breaks the single text line.
void intlGUIEditBox::breakText()
{
	IGUISkin* skin = Environment->getSkin();

	if ((!WordWrap && !MultiLine) || !skin)
		return;

	BrokenText.clear(); // need to reallocate :/
	BrokenTextPositions.set_used(0);

	IGUIFont* font = OverrideFont;
	if (!OverrideFont)
		font = skin->getFont();

	if (!font)
		return;

	LastBreakFont = font;

	core::stringw line;
	core::stringw word;
	core::stringw whitespace;
	s32 lastLineStart = 0;
	s32 size = Text.size();
	s32 length = 0;
	s32 elWidth = RelativeRect.getWidth() - 6;
	wchar_t c;

	for (s32 i=0; i<size; ++i)
	{
		c = Text[i];
		bool lineBreak = false;

		if (c == L'\r') // Mac or Windows breaks
		{
			lineBreak = true;
			c = ' ';
			if (Text[i+1] == L'\n') // Windows breaks
			{
				Text.erase(i+1);
				--size;
			}
		}
		else if (c == L'\n') // Unix breaks
		{
			lineBreak = true;
			c = ' ';
		}

		// don't break if we're not a multi-line edit box
		if (!MultiLine)
			lineBreak = false;

		if (c == L' ' || c == 0 || i == (size-1))
		{
			if (word.size())
			{
				// here comes the next whitespace, look if
				// we can break the last word to the next line.
				s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
				s32 worldlgth = font->getDimension(word.c_str()).Width;

				if (WordWrap && length + worldlgth + whitelgth > elWidth)
				{
					// break to next line
					length = worldlgth;
					BrokenText.push_back(line);
					BrokenTextPositions.push_back(lastLineStart);
					lastLineStart = i - (s32)word.size();
					line = word;
				}
				else
				{
					// add word to line
					line += whitespace;
					line += word;
					length += whitelgth + worldlgth;
				}

				word = L"";
				whitespace = L"";
			}

			whitespace += c;

			// compute line break
			if (lineBreak)
			{
				line += whitespace;
				line += word;
				BrokenText.push_back(line);
				BrokenTextPositions.push_back(lastLineStart);
				lastLineStart = i+1;
				line = L"";
				word = L"";
				whitespace = L"";
				length = 0;
			}
		}
		else
		{
			// yippee this is a word..
			word += c;
		}
	}

	line += whitespace;
	line += word;
	BrokenText.push_back(line);
	BrokenTextPositions.push_back(lastLineStart);
}

void intlGUIEditBox::setTextRect(s32 line)
{
	core::dimension2du d;

	IGUISkin* skin = Environment->getSkin();
	if (!skin)
		return;

	IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont();

	if (!font)
		return;

	// get text dimension
	const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;
	if (WordWrap || MultiLine)
	{
		d = font->getDimension(BrokenText[line].c_str());
	}
	else
	{
		d = font->getDimension(Text.c_str());
		d.Height = AbsoluteRect.getHeight();
	}
	d.Height += font->getKerningHeight();

	// justification
	switch (HAlign)
	{
	case EGUIA_CENTER:
		// align to h centre
		CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2);
		CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2);
		break;
	case EGUIA_LOWERRIGHT:
		// align to right edge
		CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width;
		CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth();
		break;
	default:
		// align to left edge
		CurrentTextRect.UpperLeftCorner.X = 0;
		CurrentTextRect.LowerRightCorner.X = d.Width;

	}

	switch (VAlign)
	{
	case EGUIA_CENTER:
		// align to v centre
		CurrentTextRect.UpperLeftCorner.Y =
			(FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line;
		break;
	case EGUIA_LOWERRIGHT:
		// align to bottom edge
		CurrentTextRect.UpperLeftCorner.Y =
			FrameRect.getHeight() - lineCount*d.Height + d.Height*line;
		break;
	default:
		// align to top edge
		CurrentTextRect.UpperLeftCorner.Y = d.Height*line;
		break;
	}

	CurrentTextRect.UpperLeftCorner.X  -= HScrollPos;
	CurrentTextRect.LowerRightCorner.X -= HScrollPos;
	CurrentTextRect.UpperLeftCorner.Y  -= VScrollPos;
	CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height;

	CurrentTextRect += FrameRect.UpperLeftCorner;

}

s32 intlGUIEditBox::getLineFromPos(s32 pos)
{
	if (!WordWrap && !MultiLine)
		return 0;

	s32 i=0;
	while (i < (s32)BrokenTextPositions.size())
	{
		if (BrokenTextPositions[i] > pos)
			return i-1;
		++i;
	}
	return (s32)BrokenTextPositions.size() - 1;
}

void intlGUIEditBox::inputChar(wchar_t c)
{
	if (!IsEnabled)
		return;

	if (c != 0)
	{
		if (Text.size() < Max || Max == 0)
		{
			core::stringw s;

			if (MarkBegin != MarkEnd)
			{
				// replace marked text
				const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				s = Text.subString(0, realmbgn);
				s.append(c);
				s.append( Text.subString(realmend, Text.size()-realmend) );
				Text = s;
				CursorPos = realmbgn+1;
			}
			else
			{
				// add new character
				s = Text.subString(0, CursorPos);
				s.append(c);
				s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
				Text = s;
				++CursorPos;
			}

			BlinkStartTime = porting::getTimeMs();
			setTextMarkers(0, 0);
		}
	}
	breakText();
	sendGuiEvent(EGET_EDITBOX_CHANGED);
	calculateScrollPos();
}

void intlGUIEditBox::calculateScrollPos()
{
	if (!AutoScroll)
		return;

	// calculate horizontal scroll position
	s32 cursLine = getLineFromPos(CursorPos);
	setTextRect(cursLine);

	// don't do horizontal scrolling when wordwrap is enabled.
	if (!WordWrap)
	{
		// get cursor position
		IGUISkin* skin = Environment->getSkin();
		if (!skin)
			return;
		IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont();
		if (!font)
			return;

		core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text;
		s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos;

		s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos +
			font->getDimension(txtLine->subString(0, cPos).c_str()).Width;

		s32 cEnd = cStart + font->getDimension(L"_ ").Width;

		if (FrameRect.LowerRightCorner.X < cEnd)
			HScrollPos = cEnd - FrameRect.LowerRightCorner.X;
		else if (FrameRect.UpperLeftCorner.X > cStart)
			HScrollPos = cStart - FrameRect.UpperLeftCorner.X;
		else
			HScrollPos = 0;

		// todo: adjust scrollbar
	}

	// vertical scroll position
	if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos)
		VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos;

	else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos)
		VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos;
	else
		VScrollPos = 0;

	// todo: adjust scrollbar
}

//! set text markers
void intlGUIEditBox::setTextMarkers(s32 begin, s32 end)
{
    if ( begin != MarkBegin || end != MarkEnd )
    {
        MarkBegin = begin;
        MarkEnd = end;
        sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED);
    }
}

//! send some gui event to parent
void intlGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type)
{
	if ( Parent )
	{
        SEvent e;
        e.EventType = EET_GUI_EVENT;
        e.GUIEvent.Caller = this;
        e.GUIEvent.Element = 0;
        e.GUIEvent.EventType = type;

        Parent->OnEvent(e);
	}
}

//! Writes attributes of the element.
void intlGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
{
	// IGUIEditBox::serializeAttributes(out,options);

	out->addBool  ("OverrideColorEnabled",OverrideColorEnabled );
	out->addColor ("OverrideColor",       OverrideColor);
	// out->addFont("OverrideFont",OverrideFont);
	out->addInt   ("MaxChars",            Max);
	out->addBool  ("WordWrap",            WordWrap);
	out->addBool  ("MultiLine",           MultiLine);
	out->addBool  ("AutoScroll",          AutoScroll);
	out->addBool  ("PasswordBox",         PasswordBox);
	core::stringw ch = L" ";
	ch[0] = PasswordChar;
	out->addString("PasswordChar",        ch.c_str());
	out->addEnum  ("HTextAlign",          HAlign, GUIAlignmentNames);
	out->addEnum  ("VTextAlign",          VAlign, GUIAlignmentNames);

	IGUIEditBox::serializeAttributes(out,options);
}

//! Reads attributes of the element
void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
{
	IGUIEditBox::deserializeAttributes(in,options);

	setOverrideColor(in->getAttributeAsColor("OverrideColor"));
	enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
	setMax(in->getAttributeAsInt("MaxChars"));
	setWordWrap(in->getAttributeAsBool("WordWrap"));
	setMultiLine(in->getAttributeAsBool("MultiLine"));
	setAutoScroll(in->getAttributeAsBool("AutoScroll"));
	core::stringw ch = in->getAttributeAsStringW("PasswordChar");

	if (!ch.size())
		setPasswordBox(in->getAttributeAsBool("PasswordBox"));
	else
		setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]);

	setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
			(EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));

	// setOverrideFont(in->getAttributeAsFont("OverrideFont"));
}

} // end namespace gui
} // end namespace irr

#endif // _IRR_COMPILE_WITH_GUI_