//##########################################################################
//#                                                                        #
//#                              CLOUDCOMPARE                              #
//#                                                                        #
//#  This program 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; version 2 or later of the License.      #
//#                                                                        #
//#  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 General Public License for more details.                          #
//#                                                                        #
//#          COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI)             #
//#                                                                        #
//##########################################################################

//GUIs generated by Qt Designer
#include <ui_openAsciiFileDlg.h>

//Local
#include "AsciiOpenDlg.h"
#include "FileIOFilter.h"

//qCC_db
#include <ccPointCloud.h>

//Qt
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFile>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QSpinBox>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QTextStream>
#include <QToolButton>
#include <QDesktopWidget>

//system
#include <cassert>
#include <cstdio>
#include <cstring>

//Semi-persistent value for max. cloud size
static double s_maxCloudSizeDoubleSpinBoxValue = (CC_MAX_NUMBER_OF_POINTS_PER_CLOUD / 1.0e6);
//Semi-persistent value for C.S. entities (quaternion) scale
static double s_csEntitiesScale = 1.0;

//Max number of points/lines to detect a column as 'labels'
static unsigned s_maxLabelCount = 256;

//! Dialog 'context'
struct AsciiOpenContext
{
	//! Default initializer
	AsciiOpenContext()
		: separator(' ')
		, extractSFNameFrom1stLine(false)
		, maxPointCountPerCloud(0)
		, skipLines(0)
		, applyAll(false)
		, commaDecimal(false)
	{}

	//! Saves state
	void save(Ui_AsciiOpenDialog* ui)
	{
		extractSFNameFrom1stLine = ui->extractSFNamesFrom1stLineCheckBox->isChecked();
		maxPointCountPerCloud = ui->maxCloudSizeDoubleSpinBox->value();
		separator = ui->lineEditSeparator->text().at(0);
		skipLines = ui->spinBoxSkipLines->value();
		commaDecimal = ui->commaDecimalCheckBox->isChecked();
	}

	//! Restores state
	void load(Ui_AsciiOpenDialog* ui) const
	{
		ui->maxCloudSizeDoubleSpinBox->setValue(maxPointCountPerCloud);
		ui->lineEditSeparator->blockSignals(true);
		ui->lineEditSeparator->setText(separator);
		ui->lineEditSeparator->blockSignals(false);
		ui->spinBoxSkipLines->blockSignals(true);
		ui->spinBoxSkipLines->setValue(skipLines);
		ui->spinBoxSkipLines->blockSignals(false);
		ui->extractSFNamesFrom1stLineCheckBox->setChecked(extractSFNameFrom1stLine);
		ui->extractSFNamesFrom1stLineCheckBox->setEnabled(skipLines > 0);
		ui->commaDecimalCheckBox->blockSignals(true);
		ui->commaDecimalCheckBox->setChecked(commaDecimal);
		ui->commaDecimalCheckBox->setEnabled(separator != ',');
		ui->commaDecimalCheckBox->blockSignals(false);
	}

	AsciiOpenDlg::Sequence sequence;
	QChar separator;
	bool extractSFNameFrom1stLine;
	double maxPointCountPerCloud;
	int skipLines;
	bool applyAll;
	bool commaDecimal;
};

//! Semi-persistent loading context
static QScopedPointer<AsciiOpenContext> s_asciiOpenContext;

AsciiOpenDlg::AsciiOpenDlg(QWidget* parent)
	: QDialog(parent)
	, m_ui(new Ui_AsciiOpenDialog)
	, m_separator(' ')
	, m_averageLineSize(-1.0)
	, m_stream(nullptr)
	, m_columnsCount(0)
{
	m_ui->setupUi(this);

	m_ui->commentLinesSkippedLabel->hide();

	connect(m_ui->applyButton,				&QPushButton::clicked,						this, &AsciiOpenDlg::apply);
	connect(m_ui->applyAllButton,			&QPushButton::clicked,						this, &AsciiOpenDlg::applyAll);
	connect(m_ui->cancelButton,				&QPushButton::clicked,						this, &AsciiOpenDlg::reject);
	connect(m_ui->lineEditSeparator,		&QLineEdit::textChanged,					this, &AsciiOpenDlg::onSeparatorChange);
	connect(m_ui->commaDecimalCheckBox,		&QCheckBox::toggled,						this, &AsciiOpenDlg::commaDecimalCheckBoxToggled);
	connect(m_ui->spinBoxSkipLines,			qOverload<int>(&QSpinBox::valueChanged),	this, &AsciiOpenDlg::onSkippedLinesChanged);
	connect(m_ui->resetColumnsToolButton,	&QToolButton::clicked,						this, &AsciiOpenDlg::resetColumnRoles);

	//shortcut buttons
	connect(m_ui->toolButtonShortcutSpace,		&QToolButton::clicked, this, &AsciiOpenDlg::shortcutButtonPressed);
	connect(m_ui->toolButtonShortcutComma,		&QToolButton::clicked, this, &AsciiOpenDlg::shortcutButtonPressed);
	connect(m_ui->toolButtonShortcutSemicolon,	&QToolButton::clicked, this, &AsciiOpenDlg::shortcutButtonPressed);

	m_ui->maxCloudSizeDoubleSpinBox->setMaximum(CC_MAX_NUMBER_OF_POINTS_PER_CLOUD / 1.0e6);
	m_ui->maxCloudSizeDoubleSpinBox->setValue(s_maxCloudSizeDoubleSpinBoxValue);

	m_ui->quaternionFrame->setVisible(false);
	m_ui->quatCSScaleDoubleSpinBox->setValue(s_csEntitiesScale);

	QSize screenSize = QApplication::desktop()->screenGeometry().size();
	setMaximumSize(screenSize);
}

AsciiOpenDlg::~AsciiOpenDlg()
{
	delete m_ui;
	m_ui = nullptr;
}

bool AsciiOpenDlg::setInput(const QString& filename, QTextStream* stream/*=nullptr*/)
{
	m_filename = filename;
	m_stream = stream;

	// update title
	m_ui->lineEditFileName->setText(m_filename);

	// try to restore the previous context
	if (s_asciiOpenContext && restorePreviousContext())
	{
		return s_asciiOpenContext->applyAll;
	}

	// else try to find the file format automatically
	autoFindBestSeparator();

	return false; // the current configuration shouldn't be applied automatically ('apply all' is false, or the configuration is different from the previous one)
}

