/* GPC (C) 2017-2018 Stephane Charette <stephanecharette@gmail.com>
 * $Id: SessionComponent.cpp 2516 2018-04-07 15:47:48Z stephane $
 */

#include "GPC.hpp"


SessionComponent::SessionComponent(const std::string &uuid) :
	Component				("canvas for session window"),
	session_record			(get_session_record(uuid)),
	description				("description field"),
	replace_image_button	("replace image button"	, DrawableButton::ImageOnButtonBackground),
	reverse_image_button	("reverse image button"	, DrawableButton::ImageOnButtonBackground),
	open_folder_button		("folder button"		, DrawableButton::ImageOnButtonBackground),
	transfer_button			("transfer button"		, DrawableButton::ImageOnButtonBackground),
	table					("image table", this)
{
	setVisible(false); // see SessionComponentLoader::run()

	description.setText(session_record.field[SessionRecord::EField::kDescription]);
	description.addListener(this);

	table.setMultipleSelectionEnabled(false);
	table.setColour(TableListBox::outlineColourId, Colours::black);
	table.setOutlineThickness(1);
	table.setRowHeight(34);	// most images are 150px high, so 150/5=30, plus 2 for the top border and another 2 for the bottom for a total of 34 px

	TableHeaderComponent &header = table.getHeader();
	header.addColumn( "filename"		, 1, 100);
	header.addColumn( "description"		, 2, 100);
	header.addColumn( "group"			, 3, 100);
	header.addColumn( "offset"			, 4, 100);
	header.addColumn( "grade trigger"	, 5, 100);
	header.addColumn( "image"			, 6, 100);
	header.addColumn( "width"			, 7, 100);
	header.addColumn( "height"			, 8, 100);

	header.setPopupMenuActive(true);

	if (cfg().containsKey("SessionTableHeaders"))
	{
		header.restoreFromString( cfg().getValue("SessionTableHeaders") );
	}
	else
	{
		header.setStretchToFitActive(true);
	}

	table.setModel(this);
	table.updateContent();

	addAndMakeVisible(description);
	addAndMakeVisible(replace_image_button);
	addAndMakeVisible(reverse_image_button);
	addAndMakeVisible(open_folder_button);
	addAndMakeVisible(transfer_button);
	addAndMakeVisible(table);

	replace_image_button.setTooltip("replace image");
	reverse_image_button.setTooltip("rotate image");
	open_folder_button.setTooltip("open folder");
	transfer_button.setTooltip("transfer images to " + session_record.field[SessionRecord::EField::kPrinterName]);

	selectedRowsChanged(-1);

	DrawableImage di;

	di.setImage(tango_mona_lisa());
	replace_image_button.setImages(&di);

	di.setImage(tango_refresh());
	reverse_image_button.setImages(&di);

	di.setImage(tango_search_folder());
	open_folder_button.setImages(&di);

	di.setImage(tango_save_as());
	transfer_button.setImages(&di);

	replace_image_button.addListener(this);
	reverse_image_button.addListener(this);
	open_folder_button	.addListener(this);
	transfer_button		.addListener(this);

	// start the thread and make the dialog box modal
	session_component_loader.reset(new SessionComponentLoader(*this));
	session_component_loader->launchThread();

	return;
}


SessionComponent::~SessionComponent(void)
{
	cfg().setValue("SessionTableHeaders", table.getHeader().toString());

	if (session_component_loader)
	{
		session_component_loader->signalThreadShouldExit();
		session_component_loader->stopThread(1000);
	}

	if (transfer_in_progress)
	{
		transfer_in_progress->signalThreadShouldExit();
		transfer_in_progress->stopThread(1000);
	}

	return;
}


void SessionComponent::resized(void)
{
	const float margin_size	= 5.0;
	const float image_size	= 50.0; // raw images are all 32x32, so this should give enough room to draw a button
	const FlexItem::Margin margin(margin_size);
	const FlexItem::Margin buttonMarginX1(0.0f, 0.0f, 0.0f, 1 * margin_size); // 5px margin to create a space between buttons
	const FlexItem::Margin buttonMarginX5(0.0f, 0.0f, 0.0f, 5 * margin_size); // 25px margin to create a space between buttons

	// this is the horizontal flexbox for the description field across the top
	FlexBox topflex;
	topflex.items.add(FlexItem(description).withHeight(25.0f).withFlex(1.0f));

	FlexBox buttonflex;
	buttonflex.flexDirection	= FlexBox::Direction::row;
	buttonflex.justifyContent	= FlexBox::JustifyContent::flexEnd;
	buttonflex.items.add(FlexItem(replace_image_button	).withWidth(image_size).withMargin(buttonMarginX1));
	buttonflex.items.add(FlexItem(reverse_image_button	).withWidth(image_size).withMargin(buttonMarginX1));
	buttonflex.items.add(FlexItem(open_folder_button).withWidth(image_size).withMargin(buttonMarginX5));
	buttonflex.items.add(FlexItem(transfer_button	).withWidth(image_size).withMargin(buttonMarginX5));

	FlexBox flexbox;	// this is the "main" flexbox for the component
	flexbox.flexDirection = FlexBox::Direction::column;
	flexbox.items.add(
		FlexItem(topflex).
		withMargin		(margin).
		withHeight		(25.0f).
		withMinHeight	(25.0f).
		withMaxHeight	(25.0f));

	flexbox.items.add(
		FlexItem(buttonflex).
		withMargin		(margin_size).
		withHeight		(image_size).
		withMinHeight	(image_size).
		withMaxHeight	(image_size) );

	flexbox.items.add(
		FlexItem(table).
		withFlex		(1.0			).
		withMargin		(margin			) );

	Rectangle<float> r = getLocalBounds().toFloat();
	r.reduce(margin_size, margin_size);	// leave a margin around the edge of the window
	flexbox.performLayout( r );

	return;
}


