//! \~\file pigrabberbase.h
//! \~\ingroup Thread
//! \~\brief
//! \~english Grabber thread base class
//! \~russian Базовый класс потока-граббера
/*
    PIP - Platform Independent Primitives
	Grabber thread base class
    Andrey Bychkov work.a.b@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef PIGRABBERBASE_H
#define PIGRABBERBASE_H

#include "pidiagnostics.h"
#include "pithread.h"
#include "pitime.h"

//! \~\ingroup Thread
//! \~\brief
//! \~english Base polling grabber thread with a pending queue, recording virtual methods, and a last-item snapshot.
//! \~russian Базовый поток-граббер с очередью ожидающих элементов, виртуальными методами записи и снимком последнего элемента.
//! \~\details
//! \~english
//! Captured items are appended to the internal queue and also copied into \a last(). The snapshot is independent from the queue.
//! \~russian
//! Захваченные элементы добавляются во внутреннюю очередь и также копируются в \a last(). Этот снимок не зависит от очереди.
template<typename T = PIByteArray>
class PIGrabberBase: public PIThread {
	PIOBJECT_SUBCLASS(PIGrabberBase, PIThread);

public:
	//! \~english Constructs a closed non-recording grabber.
	//! \~russian Создает закрытый граббер без записи.
	PIGrabberBase() {
		is_opened    = false;
		is_recording = false;
	}

	//! \~english Stops the grabber thread and releases recording/open state.
	//! \~russian Останавливает поток-граббер и освобождает состояние записи и открытия.
	virtual ~PIGrabberBase() { stopGrabber(false); }


	//! \~english Returns whether the grabber is currently opened.
	//! \~russian Возвращает, открыт ли сейчас граббер.
	virtual bool isOpened() const { return is_opened; }

	//! \~english Returns whether captured items are currently being recorded.
	//! \~russian Возвращает, записываются ли сейчас захваченные элементы.
	virtual bool isRecording() const { return is_recording; }

	//! \~english Starts recording subsequent captured items if the grabber is opened and recording is not active yet.
	//! \~russian Запускает запись последующих захваченных элементов, если граббер открыт и запись еще не активна.
	virtual void startRecord(const PIString & filename) {
		if (!isOpened()) return;
		if (isRecording()) return;
		rec_mutex.lock();
		startRecordInternal(filename);
		is_recording = true;
		rec_mutex.unlock();
	}

	//! \~english Stops recording if it is active.
	//! \~russian Останавливает запись, если она активна.
	virtual void stopRecord() {
		if (!isOpened()) return;
		if (!isRecording()) return;
		rec_mutex.lock();
		is_recording = false;
		stopRecordInternal();
		rec_mutex.unlock();
	}

	//! \~english Returns a copy of the last successfully captured item. This snapshot is kept separately from the pending queue and is not
	//! consumed by \a dequeue().
	//! \~russian Возвращает копию последнего успешно захваченного элемента. Этот снимок хранится отдельно от очереди ожидания и не
	//! извлекается через \a dequeue().
	T last() const {
		T ret;
		last_mutex.lock();
		ret = last_;
		last_mutex.unlock();
		return ret;
	}

	//! \~english Returns whether the pending queue is empty.
	//! \~russian Возвращает, пуста ли очередь ожидающих элементов.
	bool isEmpty() {
		bool ret;
		que_mutex.lock();
		ret = que.isEmpty();
		que_mutex.unlock();
		return ret;
	}

	//! \~english Returns the current number of pending captured items.
	//! \~russian Возвращает текущее количество ожидающих захваченных элементов.
	int queSize() {
		int ret;
		que_mutex.lock();
		ret = que.size();
		que_mutex.unlock();
		return ret;
	}

	//! \~english Dequeues and returns the oldest pending captured item. If the queue is empty, returns a default-constructed value.
	//! \~russian Извлекает и возвращает самый старый ожидающий захваченный элемент. Если очередь пуста, возвращает значение, созданное
	//! конструктором по умолчанию.
	T dequeue() {
		T ret;
		//		piCoutObj << "start";
		que_mutex.lock();
		if (!que.isEmpty()) {
			//			piCoutObj << "dequeue";
			ret = que.dequeue();
		}
		//		piCoutObj << "end";
		que_mutex.unlock();
		return ret;
	}

	//! \~english Stops the grabber thread. With \a wait_forever equal to \c false the method waits briefly and may terminate the thread
	//! forcibly.
	//! \~russian Останавливает поток-граббер. При \a wait_forever равном \c false метод ждет недолго и может принудительно завершить поток.
	void stopGrabber(bool wait_forever = true) {
		if (isRunning()) {
			stop();
			if (wait_forever)
				waitForFinish();
			else {
				if (!waitForFinish(100)) terminate();
				stopRecord();
				close();
			}
		}
	}

	//! \~english Opens the grabber immediately. The thread loop also tries to open it automatically when running in a closed state.
	//! \~russian Немедленно открывает граббер. Цикл потока также пытается открыть его автоматически, если поток работает в закрытом
	//! состоянии.
	bool open() {
		bool ret = openInternal();
		if (!is_opened && ret) opened();
		is_opened = ret;
		return ret;
	}

	//! \~english Closes the grabber and resets the last-item snapshot.
	//! \~russian Закрывает граббер и сбрасывает снимок последнего элемента.
	void close() {
		bool em = is_opened;
		closeInternal();
		last_ = T();
		if (em) closed();
		is_opened = false;
	}

	//! \~english Returns diagnostics collected for captured and recorded items.
	//! \~russian Возвращает диагностику для захваченных и записанных элементов.
	const PIDiagnostics & diag() const { return diag_; }

	//! \~english Clears only the pending queue. The value returned by \a last() is not changed.
	//! \~russian Очищает только очередь ожидающих элементов. Значение, возвращаемое \a last(), не изменяется.
	void clear() {
		que_mutex.lock();
		que.clear();
		que_mutex.unlock();
	}

	//! \~english Clears the pending queue and closes the grabber.
	//! \~russian Очищает очередь ожидания и закрывает граббер.
	void restart() {
		clear();
		close();
	}

	//! \events
	//! \{

	//! \fn void dataReady()
	//! \brief
	//! \~english Raised after a new item has been captured, queued, and copied into \a last().
	//! \~russian Вызывается после захвата нового элемента, его добавления в очередь и копирования в \a last().
	EVENT(dataReady);

	//! \fn void opened()
	//! \brief
	//! \~english Raised when \a open() switches the grabber into the opened state.
	//! \~russian Вызывается, когда \a open() переводит граббер в открытое состояние.
	EVENT(opened);

	//! \fn void closed()
	//! \brief
	//! \~english Raised when \a close() closes a previously opened grabber.
	//! \~russian Вызывается, когда \a close() закрывает ранее открытый граббер.
	EVENT(closed);

	//! \}

protected:
	//! \~english Virtual method executed once when the thread starts, before polling begins.
	//! \~russian Виртуальный метод, выполняемый один раз при старте потока до начала опроса.
	virtual void init() {}

	//! \~english Opens the underlying grabber resource.
	//! \~russian Открывает базовый ресурс граббера.
	virtual bool openInternal()  = 0;

	//! \~english Closes the underlying grabber resource.
	//! \~russian Закрывает базовый ресурс граббера.
	virtual void closeInternal() = 0;

	//! \~english Polls the next item into \a val. Return \c 0 when a new item was captured, a positive value when no item is ready yet, and
	//! a negative value on failure that should close the grabber.
	//! \~russian Опрашивает следующий элемент в \a val. Возвращает \c 0, когда новый элемент захвачен, положительное значение, когда
	//! элемент еще не готов, и отрицательное значение при ошибке, после которой граббер должен закрыться.
	virtual int get(T & val)     = 0;

	//! \~english Records a captured item when recording mode is active.
	//! \~russian Записывает захваченный элемент, когда активен режим записи.
	virtual void record(const T & val) {}

	//! \~english Virtual method called before the recording flag becomes active.
	//! \~russian Виртуальный метод, вызываемый перед активацией флага записи.
	virtual void startRecordInternal(const PIString & filename) {}

	//! \~english Virtual method called after the recording flag is cleared.
	//! \~russian Виртуальный метод, вызываемый после сброса флага записи.
	virtual void stopRecordInternal() {}

	bool is_opened, is_recording;
	mutable PIMutex rec_mutex;

private:
	void begin() override { init(); }
	void run() override {
		if (!isOpened()) {
			open();
			diag_.reset();
			if (!is_opened) piMSleep(200);
		}
		if (isOpened()) {
			T c;
			int ret = get(c);
			if (ret < 0) {
				close();
				return;
			}
			if (ret > 0) {
				piMinSleep();
				return;
			}
			diag_.received(1);
			que_mutex.lock();
			que.enqueue(c);
			que_mutex.unlock();
			if (isRecording()) {
				rec_mutex.lock();
				record(c);
				rec_mutex.unlock();
				diag_.sended(1);
			}
			last_mutex.lock();
			last_ = c;
			last_mutex.unlock();
			dataReady();
		}
	}

	void end() override {
		stopRecord();
		close();
	}

	T last_;
	PIQueue<T> que;
	PIDiagnostics diag_;
	mutable PIMutex que_mutex, last_mutex;
};

#endif // PIGRABBERBASE_H