void AsciiOpenDlg::autoFindBestSeparator()
{
	const QList<QChar> separators{	QChar(' '),
									QChar(','),
									QChar(';')
								 };

	//We try all default separators...
	size_t maxValidColumnCount = 0;
	QChar bestSep = separators.front();
	for (QChar sep : separators)
	{
		setSeparator(sep); //this eventually calls 'updateTable'

		size_t validColumnCount = 0;
		for (ColumnType type : m_columnType)
		{
			if (type != TEXT)
			{
				++validColumnCount;
			}
		}

		//...and look for the separator that yields the best results
		if (validColumnCount > maxValidColumnCount)
		{
			maxValidColumnCount = validColumnCount;
			bestSep = sep;
		}
	}

	setSeparator(bestSep); //this eventually calls 'updateTable'
}

void AsciiOpenDlg::onSkippedLinesChanged(int lineCount)
{
	if (lineCount < 0)
	{
		assert(false);
		return;
	}

	updateTable();
}

static bool CouldBeX (const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::X().toUpper()) || colHeader == "TX"; }
static bool CouldBeY (const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::Y().toUpper()) || colHeader == "TY"; }
static bool CouldBeZ (const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::Z().toUpper()) || colHeader == "TZ"; }
static bool CouldBeRf(const QString& colHeader) { return colHeader == AsciiHeaderColumns::Rf().toUpper(); }
static bool CouldBeGf(const QString& colHeader) { return colHeader == AsciiHeaderColumns::Gf().toUpper(); }
static bool CouldBeBf(const QString& colHeader) { return colHeader == AsciiHeaderColumns::Bf().toUpper(); }
static bool CouldBeAf(const QString& colHeader) { return colHeader == AsciiHeaderColumns::Af().toUpper(); }
static bool CouldBeR (const QString& colHeader) { return colHeader == AsciiHeaderColumns::R().toUpper() || colHeader.contains("RED"); }
static bool CouldBeG (const QString& colHeader) { return colHeader == AsciiHeaderColumns::G().toUpper() || colHeader.contains("GREEN"); }
static bool CouldBeB (const QString& colHeader) { return colHeader == AsciiHeaderColumns::B().toUpper() || colHeader.contains("BLUE"); }
static bool CouldBeA (const QString& colHeader) { return colHeader == AsciiHeaderColumns::A().toUpper() || colHeader.contains("ALPHA"); }
static bool CouldBeNx(const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::Nx().toUpper()) || (colHeader.contains("NORM") && colHeader.contains("X")); }
static bool CouldBeNy(const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::Ny().toUpper()) || (colHeader.contains("NORM") && colHeader.contains("Y")); }
static bool CouldBeNz(const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::Nz().toUpper()) || (colHeader.contains("NORM") && colHeader.contains("Z")); }

static bool CouldBeGrey(const QString& colHeader) { return colHeader == AsciiHeaderColumns::Grey().toUpper(); }
static bool CouldBeRGBi(const QString& colHeader) { return colHeader == AsciiHeaderColumns::RGB32i().toUpper(); }
static bool CouldBeRGBf(const QString& colHeader) { return colHeader == AsciiHeaderColumns::RGB32f().toUpper(); }
static bool CouldBeScal(const QString& colHeader) { return colHeader.contains("SCALAR") || colHeader.contains("INTENSITY"); }
static bool CouldBeQuatW(const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::QuatX().toUpper()) || colHeader == "QW" || colHeader == "Q0"; }
static bool CouldBeQuatX(const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::QuatY().toUpper()) || colHeader == "QX"; }
static bool CouldBeQuatY(const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::QuatZ().toUpper()) || colHeader == "QY"; }
static bool CouldBeQuatZ(const QString& colHeader) { return colHeader.startsWith(AsciiHeaderColumns::QuatW().toUpper()) || colHeader == "QZ"; }
static bool CouldBeLabel(const QString& colHeader) { return colHeader.contains("LABEL") || colHeader.contains("NAME"); }

static const unsigned MAX_COLUMNS = 512;				//maximum number of columns that can be handled
static const unsigned LINES_READ_FOR_STATS = 200;		//number of lines read for stats
static const unsigned DISPLAYED_LINES = 20;				//number of displayed lines

static unsigned X_BIT		= 1;
static unsigned Y_BIT		= 2;
static unsigned Z_BIT		= 4;
static unsigned W_BIT		= 8;
static unsigned XYZ_BITS	= X_BIT | Y_BIT | Z_BIT;
static unsigned XYZW_BITS	= XYZ_BITS | W_BIT;

static int EnabledBits(unsigned bitField)
{
	int count = 0;
	if (bitField & X_BIT)
		++count;
	if (bitField & Y_BIT)
		++count;
	if (bitField & Z_BIT)
		++count;
	if (bitField & W_BIT)
		++count;

	return count;
}

void AsciiOpenDlg::onSeparatorChange(const QString& separator)
{
	assert(separator.size() == 1);
	if (separator.length() < 1)
	{
		m_ui->asciiCodeLabel->setText("Enter a valid character!");
		m_ui->buttonWidget->setEnabled(false);
		m_ui->tableWidget->clear();
		m_columnType.clear();
		return;
	}

	//new separator
	m_separator = separator[0];
	m_ui->asciiCodeLabel->setText(QString("(ASCII code: %1)").arg(m_separator.unicode()));

	m_headerLine.clear(); //to force re-assignation of columns!
	m_columnType.clear();

	updateTable();
}

bool AsciiOpenDlg::useCommaAsDecimal() const
{
	return m_ui->commaDecimalCheckBox->isEnabled() && m_ui->commaDecimalCheckBox->isChecked();
}

void AsciiOpenDlg::commaDecimalCheckBoxToggled(bool)
{
	onSeparatorChange(m_ui->lineEditSeparator->text());
}

