#include "Controller.h"

#include "ui/map/3D/MapView3D.h"
#include "ui/map/2D/MapView2D.h"

#include "ui/menu/MainMenu.h"
#include "ui/MainWindow.h"
#include "ui/dialog/LoadSetupDialog.h"
#include "ui/debug/SensorDataWidget.h"

#include <Indoor/grid/factory/v2/GridFactory.h>
#include <Indoor/grid/factory/v2/Importance.h>

#include <Indoor/floorplan/v2/Floorplan.h>
#include <Indoor/floorplan/v2/FloorplanReader.h>
#include <Indoor/floorplan/v2/FloorplanHelper.h>

#include <Indoor/sensors/radio/WiFiGridEstimator.h>

#include <Indoor/Assertions.h>

#include "ui/LoggerUI.h"
#include <Indoor/misc/log/LoggerComposite.h>
#include <Indoor/misc/log/LoggerCOUT.h>
#include <Indoor/misc/log/LoggerAndroid.h>


#include "sensors/dummy/SensorFactoryDummy.h"
#include "sensors/android/SensorFactoryAndroid.h"
#include "sensors/offline/SensorFactoryOffline.h"

#include "tools/calibration/WiFiCalibrationDataModel.h"

#include "nav/NavController.h"

Controller::Controller() : sl(scaler) {

	// OpenGL setup
	// MUST happen before anything gets visible (= gets initialized)
	QSurfaceFormat format;
	format.setDepthBufferSize(16);
	QSurfaceFormat::setDefaultFormat(format);



	// configure the to-be-used sensor factory
	//SensorFactory::set(new SensorFactoryDummy());
	//SensorFactory::set(new SensorFactoryOffline("/apps/android/workspace/YASMIN_DATA/offline/gyroacctestingfrank/nexus6/kleinerKreis_216steps_6runden_telefongerade.csv"));
	//SensorFactory::set(new SensorFactoryOffline("/apps/android/workspace/YASMIN_DATA/offline/gyroacctestingfrank/s3mini/kleinerKreis_225steps_6runden_telefongerade.csv"));
	//SensorFactory::set(new SensorFactoryOffline("/apps/android/workspace/YASMIN_DATA/offline/gyroacctestingfrank/s4/kleinerKreis_220steps_6runden_telefongeneigt.csv"));

	//SensorFactory::set(new SensorFactoryOffline(Settings::Data::getOfflineDir() + "bergwerk/path3/nexus/vor/1454782562231.csv"));
	//SensorFactory::set(new SensorFactoryOffline(Settings::Data::getOfflineDir() + "/bergwerk/path4/nexus/rueck/1454776724285_rueck.csv"));
	//SensorFactory::set(new SensorFactoryOffline(Settings::Data::getOfflineDir() + "/bergwerk/path4/nexus/vor/1454776525797.csv"));

	SensorFactory::set(new SensorFactoryAndroid());
//	SensorFactory::get().getAccelerometer().start();
//	SensorFactory::get().getGyroscope().start();
//	SensorFactory::get().getBarometer().start();
//	SensorFactory::get().getWiFi().start();


	// create the main window
	mainWindow = new MainWindow();

	// attach logger
	LoggerComposite* log = new LoggerComposite();
	log->addLogger(new LoggerCOUT());
	log->addLogger(new LoggerAndroid());
	log->addLogger(new LoggerUI(mainWindow->getInfoWidget()));
	Log::setLogger(log);



	Assert::isTrue(connect(mainWindow->getMainMenu(), &MainMenu::onLoadButton, this, &Controller::onLoadButton), "connect() failed");
	Assert::isTrue(connect(mainWindow->getMainMenu(), &MainMenu::onDebugButton, this, &Controller::onDebugButton), "connect() failed");
	Assert::isTrue(connect(mainWindow->getMainMenu(), &MainMenu::onStartButton, this, &Controller::onStartButton), "connect() failed");
	Assert::isTrue(connect(mainWindow->getMainMenu(), &MainMenu::onTransparentButton, this, &Controller::onTransparentButton), "connect() failed");
	Assert::isTrue(connect(mainWindow->getMainMenu(), &MainMenu::onCameraButton, this, &Controller::onCameraButton), "connect() failed");
	Assert::isTrue(connect(mainWindow->getMainMenu(), &MainMenu::on3DButton, this, &Controller::on3DButton), "connect() failed");

	// order is important! otherwise OpenGL fails!
	mainWindow->show();

//	// start all sensors
//	SensorFactory::get().getAccelerometer().start();
//	SensorFactory::get().getGyroscope().start();
//	SensorFactory::get().getBarometer().start();
//	SensorFactory::get().getWiFi().start();


}

MapView3D* Controller::getMapView3D() const {
	 return mainWindow->getMapView3D();
}

MapView2D* Controller::getMapView2D() const {
	 return mainWindow->getMapView2D();
}


MainMenu* Controller::getMainMenu() const {
	return mainWindow->getMainMenu();
}