void SessionComponent::buttonClicked(Button *button)
{
	if (button == &open_folder_button)
	{
		session_record.get_ijs_file().revealToUser();
	}
	else if (button == &replace_image_button)
	{
		const int row = table.getLastRowSelected();
		if (row >= 0 && row < getNumRows())
		{
			FileChooser chooser("Select the new image...", File::getSpecialLocation(File::userDesktopDirectory), "*.*");
			const bool result = chooser.browseForFileToOpen();
			if (result)
			{
				Image new_image;
				File file = chooser.getResult();
				if (file.getFileExtension() == ".ijb")
				{
					IJB ijb;
					ijb.load(file);
					new_image = ijb.img;
				}
				else if (file.getFileExtension() == ".bmp")
				{
					new_image = load_image_from_bmp(file);
				}
				else
				{
					// if we get here, then hopefully JUCE knows how to parse this file format!
					new_image = ImageFileFormat::loadFrom(file);
				}

				if (not new_image.isValid())
				{
					const std::string msg = "Failed to read the specified file, or unknown image file format.";
					LOG_MSG(msg);
					AlertWindow::showMessageBox(AlertWindow::AlertIconType::WarningIcon, file.getFullPathName(), msg);
				}
				else
				{
					IJB & ijb = print_job.ijbs[row];
					if (ijb.img.getWidth() != new_image.getWidth() ||
						ijb.img.getHeight() != new_image.getHeight())
					{
						std::stringstream ss;
						ss	<< "Failed to assign the new image due to mismatched size."									<< std::endl
							<< ""																						<< std::endl
							<< "The original image is "	<< ijb.img.getWidth() << " x " << ijb.img.getHeight() << "."	<< std::endl
							<< "The new image measures "		<< new_image.getWidth() << " x " << new_image.getHeight() << ".";
						LOG_MSG(ss.str());
						AlertWindow::showMessageBox(AlertWindow::AlertIconType::WarningIcon, file.getFullPathName(), ss.str());
					}
					else
					{
						ijb.set_new_image(new_image);
						table.repaint();
					}
				}
			}
		}
	}
	else if (button == &reverse_image_button)
	{
		const int row = table.getLastRowSelected();
		if (row >= 0 && row < getNumRows())
		{
			IJB & ijb = print_job.ijbs[row];
			ijb.rotate_image_180();
			table.repaint();
		}
	}
	else if (button == &transfer_button)
	{
		const bool send_ijs = cfg().get_bool("also_send_ijs_file");

		std::string msg = "Are you certain you want to transfer ";
		if (send_ijs)
		{
			msg += "this print job and ";
		}
		msg +=	session_record.field[SessionRecord::EField::kNumberOfImages	] + " images to " +
				session_record.field[SessionRecord::EField::kPrinterName	] + "?";

		const bool result = AlertWindow::showOkCancelBox(
				AlertWindow::AlertIconType::QuestionIcon,
				session_record.field[SessionRecord::EField::kDescription],
				msg,
				"Send to " + session_record.field[SessionRecord::EField::kPrinterName]);

		if (result)
		{
			transfer_in_progress.reset(new SessionComponentTransfer(*this));
			transfer_in_progress->launchThread();
		}
	}

	return;
}


int SessionComponent::getNumRows(void)
{
	return print_job.ijbs.size();
}