void AsciiOpenDlg::updateTable()
{
	m_ui->tableWidget->setEnabled(false);
	m_ui->extractSFNamesFrom1stLineCheckBox->setEnabled(false);

	bool hadValidHeader = !m_headerLine.isEmpty();
	m_headerLine.clear();
	m_ui->headerLabel->setVisible(false);

	if (m_filename.isEmpty() && m_stream == nullptr)
	{
		m_ui->tableWidget->clear();
		return;
	}

	//open the file
	QFile file;
	if (nullptr == m_stream)
	{
		file.setFileName(m_filename);
		if (!file.open(QFile::ReadOnly))
		{
			m_ui->tableWidget->clear();
			m_columnType.clear();
			return;
		}
		m_stream = new QTextStream(&file);
	}
	assert(m_stream);
	m_stream->seek(0);

	//we skip the first lines (if requested)
	{
		for (int i = 0; i < m_ui->spinBoxSkipLines->value(); )
		{
			QString currentLine = m_stream->readLine();
			if (currentLine.isNull())
			{
				//end of file reached
				assert(m_stream->atEnd());
				break;
			}
			if (currentLine.isEmpty())
			{
				//empty lines are simply ignored
				continue;
			}
			//we keep track of the first skipped line (= potential header)
			if (i == 0)
			{
				m_headerLine = currentLine;
			}
			++i;
		}
	}

	//if the old setup has less than 3 columns, we forget it
	if (m_columnsCount < 3)
	{
		m_ui->tableWidget->clear();
		m_columnType.clear();
		m_columnsCount = 0;
	}
	m_ui->tableWidget->setRowCount(DISPLAYED_LINES + 1);	//+1 for first line shifting

	unsigned lineCount = 0;			//number of lines read
	unsigned totalChars = 0;		//total read characters (for stats)
	unsigned columnsCount = 0;		//max columns count per line
	unsigned commentLines = 0;		//number of comments line skipped

	std::vector<bool> valueIsNumber;	//identifies columns with numbers only [mandatory]
	std::vector<bool> valueIsBelowOne;	//identifies columns with values between -1 and 1 only
	std::vector<bool> valueIsInteger;	//identifies columns with integer values only
	std::vector<bool> valueIsBelow255;	//identifies columns with integer values between 0 and 255 only

	bool commaAsDecimal = useCommaAsDecimal();
	QLocale locale(commaAsDecimal ? QLocale::French : QLocale::English);
	QChar decimalPoint = commaAsDecimal ? ',' : '.';
	while (lineCount < LINES_READ_FOR_STATS)
	{
		QString currentLine = m_stream->readLine();
		if (currentLine.isNull())
		{
			//end of file reached
			break;
		}
		if (currentLine.isEmpty())
		{
			//empty lines are ignored
			continue;
		}

		//we recognize "//" as the beginning of a comment
		if (!currentLine.startsWith("//")/* || !currentLine.startsWith("#")*/)
		{
			QStringList parts = currentLine.simplified().split(m_separator, QString::SkipEmptyParts);
			
			if (lineCount < DISPLAYED_LINES)
			{
				unsigned partsCount = std::min(MAX_COLUMNS, static_cast<unsigned>(parts.size()));
				bool columnCountHasIncreased = (partsCount > columnsCount);

				//do we need to add one or several new columns?
				if (columnCountHasIncreased)
				{
					//we also extend vectors
					for (unsigned i = columnsCount; i < partsCount; ++i)
					{
						valueIsNumber.push_back(true);
						valueIsBelowOne.push_back(true);
						valueIsBelow255.push_back(true);
						valueIsInteger.push_back(true);
					}

					if (m_ui->tableWidget->columnCount() < static_cast<int>(partsCount))
					{
						//DGM: at this stage we must not reduce the table!
						//The first line is sometimes smaller than the next ones
						//and we want to keep the widgets/configuration for the
						//other columns!
						m_ui->tableWidget->setColumnCount(partsCount);
					}
					else if (m_ui->tableWidget->columnCount() > static_cast<int>(partsCount))
					{
						//remove the unnecessary cells!
						for (int i = static_cast<int>(partsCount); i < m_ui->tableWidget->columnCount(); ++i)
						{
							m_ui->tableWidget->setItem(lineCount + 1, i, nullptr);
						}
					}
					columnsCount = partsCount;
				}

				//we fill the current row with extracted parts
				for (unsigned i = 0; i < partsCount; ++i)
				{
					QTableWidgetItem* newItem = new QTableWidgetItem(parts[i]);

					//test values
					bool isANumber = false;
					double value = locale.toDouble(parts[i], &isANumber);
					if (!isANumber)
					{
						valueIsNumber[i]   = false;
						valueIsBelowOne[i] = false;
						valueIsInteger[i]  = false;
						valueIsBelow255[i] = false;
						newItem->setBackground(QBrush(QColor(255, 160, 160)));
					}
					else
					{
						if (columnCountHasIncreased || (lineCount == 1 && !valueIsNumber[i]))
						{
							//the previous lines were probably header lines
							//we can forget about their content otherwise it will prevent us from detecting the right pattern
							valueIsNumber[i]   = true;
							valueIsBelowOne[i] = true;
							valueIsInteger[i]  = true;
							valueIsBelow255[i] = true;
						}
						valueIsBelowOne[i] = valueIsBelowOne[i] && (std::abs(value) <= 1.0);
						valueIsInteger[i] = valueIsInteger[i] && !parts[i].contains(decimalPoint);
						valueIsBelow255[i] = valueIsBelow255[i] && valueIsInteger[i] && (value >= 0.0 && value <= 255.0);
					}

					m_ui->tableWidget->setItem(lineCount + 1, i, newItem); //+1 for first line shifting
				}
			}

			totalChars += currentLine.size() + 1; //+1 for return char at eol
			++lineCount;
		}
		else
		{
			if (m_ui->spinBoxSkipLines->value() == 0 && commentLines == 0)
			{
				//if the very first line is a comment, then we automatically skip it!
				//this way it will be considered as a header
				m_headerLine = currentLine;
				setSkippedLines(1, true);
			}
			++commentLines;
		}
	}

	file.close();

	//now we can reduce the table (if necessary)
	if (m_ui->tableWidget->columnCount() > static_cast<int>(columnsCount))
	{
		m_ui->tableWidget->setColumnCount(columnsCount);
	}

	//process header line
	if (!m_headerLine.isEmpty())
	{
		m_headerLine = m_headerLine.trimmed();
		int n = 0;
		while (n < m_headerLine.size() && m_headerLine.at(n) == '/')
		{
			++n;
		}
		if (n != 0)
		{
			m_headerLine.remove(0, n);
		}

		QString displayHeader = m_headerLine;
		if (m_headerLine.length() > 256)
		{
			displayHeader = m_headerLine.left(256) + "...";
		}
		m_ui->headerLabel->setText(QString("Header: ") + displayHeader);
		m_ui->headerLabel->setVisible(true);
		m_ui->extractSFNamesFrom1stLineCheckBox->setEnabled(true);
	}

	if (commentLines)
	{
		m_ui->commentLinesSkippedLabel->setText(QString("+ %1 comment line(s) skipped").arg(commentLines));
		m_ui->commentLinesSkippedLabel->setVisible(true);
	}

	if (lineCount == 0 || columnsCount == 0)
	{
		m_averageLineSize = -1.0;
		m_ui->tableWidget->clear();
		m_columnType.clear();
		return;
	}

	//average line size
	m_averageLineSize = static_cast<double>(totalChars) / lineCount;
	unsigned approximateTotalLineCount = static_cast<unsigned>(file.size() / m_averageLineSize);

	//we add a type selector for each column
	QStringList propsText;
	{
		propsText.reserve(ASCII_OPEN_DLG_TYPES_COUNT);
		for (unsigned i = 0; i < ASCII_OPEN_DLG_TYPES_COUNT; i++)
		{
			propsText << QString(ASCII_OPEN_DLG_TYPES_NAMES[i]);
		}
	}

	//remove unnecessary columns
	{
		while (columnsCount < m_columnsCount)
			m_ui->tableWidget->removeColumn(--m_columnsCount);
		if (m_columnType.size() > columnsCount)
			m_columnType.resize(columnsCount, UNKNOWN);
		for (unsigned i = lineCount + 1; i <= DISPLAYED_LINES; ++i)
			m_ui->tableWidget->removeRow(i);
	}

	//setup table and widgets
	{
		//Icons
		static const QIcon xIcon		(QString::fromUtf8(":/CC/images/typeXCoordinate.png"));
		static const QIcon yIcon		(QString::fromUtf8(":/CC/images/typeYCoordinate.png"));
		static const QIcon zIcon		(QString::fromUtf8(":/CC/images/typeZCoordinate.png"));
		static const QIcon NormIcon		(QString::fromUtf8(":/CC/images/typeNormal.png"));
		static const QIcon RGBIcon		(QString::fromUtf8(":/CC/images/typeRgbCcolor.png"));
		static const QIcon GreyIcon		(QString::fromUtf8(":/CC/images/typeGrayColor.png"));
		static const QIcon ScalarIcon	(QString::fromUtf8(":/CC/images/typeSF.png"));
		static const QIcon LabelIcon	(QString::fromUtf8(":/CC/images/dbLabelSymbol.png"));
		static const QIcon QuatIcon		(QString::fromUtf8(":/CC/images/typeQuaternion.png"));

		int columnWidth = (m_ui->tableWidget->width() * 9) / (columnsCount * 10);
		columnWidth = std::max(columnWidth, 80);

		for (unsigned i = 0; i < columnsCount; i++)
		{
			QComboBox* columnHeaderWidget = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
			QComboBox* _columnHeader = columnHeaderWidget;
			if (!columnHeaderWidget)
			{
				columnHeaderWidget = new QComboBox();
				columnHeaderWidget->addItems(propsText);
				columnHeaderWidget->setMaxVisibleItems(ASCII_OPEN_DLG_TYPES_COUNT);
				columnHeaderWidget->setCurrentIndex(0);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_X, xIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Y, yIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Z, zIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_NX, NormIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_NY, NormIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_NZ, NormIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_R, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_G, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_B, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_A, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Rf, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Gf, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Bf, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Af, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Grey, GreyIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_RGB32i, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_RGB32f, RGBIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Label, LabelIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_QuatW, QuatIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_QuatX, QuatIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_QuatY, QuatIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_QuatZ, QuatIcon);
				columnHeaderWidget->setItemIcon(ASCII_OPEN_DLG_Scalar, ScalarIcon);

				connect(columnHeaderWidget, qOverload<int>(&QComboBox::currentIndexChanged), this, &AsciiOpenDlg::columnsTypeHasChanged);
			}

			while (m_columnType.size() <= static_cast<size_t>(i))
				m_columnType.push_back(UNKNOWN);
			assert(m_columnType.size() >= static_cast<size_t>(i));

			if (!_columnHeader)
				m_ui->tableWidget->setCellWidget(0, i, columnHeaderWidget);
			m_ui->tableWidget->setColumnWidth(i, columnWidth);

			//a non-numerical column can't be valid
			if (!valueIsNumber[i])
			{
				m_columnType[i] = TEXT;
			}
			else
			{
				// we must do this to ensure we can get a correct result.
				// Otherwise, we may fail in such situations:
				//
				// FILE:
				// Line1   : $ gps file
				// Line2   : $ id name x y z
				// Line3   : 500
				// Line4   : 0    0001.JPG  753811.417453   4307200.381522      1957.803955
				// Linex   : ......
				// Line503 : 499  0500.JPG  753630.672714   4307195.433217      1957.803955
				// 
				// Description:
				// once we open the file, we will get a %m_columnType with 5 values of "TEXT"
				// then if we choose to skip the 3 first lines, we get a %valueIsNumber with 5 "true"
				// but the %m_columnType is still with 5 values of "TEXT" which leads to the failure!
				m_columnType[i] = UNKNOWN;
			}
		}
	}

	//auto-detect columns 'roles'
	{
		//DGM: bit flags now
		unsigned assignedXYZFlags = 0;
		unsigned assignedNormFlags = 0;
		unsigned assignedRGBFlags = 0;
		unsigned assignedQuaternionFlags = 0;

		//split header (if any)
		QStringList headerParts = m_headerLine.simplified().split(m_separator, QString::SkipEmptyParts);
		bool validHeader = (headerParts.size() >= static_cast<int>(columnsCount));
		m_ui->extractSFNamesFrom1stLineCheckBox->setEnabled(validHeader); //can we consider the first ignored line as a header?
		if (!validHeader)
		{
			//no need to keep it (+ it will serve as flag later)
			m_headerLine.clear();
		}
		else if (!hadValidHeader)
		{
			m_ui->extractSFNamesFrom1stLineCheckBox->setChecked(true);
			for (ColumnType& type : m_columnType)
			{
				if (type != TEXT)
					type = UNKNOWN;
			}
			//std::fill(m_columnType.begin(), m_columnType.end(), UNKNOWN); //if the header has changed, we force update of all columns!
		}

		//first with the help of the header (if any)
		bool labelColumnAssigned = false;
		if (validHeader)
		{
			for (unsigned i = 0; i < columnsCount; i++)
			{
				//we try to guess columns if we have a valid header for the first time!
				if (m_columnType[i] == UNKNOWN || m_columnType[i] == IGNORED)
				{
					QComboBox* columnHeaderWidget = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
					assert(columnHeaderWidget);

					columnHeaderWidget->blockSignals(true);

					QString colHeader = headerParts[i].toUpper();

					if ((assignedXYZFlags & X_BIT) == 0 && CouldBeX(colHeader))
					{
						//X
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_X);
						assignedXYZFlags |= X_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedXYZFlags & Y_BIT) == 0 && CouldBeY(colHeader))
					{
						//Y
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Y);
						assignedXYZFlags |= Y_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedXYZFlags & Z_BIT) == 0 && CouldBeZ(colHeader))
					{
						//Z
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Z);
						assignedXYZFlags |= Z_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedRGBFlags & X_BIT) == 0 && CouldBeRf(colHeader))
					{
						//Red
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Rf);
						assignedRGBFlags |= X_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedRGBFlags & Y_BIT) == 0 && CouldBeGf(colHeader))
					{
						//Green
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Gf);
						assignedRGBFlags |= Y_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedRGBFlags & Z_BIT) == 0 && CouldBeBf(colHeader))
					{
						//Blue
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Bf);
						assignedRGBFlags |= Z_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedRGBFlags & X_BIT) == 0 && CouldBeR(colHeader))
					{
						//Red
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_R);
						assignedRGBFlags |= X_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedRGBFlags & Y_BIT) == 0 && CouldBeG(colHeader))
					{
						//Green
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_G);
						assignedRGBFlags |= Y_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedRGBFlags & Z_BIT) == 0 && CouldBeB(colHeader))
					{
						//Blue
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_B);
						assignedRGBFlags |= Z_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedRGBFlags & W_BIT) == 0 && CouldBeA(colHeader))
					{
						//Blue
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_A);
						assignedRGBFlags |= W_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedNormFlags & X_BIT) == 0 && CouldBeNx(colHeader))
					{
						//Nx
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_NX);
						assignedNormFlags |= X_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedNormFlags & Y_BIT) == 0 && CouldBeNy(colHeader))
					{
						//Ny
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_NY);
						assignedNormFlags |= Y_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedNormFlags & Z_BIT) == 0 && CouldBeNz(colHeader))
					{
						//Nz
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_NZ);
						assignedNormFlags |= Z_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedQuaternionFlags & X_BIT) == 0 && CouldBeQuatX(colHeader))
					{
						//Qx
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_QuatX);
						assignedQuaternionFlags |= X_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedQuaternionFlags & Y_BIT) == 0 && CouldBeQuatY(colHeader))
					{
						//Qy
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_QuatY);
						assignedQuaternionFlags |= Y_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedQuaternionFlags & Z_BIT) == 0 && CouldBeQuatZ(colHeader))
					{
						//Qz
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_QuatZ);
						assignedQuaternionFlags |= Z_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if ((assignedQuaternionFlags & W_BIT) == 0 && CouldBeQuatW(colHeader))
					{
						//QW
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_QuatW);
						assignedQuaternionFlags |= W_BIT; //update bit field accordingly
						m_columnType[i] = VALID;
					}
					else if (CouldBeGrey(colHeader))
					{
						//Intensity
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Grey);
						m_columnType[i] = VALID;
					}
					else if (CouldBeRGBi(colHeader))
					{
						//RGBi
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_RGB32i);
						m_columnType[i] = VALID;
					}
					else if (CouldBeRGBf(colHeader))
					{
						//RGBf
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_RGB32f);
						m_columnType[i] = VALID;
					}
					else if (CouldBeScal(colHeader))
					{
						//scalar field
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Scalar);
						m_columnType[i] = VALID;
					}
					else if (!labelColumnAssigned && approximateTotalLineCount < s_maxLabelCount && CouldBeLabel(colHeader)) //no need to promote labels if there are too many of them ;)
					{
						//label
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Label);
						m_columnType[i] = VALID;
						labelColumnAssigned = true;
					}

					columnHeaderWidget->blockSignals(false);
				}
			}
		} //if (validHeader)

		//now for the auto-detection whithout header
		{
			for (unsigned i = 0; i < columnsCount; i++)
			{
				if (m_columnType[i] == TEXT)
				{
					if (!labelColumnAssigned && columnsCount > 1 && approximateTotalLineCount < s_maxLabelCount)
					{
						QComboBox* columnHeaderWidget = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
						assert(columnHeaderWidget);
						columnHeaderWidget->blockSignals(true);
						columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Label);
						columnHeaderWidget->blockSignals(false);

						labelColumnAssigned = true; //There Can Be Only One!
					}
					continue;
				}

				//now we deal with numerical values only
				assert(valueIsNumber[i]);

				//first time? let's try to assign each column a type
				if (m_columnType[i] == UNKNOWN && columnsCount > 1)
				{
					QComboBox* columnHeaderWidget = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
					assert(columnHeaderWidget);
					columnHeaderWidget->blockSignals(true);
					columnHeaderWidget->setCurrentIndex(-1);

					//by default, we assume that the first columns are always X,Y and Z
					if (assignedXYZFlags < XYZ_BITS)
					{
						//in rare cases, the first column is an index
						if (columnsCount > 3
							&& i == 0
							&& (EnabledBits(assignedXYZFlags) == 0)
							&& valueIsInteger[i]
							&& i + 1 < columnsCount
							&& !valueIsInteger[i + 1])
						{
							//let's consider it as a scalar
							columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Scalar);
						}
						else
						{
							if (!(assignedXYZFlags & X_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_X);
								assignedXYZFlags |= X_BIT; //update bit field accordingly
							}
							else if (!(assignedXYZFlags & Y_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Y);
								assignedXYZFlags |= Y_BIT; //update bit field accordingly
							}
							else if (!(assignedXYZFlags & Z_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Z);
								assignedXYZFlags |= Z_BIT; //update bit field accordingly
							}
						}
					}
					else
					{
						//looks like RGB?
						if (valueIsBelow255[i]
							&& assignedRGBFlags < XYZ_BITS
							&& (i + 2 - EnabledBits(assignedRGBFlags)) < columnsCount //make sure that we can put all values there!
							&& (EnabledBits(assignedRGBFlags) > 0 || (valueIsBelow255[i + 1] && valueIsBelow255[i + 2])) //make sure that next values are also ok!
							)
						{
							if (!(assignedRGBFlags & X_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_R);
								assignedRGBFlags |= X_BIT; //update bit field accordingly
							}
							else if (!(assignedRGBFlags & Y_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_G);
								assignedRGBFlags |= Y_BIT; //update bit field accordingly
							}
							else if (!(assignedRGBFlags & Z_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_B);
								assignedRGBFlags |= Z_BIT; //update bit field accordingly
							}
							else if (!(assignedRGBFlags & W_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_A);
								assignedRGBFlags |= W_BIT; //update bit field accordingly
							}
						}
						//looks like a normal vector?
						else if (valueIsBelowOne[i]
							&& assignedNormFlags < XYZ_BITS
							&& (i + 2 - EnabledBits(assignedNormFlags) < columnsCount) //make sure that we can put all values there!
							&& (EnabledBits(assignedNormFlags) > 0 || (valueIsBelowOne[i + 1] && valueIsBelowOne[i + 2])) //make sure that next values are also ok!
							) //make sure that next values are also ok!
						{
							if (!(assignedNormFlags & X_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_NX);
								assignedNormFlags |= X_BIT; //update bit field accordingly
							}
							else if (!(assignedNormFlags & Y_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_NY);
								assignedNormFlags |= Y_BIT; //update bit field accordingly
							}
							else if (!(assignedNormFlags & Z_BIT))
							{
								columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_NZ);
								assignedNormFlags |= Z_BIT; //update bit field accordingly
							}
						}
						else
						{
							//maybe it's a scalar?
							columnHeaderWidget->setCurrentIndex(ASCII_OPEN_DLG_Scalar);
						}
					}

					if (columnHeaderWidget->currentIndex() >= 0)
					{
						m_columnType[i] = VALID;
					}
					else
					{
						//we won't look at this column again
						m_columnType[i] = IGNORED;
					}

					columnHeaderWidget->blockSignals(false);
				}
				else
				{
					//we won't look at this column again
					m_columnType[i] = IGNORED;
				}
			}
		}
	}

	m_columnsCount = columnsCount;

	m_ui->tableWidget->setEnabled(true);
	m_ui->buttonWidget->setEnabled(true);

	//check for invalid columns
	checkSelectedColumnsValidity(); //will eventually enable of disable the "OK" button
	
	m_ui->tableWidget->resizeColumnsToContents();
}

