text: /*
  	Copyright 2015 bigbiff/Dees_Troy TeamWin
  	This file is part of TWRP/TeamWin Recovery Project.
  
  	TWRP is free software: you can redistribute it and/or modify
  	it under the terms of the GNU General Public License as published by
  	the Free Software Foundation, either version 3 of the License, or
  	(at your option) any later version.
  
  	TWRP is distributed in the hope that it will be useful,
  	but WITHOUT ANY WARRANTY; without even the implied warranty of
  	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  	GNU General Public License for more details.
  
  	You should have received a copy of the GNU General Public License
  	along with TWRP.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  // console.cpp - GUIConsole object
  
  #include <stdarg.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <fcntl.h>
  #include <time.h>
  #include <unistd.h>
  #include <pthread.h>
  
  #include <string>
  
  extern "C" {
  #include "../twcommon.h"
  }
  #include "../minuitwrp/minui.h"
  
  #include "rapidxml.hpp"
  #include "objects.hpp"
  #include "gui.hpp"
  #include "twmsg.h"
  
  #define GUI_CONSOLE_BUFFER_SIZE 512
  
  static pthread_mutex_t console_lock;
  static size_t last_message_count = 0;
  static std::vector<Message> gMessages;
  
  static std::vector<std::string> gConsole;
  static std::vector<std::string> gConsoleColor;
  static FILE* ors_file = NULL;
  
  struct InitMutex
  {
  	InitMutex() { pthread_mutex_init(&console_lock, NULL); }
  } initMutex;
  
  static void internal_gui_print(const char *color, char *buf)
  {
  	// make sure to flush any outstanding messages first to preserve order of outputs
  	GUIConsole::Translate_Now();
  
  	fputs(buf, stdout);
  	if (ors_file) {
  		fprintf(ors_file, "%s", buf);
  		fflush(ors_file);
  	}
  
  	char *start, *next;
  
  	if (buf[0] == '\n' && strlen(buf) < 2) {
  		// This prevents the double lines bug seen in the console during zip installs
  		return;
  	}
  
  	pthread_mutex_lock(&console_lock);
  	for (start = next = buf; *next != '\0';)
  	{
  		if (*next == '\n')
  		{
  			*next = '\0';
  			gConsole.push_back(start);
  			gConsoleColor.push_back(color);
  
  			start = ++next;
  		}
  		else
  			++next;
  	}
  
  	// The text after last \n (or whole string if there is no \n)
  	if (*start) {
  		gConsole.push_back(start);
  		gConsoleColor.push_back(color);
  	}
  	pthread_mutex_unlock(&console_lock);
  }
  
  extern "C" void gui_print(const char *fmt, ...)
  {
  	char buf[GUI_CONSOLE_BUFFER_SIZE];		// We're going to limit a single request to 512 bytes
  
  	va_list ap;
  	va_start(ap, fmt);
  	vsnprintf(buf, GUI_CONSOLE_BUFFER_SIZE, fmt, ap);
  	va_end(ap);
  
  	internal_gui_print("normal", buf);
  }
  
  extern "C" void gui_print_color(const char *color, const char *fmt, ...)
  {
  	char buf[GUI_CONSOLE_BUFFER_SIZE];		// We're going to limit a single request to 512 bytes
  
  	va_list ap;
  	va_start(ap, fmt);
  	vsnprintf(buf, GUI_CONSOLE_BUFFER_SIZE, fmt, ap);
  	va_end(ap);
  
  	internal_gui_print(color, buf);
  }
  
  extern "C" void gui_set_FILE(FILE* f)
  {
  	ors_file = f;
  }
  
  void gui_msg(const char* text)
  {
  	if (text) {
  		Message msg = Msg(text);
  		gui_msg(msg);
  	}
  }
  
  void gui_warn(const char* text)
  {
  	if (text) {
  		Message msg = Msg(msg::kWarning, text);
  		gui_msg(msg);
  	}
  }
  
  void gui_process(const char* text)
  {
  	if (text) {
  		Message msg = Msg(msg::kProcess, text);
  		gui_msg(msg);
  	}
  }
  
  void gui_err(const char* text)
  {
  	if (text) {
  		Message msg = Msg(msg::kError, text);
  		gui_msg(msg);
  	}
  }
  
  void gui_highlight(const char* text)
  {
  	if (text) {
  		Message msg = Msg(msg::kHighlight, text);
  		gui_msg(msg);
  	}
  }
  
  void gui_msg(Message msg)
  {
  	std::string output = msg;
  	output += "\n";
  	fputs(output.c_str(), stdout);
  	if (ors_file) {
  		fprintf(ors_file, "%s", output.c_str());
  		fflush(ors_file);
  	}
  	pthread_mutex_lock(&console_lock);
  	gMessages.push_back(msg);
  	pthread_mutex_unlock(&console_lock);
  }
  
  void GUIConsole::Translate_Now()
  {
  	pthread_mutex_lock(&console_lock);
  	size_t message_count = gMessages.size();
  	if (message_count <= last_message_count)
  	{
  		pthread_mutex_unlock(&console_lock);
  		return;
  	}
  
  	for (size_t m = last_message_count; m < message_count; m++) {
  		std::string message = gMessages[m];
  		std::string color = "normal";
  		if (gMessages[m].GetKind() == msg::kError)
  			color = "error";
  		else if (gMessages[m].GetKind() == msg::kHighlight)
  			color = "highlight";
  		else if (gMessages[m].GetKind() == msg::kWarning)
  			color = "warning";
  		else if (gMessages[m].GetKind() == msg::kProcess)
  			color = "process";
  		gConsole.push_back(message);
  		gConsoleColor.push_back(color);
  	}
  	last_message_count = message_count;
  	pthread_mutex_unlock(&console_lock);
  }
  
  void GUIConsole::Clear_For_Retranslation()
  {
  	pthread_mutex_lock(&console_lock);
  	last_message_count = 0;
  	gConsole.clear();
  	gConsoleColor.clear();
  	pthread_mutex_unlock(&console_lock);
  }
  
  GUIConsole::GUIConsole(xml_node<>* node) : GUIScrollList(node)
  {
  	xml_node<>* child;
  
  	mLastCount = 0;
  	scrollToEnd = true;
  	mSlideoutX = mSlideoutY = mSlideoutW = mSlideoutH = 0;
  	mSlideout = 0;
  	mSlideoutState = visible;
  
  	allowSelection = false;	// console doesn't support list item selections
  
  	if (!node)
  	{
  		mRenderX = 0;
  		mRenderY = 0;
  		mRenderW = gr_fb_width();
  		mRenderH = gr_fb_height();
  	}
  	else
  	{
  		child = FindNode(node, "color");
  		if (child)
  		{
  			mFontColor = LoadAttrColor(child, "foreground", mFontColor);
  			mBackgroundColor = LoadAttrColor(child, "background", mBackgroundColor);
  			//mScrollColor = LoadAttrColor(child, "scroll", mScrollColor);
  		}
  
  		child = FindNode(node, "slideout");
  		if (child)
  		{
  			mSlideout = 1;
  			mSlideoutState = hidden;
  			LoadPlacement(child, &mSlideoutX, &mSlideoutY, &mSlideoutW, &mSlideoutH, &mPlacement);
  
  			mSlideoutImage = LoadAttrImage(child, "resource");
  
  			if (mSlideoutImage && mSlideoutImage->GetResource())
  			{
  				mSlideoutW = mSlideoutImage->GetWidth();
  				mSlideoutH = mSlideoutImage->GetHeight();
  				if (mPlacement == CENTER || mPlacement == CENTER_X_ONLY) {
  					mSlideoutX = mSlideoutX - (mSlideoutW / 2);
  					if (mPlacement == CENTER) {
  						mSlideoutY = mSlideoutY - (mSlideoutH / 2);
  					}
  				}
  			}
  		}
  	}
  }
  
  int GUIConsole::RenderSlideout(void)
  {
  	if (!mSlideoutImage || !mSlideoutImage->GetResource())
  		return -1;
  
  	gr_blit(mSlideoutImage->GetResource(), 0, 0, mSlideoutW, mSlideoutH, mSlideoutX, mSlideoutY);
  	return 0;
  }
  
  int GUIConsole::RenderConsole(void)
  {
  	Translate_Now();
  	pthread_mutex_lock(&console_lock);
  	AddLines(&gConsole, &gConsoleColor, &mLastCount, &rConsole, &rConsoleColor);
  	pthread_mutex_unlock(&console_lock);
  	GUIScrollList::Render();
  
  	// if last line is fully visible, keep tracking the last line when new lines are added
  	int bottom_offset = GetDisplayRemainder() - actualItemHeight;
  	bool isAtBottom = firstDisplayedItem == (int)GetItemCount() - GetDisplayItemCount() - (bottom_offset != 0) && y_offset == bottom_offset;
  	if (isAtBottom)
  		scrollToEnd = true;
  #if 0
  	// debug - show if we are tracking the last line
  	if (scrollToEnd) {
  		gr_color(0,255,0,255);
  		gr_fill(mRenderX+mRenderW-5, mRenderY+mRenderH-5, 5, 5);
  	} else {
  		gr_color(255,0,0,255);
  		gr_fill(mRenderX+mRenderW-5, mRenderY+mRenderH-5, 5, 5);
  	}
  #endif
  	return (mSlideout ? RenderSlideout() : 0);
  }
  
  int GUIConsole::Render(void)
  {
  	if (!isConditionTrue())
  		return 0;
  
  	if (mSlideout && mSlideoutState == hidden)
  		return RenderSlideout();
  
  	return RenderConsole();
  }
  
  int GUIConsole::Update(void)
  {
  	if (mSlideout && mSlideoutState != visible)
  	{
  		if (mSlideoutState == hidden)
  			return 0;
  
  		if (mSlideoutState == request_hide)
  			mSlideoutState = hidden;
  
  		if (mSlideoutState == request_show)
  			mSlideoutState = visible;
  
  		// Any time we activate the console, we reset the position
  		SetVisibleListLocation(rConsole.size() - 1);
  		mUpdate = 1;
  		scrollToEnd = true;
  	}
  
  	pthread_mutex_lock(&console_lock);
  	bool addedNewText = AddLines(&gConsole, &gConsoleColor, &mLastCount, &rConsole, &rConsoleColor);
  	pthread_mutex_unlock(&console_lock);
  	if (addedNewText) {
  		// someone added new text
  		// at least the scrollbar must be updated, even if the new lines are currently not visible
  		mUpdate = 1;
  	}
  
  	if (scrollToEnd) {
  		// keep the last line in view
  		SetVisibleListLocation(rConsole.size() - 1);
  	}
  
  	GUIScrollList::Update();
  
  	if (mUpdate) {
  		mUpdate = 0;
  		if (Render() == 0)
  			return 2;
  	}
  	return 0;
  }
  
  // IsInRegion - Checks if the request is handled by this object
  //  Return 1 if this object handles the request, 0 if not
  int GUIConsole::IsInRegion(int x, int y)
  {
  	if (mSlideout) {
  		// Check if they tapped the slideout button
  		if (x >= mSlideoutX && x < mSlideoutX + mSlideoutW && y >= mSlideoutY && y < mSlideoutY + mSlideoutH)
  			return 1;
  
  		// If we're only rendering the slideout, bail now
  		if (mSlideoutState == hidden)
  			return 0;
  	}
  
  	return GUIScrollList::IsInRegion(x, y);
  }
  
  // NotifyTouch - Notify of a touch event
  //  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
  int GUIConsole::NotifyTouch(TOUCH_STATE state, int x, int y)
  {
  	if (!isConditionTrue())
  		return -1;
  
  	if (mSlideout && x >= mSlideoutX && x < mSlideoutX + mSlideoutW && y >= mSlideoutY && y < mSlideoutY + mSlideoutH) {
  		if (state == TOUCH_START) {
  			if (mSlideoutState == hidden)
  				mSlideoutState = request_show;
  			else if (mSlideoutState == visible)
  				mSlideoutState = request_hide;
  		}
  		return 1;
  	}
  	scrollToEnd = false;
  	return GUIScrollList::NotifyTouch(state, x, y);
  }
  
  size_t GUIConsole::GetItemCount()
  {
  	return rConsole.size();
  }
  
  void GUIConsole::RenderItem(size_t itemindex, int yPos, bool selected __unused)
  {
  	// Set the color for the font
  	if (rConsoleColor[itemindex] == "normal") {
  		gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
  	} else {
  		COLOR FontColor;
  		std::string color = rConsoleColor[itemindex];
  		ConvertStrToColor(color, &FontColor);
  		FontColor.alpha = 255;
  		gr_color(FontColor.red, FontColor.green, FontColor.blue, FontColor.alpha);
  	}
  
  	// render text
  	const char* text = rConsole[itemindex].c_str();
  	gr_textEx_scaleW(mRenderX, yPos, text, mFont->GetResource(), mRenderW, TOP_LEFT, 0);
  }
  
  void GUIConsole::NotifySelect(size_t item_selected __unused)
  {
  	// do nothing - console ignores selections
  }
  