#pragma once
#include "Core/Object.h"
#include "Core/Array.h"
#include "HandleStream.h"

namespace storm {
	STORM_PKG(core.io);

	class SerialStream;

	/**
	 * Serial parity.
	 */
	namespace parity {
		enum SerialParity {
			none,
			even,
			odd,
		};
	}


	/**
	 * Stop bits.
	 */
	namespace stop {
		enum SerialStopBits {
			one,
			two,
		};
	}


	/**
	 * Options for serial communication.
	 */
	class SerialOptions : public Object {
		STORM_CLASS;
	public:
		// Create default options (9600 baud, no parity, 1 stop bit).
		STORM_CTOR SerialOptions();

		// Create default options with custom baud rate, no parity, 1 stop bit.
		STORM_CTOR SerialOptions(Nat baudrate);

		// Create with all options.
		STORM_CTOR SerialOptions(Nat baudrate, Nat byteSize, parity::SerialParity parity, stop::SerialStopBits stopBits);

		// Baudrate.
		Nat baudrate;

		// Bits per byte.
		Nat byteSize;

		// Parity.
		parity::SerialParity parity;

		// Stop bits.
		stop::SerialStopBits stopBits;

		// To string.
		void STORM_FN toS(StrBuf *to) const;

		// Hash.
		Nat STORM_FN hash() const;

		// Equality.
		Bool STORM_FN operator ==(const SerialOptions &o) const;
	};


	/**
	 * Input stream for a serial port.
	 */
	class SerialIStream : public HandleTimeoutIStream {
		STORM_CLASS;
	public:
		// Not exposed to Storm. Created by the Socket.
		SerialIStream(SerialStream *owner);

		// Destroy.
		virtual ~SerialIStream();

		// Close this stream.
		virtual void STORM_FN close();

	private:
		// Owner.
		SerialStream *owner;
	};


	/**
	 * Output stream for a serial port.
	 */
	class SerialOStream : public HandleTimeoutOStream {
		STORM_CLASS;
	public:
		// Not exposed to Storm. Created by the socket.
		SerialOStream(SerialStream *owner);

		// Destroy.
		virtual ~SerialOStream();

		// Close.
		virtual void STORM_FN close();

	private:
		// Owner.
		SerialStream *owner;
	};


	/**
	 * An open serial port. Use `SerialPort` to create.
	 *
	 * Contains two streams: an input and an output stream.
	 */
	class SerialStream : public Object {
		STORM_CLASS;
	public:
		// Close.
		virtual ~SerialStream();

		// Get the input stream.
		SerialIStream *STORM_FN input() const;

		// Get the output stream.
		SerialOStream *STORM_FN output() const;

		// Close the stream. Also closes the input/output streams.
		void STORM_FN close();

		// Get options.
		SerialOptions *STORM_FN options() const;

		// Set options.
		void STORM_ASSIGN options(SerialOptions *options);

	private:
		friend class SerialPort;
		friend class SerialIStream;
		friend class SerialOStream;

		// Create. Called from SerialPort::open.
		SerialStream(os::Handle handle, const os::Thread &attachedTo, SerialOptions *options, GcArray<char> *lockFile);

		// Handle.
		UNKNOWN(PTR_NOGC) os::Handle handle;

		// Thread.
		UNKNOWN(PTR_NOGC) os::Thread attachedTo;

		// Pointer to the name of the lockfile used (on Linux), if any.
		UNKNOWN(PTR_GC) GcArray<char> *lockFile;

		// Options.
		SerialOptions *opts;

		// Input and output streams.
		SerialIStream *in;
		SerialOStream *out;

		// Closed ends of the stream.
		Nat closed;

		enum {
			closeRead = 0x1,
			closeWrite = 0x2,
		};

		// Close one end of the socket.
		void closeEnd(Nat which);

		// Apply options in 'opts'.
		void applyOptions();

		// Acquire/release any locks on the device file.
		static bool acquireLock(Str *port, os::Handle handle, GcArray<char> *&lockFile);
		void releaseLock();
	};


	/**
	 * A description of a serial port.
	 *
	 * This class essentially encapsulates the name of a serial port to avoid confusing it with
	 * other strings. It provides an 'open' function to open the serial port.
	 */
	class SerialPort : public Object {
		STORM_CLASS;
	public:
		// Create, with a OS-specific identifier.
		STORM_CTOR SerialPort(Str *identifier);

		// Open the serial port. Provide configuration options.
		MAYBE(SerialStream *) STORM_FN open(SerialOptions *options);

		// Get the name of the port.
		Str *STORM_FN name() const {
			return identifier;
		}

		// To string.
		void STORM_FN toS(StrBuf *to) const;

		// Hash.
		Nat STORM_FN hash() const;

		// Equality.
		Bool STORM_FN operator ==(const SerialPort &other) const;

	private:
		// Name of the serial port. OS-specific.
		Str *identifier;
	};


	// Find available serial ports.
	Array<SerialPort *> *STORM_FN serialPorts(EnginePtr e);

}
