/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * main_window.cpp - qcam - Main application window
 */

#include <iomanip>
#include <iostream>
#include <string>

#include <QCoreApplication>
#include <QInputDialog>
#include <QTimer>

#include <libcamera/camera_manager.h>

#include "main_window.h"
#include "viewfinder.h"

using namespace libcamera;

MainWindow::MainWindow(const OptionsParser::Options &options)
	: options_(options), isCapturing_(false)
{
	int ret;

	viewfinder_ = new ViewFinder(this);
	setCentralWidget(viewfinder_);
	viewfinder_->setFixedSize(500, 500);
	adjustSize();

	ret = openCamera();
	if (!ret)
		ret = startCapture();

	if (ret < 0)
		QTimer::singleShot(0, QCoreApplication::instance(),
				   &QCoreApplication::quit);
}

MainWindow::~MainWindow()
{
	if (camera_) {
		stopCapture();
		camera_->release();
		camera_.reset();
	}

	CameraManager::instance()->stop();
}

int MainWindow::openCamera()
{
	CameraManager *cm = CameraManager::instance();
	std::string cameraName;

	if (!options_.isSet(OptCamera)) {
		QStringList cameras;
		bool result;

		for (const std::shared_ptr<Camera> &cam : cm->cameras())
			cameras.append(QString::fromStdString(cam->name()));

		QString name = QInputDialog::getItem(this, "Select Camera",
						     "Camera:", cameras, 0,
						     false, &result);
		if (!result)
			return -EINVAL;

		cameraName = name.toStdString();
	} else {
		cameraName = static_cast<std::string>(options_[OptCamera]);
	}

	camera_ = cm->get(cameraName);
	if (!camera_) {
		std::cout << "Camera " << cameraName << " not found"
			  << std::endl;
		return -ENODEV;
	}

	if (camera_->acquire()) {
		std::cout << "Failed to acquire camera" << std::endl;
		camera_.reset();
		return -EBUSY;
	}

	std::cout << "Using camera " << camera_->name() << std::endl;

	camera_->requestCompleted.connect(this, &MainWindow::requestComplete);

	return 0;
}

int MainWindow::startCapture()
{
	int ret;

	config_ = camera_->streamConfiguration({ Stream::VideoRecording() });
	Stream *stream = config_.begin()->first;
	ret = camera_->configureStreams(config_);
	if (ret < 0) {
		std::cout << "Failed to configure camera" << std::endl;
		return ret;
	}

	const StreamConfiguration &sconf = config_[stream];
	ret = viewfinder_->setFormat(sconf.pixelFormat, sconf.width, sconf.height);
	if (ret < 0) {
		std::cout << "Failed to set viewfinder format" << std::endl;
		return ret;
	}

	adjustSize();

	ret = camera_->allocateBuffers();
	if (ret) {
		std::cerr << "Failed to allocate buffers"
			  << std::endl;
		return ret;
	}

	BufferPool &pool = stream->bufferPool();
	std::vector<Request *> requests;

	for (Buffer &buffer : pool.buffers()) {
		Request *request = camera_->createRequest();
		if (!request) {
			std::cerr << "Can't create request" << std::endl;
			ret = -ENOMEM;
			goto error;
		}

		std::map<Stream *, Buffer *> map;
		map[stream] = &buffer;
		ret = request->setBuffers(map);
		if (ret < 0) {
			std::cerr << "Can't set buffers for request" << std::endl;
			goto error;
		}

		requests.push_back(request);
	}

	ret = camera_->start();
	if (ret) {
		std::cout << "Failed to start capture" << std::endl;
		goto error;
	}

	for (Request *request : requests) {
		ret = camera_->queueRequest(request);
		if (ret < 0) {
			std::cerr << "Can't queue request" << std::endl;
			goto error;
		}
	}

	isCapturing_ = true;
	return 0;

error:
	for (Request *request : requests)
		delete request;

	camera_->freeBuffers();
	return ret;
}

void MainWindow::stopCapture()
{
	if (!isCapturing_)
		return;

	int ret = camera_->stop();
	if (ret)
		std::cout << "Failed to stop capture" << std::endl;

	camera_->freeBuffers();
	isCapturing_ = false;
}

void MainWindow::requestComplete(Request *request,
				 const std::map<Stream *, Buffer *> &buffers)
{
	static uint64_t last = 0;

	if (request->status() == Request::RequestCancelled)
		return;

	Buffer *buffer = buffers.begin()->second;

	double fps = buffer->timestamp() - last;
	fps = last && fps ? 1000000000.0 / fps : 0.0;
	last = buffer->timestamp();

	std::cout << "seq: " << std::setw(6) << std::setfill('0') << buffer->sequence()
		  << " buf: " << buffer->index()
		  << " bytesused: " << buffer->bytesused()
		  << " timestamp: " << buffer->timestamp()
		  << " fps: " << std::fixed << std::setprecision(2) << fps
		  << std::endl;

	display(buffer);

	request = camera_->createRequest();
	if (!request) {
		std::cerr << "Can't create request" << std::endl;
		return;
	}

	request->setBuffers(buffers);
	camera_->queueRequest(request);
}

int MainWindow::display(Buffer *buffer)
{
	if (buffer->planes().size() != 1)
		return -EINVAL;

	Plane &plane = buffer->planes().front();
	unsigned char *raw = static_cast<unsigned char *>(plane.mem());
	viewfinder_->display(raw, buffer->bytesused());

	return 0;
}