//! \~\file piscreen.h
//! \~\ingroup Console
//! \~\brief
//! \~english Console screen manager and tile host
//! \~russian Менеджер консольного экрана и контейнер тайлов
/*
    PIP - Platform Independent Primitives
    Console GUI
    Ivan Pelipenko peri4ko@yandex.ru

    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 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU 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, see <http://www.gnu.org/licenses/>.
*/

#ifndef PISCREEN_H
#define PISCREEN_H

#include "pip_console_export.h"
#include "piscreendrawer.h"
#include "piscreentile.h"


//! \~\ingroup Console
//! \~\brief
//! \~english Console screen manager with tile layout, drawing, and input routing.
//! \~russian Менеджер консольного экрана с раскладкой тайлов, отрисовкой и маршрутизацией ввода.
class PIP_CONSOLE_EXPORT PIScreen
	: public PIThread
	, public PIScreenTypes::PIScreenBase {
	PIOBJECT_SUBCLASS(PIScreen, PIThread);
	class SystemConsole;

public:
	//! \~english Constructs a screen with an internal keyboard listener, optional callback, and auto-start mode.
	//! \~russian Создает экран со встроенным слушателем клавиатуры, необязательным обратным вызовом и режимом автозапуска.
	//! \~\details
	//! \~english Constructs a new PIScreen instance with optional immediate start.
	//! \~russian Создает новый экземпляр PIScreen с опциональным немедленным запуском.
	//! \~\param startNow
	//! \~english Start immediately if true.
	//! \~russian Запустить немедленно, если true.
	//! \~\param slot
	//! \~english Keyboard handler function.
	//! \~russian Функция обработчика клавиатуры.
	PIScreen(bool startNow = true, PIKbdListener::KBFunc slot = 0);

	//! \~english Destroys PIScreen and cleans up resources.
	//! \~russian Уничтожает PIScreen и очищает ресурсы.
	~PIScreen();

	//! \~english Enables catching the exit key (default is 'Q') to stop the screen.
	//! \~russian Включает захват клавиши выхода (по умолчанию 'Q') для остановки экрана.
	void enableExitCapture(int key = 'Q') { listener->enableExitCapture(key); }

	//! \~english Disables catching the exit key.
	//! \~russian Отключает захват клавиши выхода.
	void disableExitCapture() { listener->disableExitCapture(); }

	//! \~english Returns whether exit key capture is enabled.
	//! \~russian Возвращает, включен ли перехват клавиши выхода.
	bool exitCaptured() const { return listener->exitCaptured(); }

	//! \~english Returns the configured exit key.
	//! \~russian Возвращает настроенную клавишу выхода.
	int exitKey() const { return listener->exitKey(); }

	//! \~english Returns the current console width in characters.
	//! \~russian Возвращает текущую ширину консоли в символах.
	int windowWidth() const { return console.width; }

	//! \~english Returns the current console height in characters.
	//! \~russian Возвращает текущую высоту консоли в символах.
	int windowHeight() const { return console.height; }

	//! \~english Returns whether mouse hit-testing and routing are enabled.
	//! \~russian Возвращает, включены ли проверка попадания и маршрутизация событий мыши.
	bool isMouseEnabled() const { return mouse_; }

	//! \~english Enables or disables mouse routing and tile hit-testing.
	//! \~russian Включает или выключает маршрутизацию мыши и проверку попадания по тайлам.
	void setMouseEnabled(bool on);

	//! \~english Returns the root tile covering the whole screen.
	//! \~russian Возвращает корневой тайл, покрывающий весь экран.
	PIScreenTile * rootTile() { return &root; }

	//! \~english Searches the root tile subtree by object name.
	//! \~russian Ищет тайл по имени объекта в поддереве корневого тайла.
	//! \~\return
	//! \~english Tile pointer if found, otherwise nullptr.
	//! \~russian Указатель на тайл, если найден, иначе nullptr.
	PIScreenTile * tileByName(const PIString & name);

	//! \~english Sets a dialog tile drawn above the root tree, centered on screen, and focused first. Pass \c nullptr to remove it.
	//! \~russian Задает диалоговый тайл, рисуемый поверх корневого дерева, центрируемый на экране и первым получающий фокус. Передайте \c
	//! nullptr, чтобы убрать его.
	void setDialogTile(PIScreenTile * t);

	//! \~english Returns the currently active dialog tile or \c nullptr.
	//! \~russian Возвращает активный диалоговый тайл или \c nullptr.
	PIScreenTile * dialogTile() const { return tile_dialog; }

	//! \~english Returns the drawer used to fill the off-screen cell buffer for the next frame.
	//! \~russian Возвращает рисовальщик, используемый для заполнения внеэкранного буфера ячеек следующего кадра.
	PIScreenDrawer * drawer() { return &drawer_; }

	//! \~english Clears the off-screen cell buffer. The terminal is updated on the next draw cycle.
	//! \~russian Очищает внеэкранный буфер ячеек. Терминал обновится на следующем цикле отрисовки.
	void clear() { drawer_.clear(); }

	//! \~english Resizes the internal console buffers used for subsequent frames.
	//! \~russian Изменяет размер внутренних консольных буферов, используемых в следующих кадрах.
	void resize(int w, int h) { console.resize(w, h); }


	//! \handlers
	//! \{

	//! \fn void waitForFinish()
	//! \~english Blocks until the captured exit key is pressed and then stops the screen.
	//! \~russian Блокирует выполнение, пока не будет нажата перехватываемая клавиша выхода, затем останавливает экран.
	EVENT_HANDLER0(void, waitForFinish);

	//! \fn void start(bool wait = false)
	//! \~english Starts the screen thread and optionally waits until the configured exit key is captured.
	//! \~russian Запускает поток экрана и при необходимости ждет, пока не будет перехвачена настроенная клавиша выхода.
	EVENT_HANDLER0(void, start) { start(false); }
	EVENT_HANDLER1(void, start, bool, wait);

	//! \fn void stop(bool clear = false)
	//! \~english Stops the screen thread, restores console state, and optionally clears the terminal.
	//! \~russian Останавливает поток экрана, восстанавливает состояние консоли и при необходимости очищает терминал.
	EVENT_HANDLER0(void, stop) { stop(false); }
	EVENT_HANDLER1(void, stop, bool, clear);

	//! \}
	//! \events
	//! \{

	//! \fn void keyPressed(PIKbdListener::KeyEvent key, void * data)
	//! \~english Raised when a key was not consumed by focus navigation or the focused tile. \a data is the screen user data pointer.
	//! \~russian Вызывается, когда клавиша не была поглощена навигацией фокуса или тайлом с фокусом. \a data содержит пользовательский
	//! указатель экрана.
	EVENT2(keyPressed, PIKbdListener::KeyEvent, key, void *, data);

	//! \fn void tileEvent(PIScreenTile * tile, PIScreenTypes::TileEvent e)
	//! \~english Raised when a tile reports a custom event \a e.
	//! \~russian Вызывается, когда тайл сообщает пользовательское событие \a e.
	EVENT2(tileEvent, PIScreenTile *, tile, PIScreenTypes::TileEvent, e);

	//! \}

private:
	class PIP_CONSOLE_EXPORT SystemConsole {
	public:
		SystemConsole();
		~SystemConsole();
		void begin();
		void end();
		void prepare();
		void clear();
		void print();
		void resize(int w, int h);
		void toUpperLeft();
		void moveTo(int x = 0, int y = 0);
		void hideCursor();
		void showCursor();
		void clearScreen();
		void clearScreenLower();
#ifdef WINDOWS
		void getWinCurCoord();
		void clearLine();
		void newLine();
		ushort attributes(const PIScreenTypes::Cell & c);
#else
		PIString formatString(const PIScreenTypes::Cell & c);
#endif
		PRIVATE_DECLARATION(PIP_CONSOLE_EXPORT)
		int width, height, pwidth, pheight;
		int mouse_x, mouse_y;
		PIVector<PIVector<PIScreenTypes::Cell>> cells, pcells;
	};

	void begin() override;
	void run() override;
	void end() override;
	void key_event(PIKbdListener::KeyEvent key);
	EVENT_HANDLER1(void, mouse_event, PIKbdListener::MouseEvent, me);
	EVENT_HANDLER1(void, wheel_event, PIKbdListener::WheelEvent, we);
	static void key_eventS(PIKbdListener::KeyEvent key, void * t) { ((PIScreen *)t)->key_event(key); }
	PIVector<PIScreenTile *> tiles() { return root.children(); }
	PIVector<PIScreenTile *> prepareMouse(PIKbdListener::MouseEvent * e);
	PIVector<PIScreenTile *> tilesUnderMouse(int x, int y);
	bool nextFocus(PIScreenTile * rt, PIKbdListener::KeyEvent key = PIKbdListener::KeyEvent());
	void tileEventInternal(PIScreenTile * t, PIScreenTypes::TileEvent e) override;
	void tileRemovedInternal(PIScreenTile * t) override;
	void tileSetFocusInternal(PIScreenTile * t) override;

	bool mouse_;
	SystemConsole console;
	PIScreenDrawer drawer_;
	PIKbdListener * listener;
	PIKbdListener::KBFunc ret_func;
	PIScreenTile root;
	PIScreenTile *tile_focus, *tile_dialog;
};


#endif // PISCREEN_H