double AsciiOpenDlg::getQuaternionScale() const
{
	return m_ui->quatCSScaleDoubleSpinBox->value();
}

void AsciiOpenDlg::checkSelectedColumnsValidity()
{
	//check for invalid columns
	bool m_selectedInvalidColumns = false;
	{
		assert(m_columnType.size() == static_cast<size_t>(m_columnsCount));
		assert(m_ui->tableWidget->columnCount() >= static_cast<int>(m_columnsCount));
		m_ui->show2DLabelsCheckBox->setEnabled(false);

		unsigned quaternionFields = 0;
		for (unsigned i = 0; i < m_columnsCount; i++)
		{
			QComboBox* columnHeaderWidget = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));

			if (columnHeaderWidget->currentIndex() == ASCII_OPEN_DLG_Label)
			{
				m_ui->show2DLabelsCheckBox->setEnabled(true);
			}
			else if (m_columnType[i] == TEXT && columnHeaderWidget->currentIndex() != 0)
			{
				//text columns shouldn't be selected (other than for Labels)
				m_selectedInvalidColumns |= true;
			}
			else if (columnHeaderWidget->currentIndex() >= ASCII_OPEN_DLG_QuatW && columnHeaderWidget->currentIndex() <= ASCII_OPEN_DLG_QuatZ)
			{
				++quaternionFields;
			}
		}

		// we only show the 'quaternion' frame if all 4 fields are defined
		m_ui->quaternionFrame->setVisible(quaternionFields >= 4);
	}

	m_ui->applyAllButton->setEnabled(!m_selectedInvalidColumns);
	m_ui->applyButton->setEnabled(!m_selectedInvalidColumns);
}