void SessionComponent::paintCell(Graphics &g, int rowNumber, int columnId, int width, int height, bool rowIsSelected)
{
	// rows as zero-based, columns are 1-based:
	//
	//		1:  filename
	//		2:  description
	//		3:  group number
	//		4:  position
	//		5:  grade trigger
	//		6:  image
	//		7:  width
	//		8:  height
	//
	if (rowNumber < 0 || rowNumber >= getNumRows() || columnId < 1 || columnId > 8)
	{
		// do nothing -- invalid row or column
		return;
	}

	// draw the text, the image, and the right-hand-side dividing line between cells

	const IJS & ijs			= print_job.ijs;
	const IJB & ijb			= print_job.ijbs.at(rowNumber);
	const auto *bitmap_rec	= ijs.bitmaps.at(rowNumber);
	const size_t group_id	= bitmap_rec->group_number;
	const auto *group_rec	= ijs.groups.at(group_id - 1);
	const Image &img		= ijb.img;

	std::string text;

	if (columnId == 1)
	{
		text = ijb.rec->short_name;
	}
	else if (columnId == 2)
	{
		text = session_record.image_descriptions[ijb.rec->short_name];
	}
	else if (columnId == 3)
	{
		text = std::to_string(group_id);
	}
	else if (columnId == 4)
	{
		// position
		text = std::to_string(ByteOrder::swap(group_rec->horizontal_offset)) + " x " + std::to_string(ByteOrder::swap(group_rec->vertical_offset));
	}
	else if (columnId == 5)
	{
		// grade trigger
		text = bitmap_rec->trigger_value;
	}
	else if (columnId == 6)
	{
		const RectanglePlacement flag(
			RectanglePlacement::xLeft				|
			RectanglePlacement::yMid				|
			RectanglePlacement::onlyReduceInSize	);

		g.drawImageWithin(img, 2, 2, width - 4, height - 4, flag);
	}
	else if (columnId == 7)
	{
		text = std::to_string(img.getWidth());
	}
	else if (columnId == 8)
	{
		text = std::to_string(img.getHeight());
	}

	if (text.empty() == false)
	{
		g.setColour(Colours::darkblue);
		g.drawFittedText(text, 2, 2, width - 4, height - 4, Justification::centredLeft, 2);
	}

	// draw the divider on the right side of the column
	g.setColour( Colours::lightgrey );
	g.drawVerticalLine(width - 1, 0.0f, static_cast<float>(height) );

	return;
}


void SessionComponent::paintRowBackground(Graphics &g, int rowNumber, int width, int height, bool rowIsSelected)
{
	if (rowNumber < 0 || rowNumber >= getNumRows())
	{
		// invalid row
		return;
	}

	// image backgrounds are white, so pick a different colour for the container background
	Colour colour = Colour(0xee, 0xff, 0xee); // very pale green
	if (rowIsSelected)
	{
		colour = Colours::lightgreen;
	}

	g.fillAll( colour );

	// draw the cell bottom divider between rows
	g.setColour( Colours::lightgrey );
	g.drawHorizontalLine(height - 1, 0.0f, static_cast<float>(width));

	return;
}


void SessionComponent::selectedRowsChanged(int lastRowSelected)
{
	const bool selectionIsValid = (lastRowSelected >= 0 && lastRowSelected < getNumRows());

	replace_image_button.setEnabled(selectionIsValid);
	reverse_image_button.setEnabled(selectionIsValid);

	return;
}


void SessionComponent::textEditorTextChanged(TextEditor &editor)
{
	const std::string text = editor.getText().toStdString();

	session_record.field[SessionRecord::EField::kDescription] = text;
	summaryWnd().canvas.table.repaint();

	session_record.schedule_sessions_to_be_saved();

	return;
}


void SessionComponent::returnKeyPressed(int row)
{
	edit_description_field(row);

	return;
}


void SessionComponent::cellDoubleClicked(int row, int column, const MouseEvent &event)
{
	edit_description_field(row);

	return;
}


void SessionComponent::edit_description_field(const int row)
{
	// prevent multiple callout boxes from being stacked on top of each other
	static std::atomic<bool> already_showing_calloutbox(false);

	if (already_showing_calloutbox == false)
	{
		already_showing_calloutbox = true;

		try
		{
			// rows are zero-based, columns are 1-based

			const IJB & ijb = print_job.ijbs.at(row);
			const std::string text = session_record.image_descriptions[ijb.rec->short_name];

			FieldEditor editor;
			editor.setText(text);

			// get the rectangle that covers the entire cell in question so the callout box can be positioned correctly
			const Rectangle<int> r(table.getCellPosition(2, row, true));

			CallOutBox cob(editor, r, &table);
			cob.setLookAndFeel(gpc().laf.get());
			const int result = cob.runModalLoop();

			if (result == 1)
			{
				session_record.image_descriptions[ijb.rec->short_name] = editor.getText().toStdString();
				session_record.schedule_sessions_to_be_saved();
				table.repaint();
			}
		}
		catch(...)
		{
			LOG_MSG("exception caught in " << __FUNCTION__);
		}

		already_showing_calloutbox = false;
	}

	return;
}