InfoWidget* Controller::getInfoWidget() const {
	return mainWindow->getInfoWidget();
}

#include <QMessageBox>
#include <Indoor/sensors/radio/setup/WiFiOptimizer.h>

void buildGridOnce(Grid<MyGridNode>* grid, Floorplan::IndoorMap* map, const std::string& fpFile, const std::string& saveFile) {
	//FloorplanHelper::align(map, grid->getGridSize_cm());

	// ask questions
	const QMessageBox::StandardButton replyWiFiFP = QMessageBox::question(nullptr, "WiFi", "Use Fingerprints for WiFiCalibration?\n\nYes: Use fingerprints and num-optimize AP-Params\nNo: Use APs from the map.xml (pos+params)", QMessageBox::Yes|QMessageBox::No);

	// WiFi setup
	WiFiModelLogDistCeiling wifiModel(map);
	if (replyWiFiFP == QMessageBox::Yes) {
		VAPGrouper vg(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::AVERAGE);
		WiFiCalibrationDataModel mdl(fpFile);
		Assert::isFalse(mdl.getFingerprints().empty(), "no fingerprints available!");
		WiFiOptimizer opt(map, vg);
		for (const WiFiFingerprint& fp : mdl.getFingerprints()) {
			opt.addFingerprint(fp);
		}
		const std::vector<WiFiOptimizer::APParamsMAC> res = opt.optimizeAll();
		WiFiGridEstimator::estimate(*grid, wifiModel, Settings::smartphoneAboveGround);
		for (const WiFiOptimizer::APParamsMAC& ap : res) {
			const WiFiModelLogDistCeiling::APEntry entry(ap.params.getPos(), ap.params.txp, ap.params.exp, ap.params.waf);
			wifiModel.addAP(ap.mac, entry);
		}
	} else {

		// load all APs from the floorplan and use same TXP/EXP/WAF for all of them
		wifiModel.loadAPs(map, Settings::WiFiModel::TXP, Settings::WiFiModel::EXP, Settings::WiFiModel::WAF);
		Assert::isFalse(wifiModel.getAllAPs().empty(), "no AccessPoints stored within the map.xml");

	}

	// build the grid
	GridFactory<MyGridNode> gf(*grid);
	gf.build(map);

	// add node-importance
	Importance::addImportance(*grid);

	// stamp WiFi signal-strengths onto the grid
	WiFiGridEstimator::estimate(*grid, wifiModel, Settings::smartphoneAboveGround);

	// serialize the grid
	std::ofstream out(saveFile, std::ofstream::binary);
	grid->write(out);
	out.close();

}

void Controller::on3DButton() {

	static bool use3D = false;
	use3D = !use3D;

	getMapView2D()->setVisible(!use3D);
	getMapView3D()->setVisible( use3D);

}

void Controller::onLoadButton() {

	// pick a map to load
	QDir dir = LoadSetupDialog::pickSetupFolder();

	// cancelled?
	if (dir.path() == ".") { return; }

	QFile fMap(dir.path() + "/map.xml");
	QFile fGrid(dir.path() + "/grid.dat");
	QFile fWiFiFP(dir.path() + "/wifi_fp.dat");

	Assert::isTrue(fMap.exists(), "map.xml missing");
	//Assert::isTrue(fGrid.exists(), "grid.dat missing");

	fMap.open(QIODevice::ReadOnly);
	QString str = QString(fMap.readAll());
	im = Floorplan::Reader::readFromString(str.toStdString());



	const std::string sGrid = fGrid.fileName().toStdString();
	std::ifstream inp(sGrid, std::ifstream::binary);
	//Assert::isTrue(inp.good(), "failed to open grid.dat");

	const std::string sWiFiFP = fWiFiFP.fileName().toStdString();

	// create a new, empty grid
	if (grid) {delete grid; grid = nullptr;}
	grid = new Grid<MyGridNode>(Settings::Grid::gridSize_cm);

	// grid.dat empty? -> build one and save it
	if (!inp.good() || (inp.peek()&&0) || inp.eof()) {
		buildGridOnce(grid, im, sWiFiFP, sGrid);
	} else {
		grid->read(inp);
	}

	// create a new navigator
	if (nav) {delete nav; nav = nullptr;}
	nav = new NavController(this, grid, im);

	WiFiCalibrationDataModel* wifiCalib = new WiFiCalibrationDataModel(sWiFiFP);

	getMapView3D()->setMap(im);
	getMapView2D()->setMap(wifiCalib, im);

	getMapView3D()->showGridImportance(grid);
	getMapView2D()->showGridImportance(grid);

	// attach ipin step logger
	nav->addListener(&sl);


}

void Controller::onDebugButton() {
	mainWindow->getSensorDataWidget()->setVisible( !mainWindow->getSensorDataWidget()->isVisible() );
}

void Controller::onStartButton() {
	nav->start();
}

void Controller::onTransparentButton() {
	mainWindow->getMapView3D()->toggleRenderMode();
}

void Controller::onCameraButton() {
	nav->toggleCamera();
}