bool AsciiOpenDlg::CheckOpenSequence(const AsciiOpenDlg::Sequence& sequence, QString& errorMessage)
{
	//two requirements:
	//- at least 2 coordinates must be defined
	//- apart from SFs, only one column assignment per property
	std::vector<unsigned> counters(ASCII_OPEN_DLG_TYPES_COUNT, 0);
	{
		for (const SequenceItem& item : sequence)
		{
			++counters[item.type];
		}
	}

	//check for doublons
	{
		for (size_t i = 1; i < ASCII_OPEN_DLG_Scalar; i++)
		{
			if (counters[i] > 1)
			{
				errorMessage = QString("'%1' defined at least twice!").arg(ASCII_OPEN_DLG_TYPES_NAMES[i]);
				return false;
			}
		}
	}

	unsigned char coordIsDefined[3] = {	counters[ASCII_OPEN_DLG_X] != 0,
										counters[ASCII_OPEN_DLG_Y] != 0,
										counters[ASCII_OPEN_DLG_Z] != 0 };

	if (coordIsDefined[0] + coordIsDefined[1] + coordIsDefined[2] < 2)
	{
		errorMessage = "At least 2 vertex coordinates must be defined!";
		return false;
	}

	// for quaternions, we need all 4 values
	unsigned quaternionTotal =		counters[ASCII_OPEN_DLG_QuatW]
								+	counters[ASCII_OPEN_DLG_QuatX]
								+	counters[ASCII_OPEN_DLG_QuatY]
								+	counters[ASCII_OPEN_DLG_QuatZ];
	if (quaternionTotal != 0 && quaternionTotal != 4)
	{
		errorMessage = "Incomplete quaternion definition! (4 components expected)";
		return false;
	}

	return true;
}

