//SPDX-License-Identifier: GPL-3.0-or-later
/*****************************************************************************/
/*  Copyright 2020 WSL Institute for Snow and Avalanche Research  SLF-DAVOS  */
/*****************************************************************************/
/* This file is part of INIshell.
   INIshell 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.

   INIshell 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 INIshell.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <src/gui/PreviewEdit.h>
#include <src/main/colors.h>

#include <QDir>
#include <QFontMetrics>
#include <QMainWindow>
#include <QtWidgets>

#ifdef DEBUG
	#include <iostream>
#endif

/**
 * @brief Default constructor for a PreviewEdit.
 * @param[in] monospace Use a monospaced font?
 * @param[in] parent The parent widget.
 */
PreviewEdit::PreviewEdit(const bool& monospace, QWidget *parent) : QPlainTextEdit(parent)
{
	QList<QKeySequence> shortcuts;
	QStringList descriptions;

	/* set some keyboard shortcuts as tooltip */
	shortcuts << QKeySequence::Cut; descriptions << tr("Cut (whole line on empty selection)");
	shortcuts << QKeySequence::DeleteEndOfLine; descriptions << tr("Delete to the end of the line");
	shortcuts << QKeySequence::Undo; descriptions << tr("Undo");
	shortcuts << QKeySequence::Redo; descriptions << tr("Redo");
	shortcuts << QKeySequence::AddTab; descriptions << tr("Add tab");
	shortcuts << QKeySequence::NextChild; descriptions << tr("Move to next tab");
	shortcuts << QKeySequence::PreviousChild; descriptions << tr("Move to previous tab");
	shortcuts << QKeySequence::Find; descriptions << tr("Find text");
	shortcuts << QKeySequence::SelectNextWord; descriptions << tr("Select next word");
	QString shortcut_help( tr("Some supported editor shortcuts:\n\n") );
	for (int ii = 0; ii < shortcuts.length(); ++ii) {
		shortcut_help.append(shortcuts.at(ii).toString() +
			QString( "\t" ).repeated(3) + descriptions.at(ii) + "\n"); //TODO: the tabs don't align well?
	}
	shortcut_help.replace("Backtab", "Tab"); //bit of an unusual name
	shortcut_help.chop(1); //trailing '\n'
	this->setToolTip(shortcut_help);

	/* add line number side panel */
	if (monospace) setMonospacedFont();
	sidePanel = new PreviewSidePanel(this);
	sidePanel->setStyleSheet("QWidget {background-color: " + colors::getQColor("syntax_background").name() + "; color: " +
	    colors::getQColor("sl_base01").name() + "; font-style: italic; font-size: 9pt}");
	updateSidePanelWidth();

	connect(this, &PreviewEdit::blockCountChanged, this, &PreviewEdit::updateSidePanelWidth);
	connect(this, &PreviewEdit::updateRequest, this, &PreviewEdit::updateSidePanel);

	highlighter_ = new INISyntaxHighlighter(this->document()); //this parents the highlighter
}

/**
 * @brief Try to set a monospaced font for files that are space indented.
 */
void PreviewEdit::setMonospacedFont()
{
	QFont mono_font( QFontDatabase::systemFont(QFontDatabase::FixedFont) ); //system recommendation
	mono_font.setPointSize( this->font().pointSize() );
	this->setFont(mono_font);
}

/**
 * @brief Retrieve full file path of opened content.
 * @return Path and name of currently opened file.
 */
QString PreviewEdit::getFullFilePath() const {
	return QDir::toNativeSeparators(file_path_ + "/" + file_name_);
}

/**
 * @brief Remember the currently displayed file's path.
 * @details This function splits its argument up into file path and name, so that
 * any one or both combined can always be retrieved.
 * @param[in] file_path Full path with name and extension to opened file.
 */
void PreviewEdit::setFile(const QString &file_path) {
	const QFileInfo file_info( file_path );
	file_name_ = file_info.fileName();
	if (file_info.exists()) //avoid warning
		file_path_ = file_info.absolutePath();
	else
		file_path_ = QDir::currentPath(); //only a name was given
}

/**
 * @brief Show info when users drag files over the editor.
 * @details We let the text editor handle the drag and drop behaviour. However, we give notice that
 * dropping files in an area outside of the text editor itself will open the files.
 * @param[in] event The drag move event.
 */
void PreviewEdit::dragMoveEvent(QDragMoveEvent *event)
{
	const QMainWindow* preview_window( qobject_cast<QMainWindow*>(this->parent()->parent()->parent()) ); //QStackedWidget->QTabWidget->PreviewWindow
	if (preview_window) {
		if (event->mimeData()->hasUrls()) {
			preview_window->statusBar()->showMessage(tr("Drop files over the menu or tab titles to open."));
			preview_window->statusBar()->show(); //might be hidden from closing search bar
		}
	}

	event->acceptProposedAction(); //progress as normal

	//Note: overriding QDragENTERevent instead causes "QDragManager::drag in possibly invalid state" for no apparent reason,
	//but this here should be good.
}

/****************************************************************************
** This following code is based on the line numbering example in Qt's documentation at
** https://doc.qt.io/qt-5/qtwidgets-widgets-PreviewEdit-example.html.
** It is lincensed under a BSD license (see original text below).
** Changes by M. Bavay, 2020-03-20 for WSL/SLF
**/

/**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** BSD License Usage
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
*/

int PreviewEdit::getSidePanelWidth()
{
	//since log10 returns 0 for numbers <10, add 1. For a nicer look, we add 0.5 char's width
	const double digits = floor(log10(qMax(1, blockCount()))) + 1.5;
	const int width = static_cast<int>(fontMetrics().boundingRect(QLatin1Char('0')).width() * digits);
	return width;
}

void PreviewEdit::updateSidePanelWidth()
{
	//we add a small margin between the line number and the line itself
	const int margin_width = fontMetrics().boundingRect(QLatin1Char('0')).width() / 2;
	setViewportMargins(getSidePanelWidth()+margin_width, 0, 0, 0);
}

void PreviewEdit::updateSidePanel(const QRect &rect, int dy)
{
	if (dy)
		sidePanel->scroll(0, dy);
	else
		sidePanel->update(0, rect.y(), sidePanel->width(), rect.height());

	if (rect.contains(viewport()->rect()))
		updateSidePanelWidth();
}

void PreviewEdit::resizeEvent(QResizeEvent *event)
{
	QPlainTextEdit::resizeEvent(event);
	const QRect cr( contentsRect() );
	sidePanel->setGeometry(QRect(cr.left(), cr.top(), getSidePanelWidth(), cr.height()));
}

void PreviewEdit::repaintSidePanel(QPaintEvent *event)
{
	QPainter painter(sidePanel);
	QTextBlock block( firstVisibleBlock() );
	int lineNumber = block.blockNumber();
	int top = static_cast<int>(blockBoundingGeometry(block).translated(contentOffset()).top());
	int bottom = top + static_cast<int>(blockBoundingRect(block).height());

	while (block.isValid() && top <= event->rect().bottom()) {
		if (block.isVisible() && bottom >= event->rect().top()) {
			const QString number( QString::number(lineNumber + 1) );
			painter.drawText(0, top, sidePanel->width(), fontMetrics().height(), Qt::AlignRight | Qt::AlignVCenter, number);
		}

		block = block.next();
		top = bottom;
		bottom = top + static_cast<int>(blockBoundingRect(block).height());
		++lineNumber;
	} //end while
}
