#ifndef DICT_H
#define DICT_H

#include <QPair>
#include <QList>
#include <QStringList>
#include <QTimer>
#include <QByteArray>
#include <QTcpSocket>


namespace Dict {

typedef QPair<QString, QString> Pair;
typedef QList<Pair> List;

struct Request {
	enum RequestCommand {
		None, Define, Match,
		Databases, Strategies,
		Info, Server, Option, Client,
		Auth, Status, Help, Quit
	};
	Request(RequestCommand req, const QString &cmd)
		: type(req), command(cmd) {}
	RequestCommand type;
	QString command;
};

class Response : public QObject {
	Q_OBJECT

public:
	enum ResponseCode {
		DatabaseList = 110,
		StrategyList = 111,
		DatabaseInfo = 112,
		HelpMessage = 113,
		ServerInfo = 114,
		Definitions = 150,
		Words = 151,
		Matches = 152,

		StatusMessage = 210,
		Hello = 220,
		Bye = 221,
		AuthOK = 230,
		OK = 250,

		ServerUnavailable = 420,
		ServerShutdown = 421,

		UnrecognizedCommand = 500,
		IllegalParameter = 501,
		UnimplementedCommand = 502,
		UnimplementedParameter = 503,
		AccessDenied = 530,
		AuthDenied = 531,
		UnknownMechanism = 532,
		InvalidDatabase = 550,
		InvalidStrategy = 551,
		NoMatch = 552,
		NoDatabase = 554,
		NoStrategy = 555,
	};
	ResponseCode type;
	QString message, text;

	Response(ResponseCode code) : type(code) {}

	virtual void parse(const QString &) {}
	virtual void append(const QString &line) { text += line + '\n'; }
	virtual void end() {}
	virtual bool done() const { return true; }

Q_SIGNALS:
	void processed(const Response &);
};

class ListResponse : public Response {
public:
	ListResponse(ResponseCode code) : Response(code) {}
	List list;

	void append(const QString &line) override;
};

class MatchResponse : public Response {
public:
	MatchResponse(ResponseCode code) : Response(code), isDone(false) {}
	QString database;
	QStringList list;
	bool isDone;

	void append(const QString &line) override;
	bool done() const override { return isDone; }
};

class DefineResponse : public MatchResponse {
public:
	DefineResponse(ResponseCode code) : MatchResponse(code) {}
	QString description;

	void parse(const QString &line) override;
	void append(const QString &line) override { text += line + '\n'; }
	void end() override { list << text; }
};

class MimeResponse : public DefineResponse {
public:
	MimeResponse(ResponseCode code) : DefineResponse(code) {}
	QStringList header;
	bool isHeader;

	void parse(const QString &line) override;
	void append(const QString &line) override;
	void end() override;
	void decode();
};

typedef QList<const Request *> Queue;

class Connection : public QObject {
	Q_OBJECT

public:
	static const QString DefaultHost;
	static const int DefaultPort;
	static const QString DefaultDatabase;
	static const QString DefaultStrategy;
	static const QString AllDatabases;

	Connection(const QString &host = DefaultHost, int port = DefaultPort);
	void setHost(const QString &hostname, int port = DefaultPort);
	void setUser(const QString &username, const QString &password);
	void setClient(const QString &client);
	enum Option { Mime = 1, Distance };
	void setOption(Option opt, const QVariant &val);

	void setTimeouts(int connect = 20, int read = 10, int idle = 300);
	void setKeepConnect(bool on) { keepconnect = on; }
	void setKeepAlive(bool on) { keepalive = on; };

	QString host() const { return dict.host; }
	int port() const { return dict.port; }
	QString client() const { return dict.client; }
	QString user() const { return dict.user; }
	QString ident() const { return dict.ident; }
	QStringList capabilities() const { return dict.capabilities; }

	enum NetworkError {
		UnknownError,
		HostNotFound = 600,
		ConnectionRefused,
		ConnectTimeout,
		ReadTimeout,
	};

public Q_SLOTS:
	void define(const QString &query, const QString &database = DefaultDatabase);
	void match(const QString &query, const QString &database = DefaultDatabase, const QString &strategy = DefaultStrategy);
	void info(const QString &query);
	void auth(const QString &username, const QString &password);
	void status();
	void quit();
	void cancel();

private Q_SLOTS:
	void read();
	void socketError(QAbstractSocket::SocketError);
	void socketState(QAbstractSocket::SocketState);
	void connectTimeout();
	void readTimeout();

Q_SIGNALS:
	void responseReceived(const Response &);
	void responseProcessed(const Response &);
	void responseText(const QString &);
	void connected(bool, const QString &);
	void authenticated(bool, const QString &);
	void identified(bool, const QStringList &);
	void statusMessage(const QString &);
	void errorMessage(const QString &, int code = 0);

private:
	enum State {
		Disconnected,
		HostLookup,
		Connecting,
		Connected,
		Writing,
		Reading,
		Parsing,
		Appending,
		Canceled,
	};
	QTcpSocket *socket;

	struct Info {
		QString host;
		int port;
		QString client;
		QString user, password;
		QString ident, msgid;
		QStringList capabilities;
		bool mime;
		int distance;
		void clear();
	} dict;

	QTimer connectTimer, readTimer, idleTimer;

	State state;
	Response *response;
	Queue sendqueue, recvqueue;
	QByteArray extra;
	bool interauth;
	bool keepconnect, keepalive;

	void setupSocket();
	void setupTimers();
	void reset();
	void abort();
	void clear();

	void open();
	void auth();
	void client();
	void options();
	void serverIdent(const QString &text);

	void send(const Request *request);
	void read(const QString &text);
	void parse(const QString &text);
	void done();
	void status(const QString &);
	void error(const QString &, int code = 0);

	Response *newResponse(Response::ResponseCode code);
	void delResponse();
	void okResponse();
	void helloResponse();
};

}

#endif