bool AsciiOpenDlg::apply()
{
	QString errorMessage;
	auto sequence = getOpenSequence();

	if (!CheckOpenSequence(sequence, errorMessage))
	{
		QMessageBox::warning(nullptr, "Error", errorMessage);
		return false;
	}
	else
	{
		// save semi-persistent values
		s_maxCloudSizeDoubleSpinBoxValue = m_ui->maxCloudSizeDoubleSpinBox->value();
		s_csEntitiesScale = m_ui->quatCSScaleDoubleSpinBox->value();
		
		if (!s_asciiOpenContext)
		{
			s_asciiOpenContext.reset(new AsciiOpenContext);
		}
		s_asciiOpenContext->save(m_ui);
		s_asciiOpenContext->sequence = sequence;
		s_asciiOpenContext->applyAll = false;

		accept();
		
		return true;
	}
}

void AsciiOpenDlg::applyAll()
{
	if (apply())
	{
		//backup current open sequence
		if (s_asciiOpenContext)
		{
			s_asciiOpenContext->applyAll = true;
		}
		else
		{
			assert(false);
		}
	}
}

void AsciiOpenDlg::ResetApplyAll()
{
	if (s_asciiOpenContext)
	{
		s_asciiOpenContext->applyAll = false;
	}
}

bool AsciiOpenDlg::restorePreviousContext()
{
	if (!s_asciiOpenContext)
	{
		return false;
	}

	//restore previous dialog state
	s_asciiOpenContext->load(m_ui);
	m_separator = s_asciiOpenContext->separator;
	setSkippedLines(s_asciiOpenContext->skipLines, true);
	updateTable();

	//saved sequence and cloud content don't match!!!
	if (static_cast<size_t>(m_columnsCount) != s_asciiOpenContext->sequence.size())
	{
		s_asciiOpenContext->applyAll = false; //cancel the 'Apply All' effect
		setSkippedLines(0, true);
		autoFindBestSeparator();
		return false;
	}

	//restore columns attributes
	for (unsigned i = 0; i < m_columnsCount; i++)
	{
		QComboBox* combo = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
		if (!combo) //yes, it happens if all lines are skipped!
		{
			s_asciiOpenContext->applyAll = false; //cancel the 'Apply All' effect
			return false;
		}
		SequenceItem& item = s_asciiOpenContext->sequence[i];
		combo->setCurrentIndex(item.type);
	}

	QString errorMessage;
	if (!CheckOpenSequence(s_asciiOpenContext->sequence, errorMessage))
	{
		s_asciiOpenContext->applyAll = false; //cancel the 'Apply All' effect
	}

	return true;
}

AsciiOpenDlg::Sequence AsciiOpenDlg::getOpenSequence() const
{
	Sequence seq;

	if (m_columnsCount != 0)
	{
		//shall we extract headerParts?
		QStringList headerParts;
		if (!m_headerLine.isEmpty()
			&& m_ui->extractSFNamesFrom1stLineCheckBox->isEnabled()
			&& m_ui->extractSFNamesFrom1stLineCheckBox->isChecked())
		{
			headerParts = m_headerLine.simplified().split(m_separator, QString::SkipEmptyParts);
		}

		seq.reserve(m_columnsCount - 1);
		for (unsigned i = 0; i < m_columnsCount; i++)
		{
			const QComboBox* combo = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
			if (!combo) //yes, it happens if all lines are skipped!
				break;
			seq.emplace_back(static_cast<CC_ASCII_OPEN_DLG_TYPES>(combo->currentIndex()), headerParts.size() > static_cast<int>(i) ? headerParts[i] : QString());
		}
	}

	return seq;
}

bool AsciiOpenDlg::safeSequence() const
{
	//less than 6 columns: we probably have a 3D point (3 columns)
	//and 2 other columns (i.e. scalar fields) --> no ambiguity
	if (getColumnsCount() < 6)
		return true;

	//no header
	if (m_headerLine.isEmpty())
		return false;

	AsciiOpenDlg::Sequence seq = getOpenSequence();
	QStringList headerParts = m_headerLine.simplified().split(m_separator, QString::SkipEmptyParts);

	//not enough column headers?
	if (headerParts.size() < static_cast<int>(seq.size()))
		return false;

	for (int i = 0; i < headerParts.size(); ++i)
	{
		//column header
		QString colHeader = headerParts[i].toUpper();
		//column type
		switch (seq[i].type)
		{
		case ASCII_OPEN_DLG_None:
			break;
		case ASCII_OPEN_DLG_X:
			if (!CouldBeX(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_Y:
			if (!CouldBeY(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_Z:
			if (!CouldBeZ(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_NX:
			if (!CouldBeNx(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_NY:
			if (!CouldBeNy(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_NZ:
			if (!CouldBeNz(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_R:
		case ASCII_OPEN_DLG_Rf:
			if (!CouldBeR(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_G:
		case ASCII_OPEN_DLG_Gf:
			if (!CouldBeG(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_B:
		case ASCII_OPEN_DLG_Bf:
			if (!CouldBeB(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_A:
		case ASCII_OPEN_DLG_Af:
			if (!CouldBeA(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_Grey:
			if (	!CouldBeGrey(colHeader)
				&&	!colHeader.contains("INT"))
				return false;
			break;
		case ASCII_OPEN_DLG_QuatW:
			if (!CouldBeQuatW(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_QuatX:
			if (!CouldBeQuatX(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_QuatY:
			if (!CouldBeQuatY(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_QuatZ:
			if (!CouldBeQuatZ(colHeader))
				return false;
			break;
		case ASCII_OPEN_DLG_Scalar:
			//a SF name can be anything!
			break;
		case ASCII_OPEN_DLG_RGB32i:
			if (	!CouldBeRGBi(colHeader)
				&&	!colHeader.contains("RGB"))
				return false;
			break;
		case ASCII_OPEN_DLG_RGB32f:
			if (	!CouldBeRGBf(colHeader)
				&&	!colHeader.contains("RGB"))
				return false;
			break;
		case ASCII_OPEN_DLG_Label:
			if (!CouldBeLabel(colHeader))
				return false;
			break;
		default:
			//unhandled case?!
			assert(false);
			return false;
		}
	}

	return true;
}

void AsciiOpenDlg::columnsTypeHasChanged(int index)
{
	if (!m_columnsCount)
	{
		return;
	}

	//we get the signal sender
	QObject* obj = sender();
	if (!obj)
	{
		assert(false);
		return;
	}

	//it should be a QComboBox
	QComboBox* changedCombo = qobject_cast<QComboBox*>(obj);
	if (!changedCombo)
	{
		assert(false);
		return;
	}

	//now we look which column's combobox it is
	for (unsigned i = 0; i < m_columnsCount; i++)
	{
		QComboBox* combo = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
		//we found the right element
		if (changedCombo == combo)
		{
			if (index == int(ASCII_OPEN_DLG_X)  ||
				index == int(ASCII_OPEN_DLG_NX) ||
				index == int(ASCII_OPEN_DLG_R)  ||
				index == int(ASCII_OPEN_DLG_Rf) ||
				index == int(ASCII_OPEN_DLG_QuatW)
				)
			{
				//Auto select the next columns type
				if (i + 2 < m_columnsCount)
				{
					QComboBox* nextCombo = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i + 1));
					QComboBox* nextNextCombo = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i + 2));
					//if the two next columns have no assigned type, we set them auto.
					if (	nextCombo->currentIndex() == int(ASCII_OPEN_DLG_None)
						&&	nextNextCombo->currentIndex() == int(ASCII_OPEN_DLG_None))
					{
						nextCombo->blockSignals(true);
						nextNextCombo->blockSignals(true);

						if (index == int(ASCII_OPEN_DLG_X))
						{
							nextCombo->setCurrentIndex(ASCII_OPEN_DLG_Y);
							nextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_Z);
						}
						else if (index == int(ASCII_OPEN_DLG_NX))
						{
							nextCombo->setCurrentIndex(ASCII_OPEN_DLG_NY);
							nextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_NZ);
						}
						else if (index == int(ASCII_OPEN_DLG_R))
						{
							nextCombo->setCurrentIndex(ASCII_OPEN_DLG_G);
							nextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_B);
						}
						else if (index == int(ASCII_OPEN_DLG_Rf))
						{
							nextCombo->setCurrentIndex(ASCII_OPEN_DLG_Gf);
							nextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_Bf);
						}
						else if (index == int(ASCII_OPEN_DLG_QuatW))
						{
							if (i + 3 < m_columnsCount) // we need one more value for quaternions
							{
								QComboBox* nexNextNextCombo = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i + 3));
								if (nexNextNextCombo->currentIndex() == int(ASCII_OPEN_DLG_None))
								{
									nextCombo->setCurrentIndex(ASCII_OPEN_DLG_QuatX);
									nextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_QuatY);
									nexNextNextCombo->setCurrentIndex(ASCII_OPEN_DLG_QuatZ);
								}
							}
						}
					}

					nextCombo->blockSignals(false);
					nextNextCombo->blockSignals(false);
				}
			}
		}
		else if (index< ASCII_OPEN_DLG_Scalar) //check that the other combo as the same index (apart from SF)
		{
			if (combo->currentIndex() == index)
			{
				combo->blockSignals(true);
				combo->setCurrentIndex(ASCII_OPEN_DLG_None);
				combo->blockSignals(false);
			}
		}
	}

	checkSelectedColumnsValidity(); //will eventually enable of disable the "OK" button
}

void AsciiOpenDlg::shortcutButtonPressed()
{
	if (!m_columnsCount)
		return;

	//we get the signal sender
	QObject* obj = sender();
	if (!obj)
		return;

	//it should be a QToolButton (could we test this?)
	QToolButton* shortcutButton = static_cast<QToolButton*>(obj);

	char newSeparator = 0;
	if (shortcutButton == m_ui->toolButtonShortcutSpace)
		newSeparator = 32;
	else if (shortcutButton == m_ui->toolButtonShortcutComma)
		newSeparator = 44;
	else if (shortcutButton == m_ui->toolButtonShortcutSemicolon)
		newSeparator = 59;

	if (newSeparator != 0 && getSeparator() != newSeparator)
	{
		setSeparator(QChar(newSeparator));
	}
}

void AsciiOpenDlg::setSeparator(QChar sep)
{
	m_ui->commaDecimalCheckBox->blockSignals(true);
	if (sep == 44) //comma
	{
		m_ui->commaDecimalCheckBox->setEnabled(false);
		//m_ui->commaDecimalCheckBox->setChecked(false);
	}
	else
	{
		m_ui->commaDecimalCheckBox->setEnabled(true);
	}
	m_ui->commaDecimalCheckBox->blockSignals(false);
	m_ui->lineEditSeparator->setText(sep);
}

unsigned AsciiOpenDlg::getMaxCloudSize() const
{
	return static_cast<unsigned>(m_ui->maxCloudSizeDoubleSpinBox->value() * 1.0e6); //static_cast is equivalent to floor if value >= 0
}

bool AsciiOpenDlg::showLabelsIn2D() const
{
	return m_ui->show2DLabelsCheckBox->isEnabled() && m_ui->show2DLabelsCheckBox->isChecked();
}

void AsciiOpenDlg::resetColumnRoles()
{
	//restore columns attributes
	for (unsigned i = 0; i < m_columnsCount; i++)
	{
		QComboBox* combo = static_cast<QComboBox*>(m_ui->tableWidget->cellWidget(0, i));
		if (combo) //yes, it happens if all lines are skipped!
		{
			combo->setCurrentIndex(0);
		}
	}

	checkSelectedColumnsValidity();
}

void AsciiOpenDlg::setSkippedLines(int lineCount, bool blockSignal/*=true*/)
{
	if (lineCount < 0)
	{
		assert(false);
		lineCount = 0;
	}

	if (lineCount == 0)
	{
		m_headerLine.clear();
		m_ui->extractSFNamesFrom1stLineCheckBox->setEnabled(false);
	}

	m_ui->spinBoxSkipLines->blockSignals(blockSignal);
	m_ui->spinBoxSkipLines->setValue(lineCount);
	m_ui->spinBoxSkipLines->blockSignals(false);
}

unsigned AsciiOpenDlg::getSkippedLinesCount() const
{
	return static_cast<unsigned>(std::max(0, m_ui->spinBoxSkipLines->value()));
}
