/*
 * Copyright (C) 2004-2012  See the AUTHORS file for details.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 */

#include <algorithm>
#include <memory>
#include <znc/Modules.h>
#include <znc/User.h>
#include <znc/IRCNetwork.h>
#include <znc/Chan.h>
#if (VERSION_MAJOR >= 1) && (VERSION_MINOR == 6)
#include <znc/Query.h>
#endif

using std::vector;

#define AWAY_DEFAULT_REASON "Auto away at %time%"

class CClientAwayMod : public CModule {
public:
	CString GetAwayReason() const {
		CString sAway = GetNV("reason");

		if (sAway.empty()) {
			sAway = AWAY_DEFAULT_REASON;
		}

		if (m_pNetwork) {
			return m_pNetwork->ExpandString(sAway);
		}

		return m_pUser->ExpandString(sAway);
	}

	bool GetAutoAway() const {
		return GetNV("autoaway").ToBool();
	}

	void ListCommand(const CString &sLine) {
		CTable Table;
		Table.AddColumn("Host");
		Table.AddColumn("Network");
		Table.AddColumn("Away");

		const vector<CClient*> vClients = m_pUser->GetAllClients();

		for (vector<CClient*>::const_iterator it = vClients.begin(); it != vClients.end(); ++it) {
			CClient *pClient = *it;

			Table.AddRow();
			Table.SetCell("Host", pClient->GetRemoteIP());
			if (pClient->GetNetwork()) {
				Table.SetCell("Network", pClient->GetNetwork()->GetName());
			}
			Table.SetCell("Away", CString(pClient->IsAway()));
		}

		PutModule(Table);
	}

	void SetAwayReasonCommand(const CString &sLine) {
		CString sReason = sLine.Token(1, true);

		if (!sReason.empty()) {
			SetNV("reason", sReason);
			PutModule("Away reason set to [" + sReason + "]");
		}

		PutModule("Away message will be expanded to [" + GetAwayReason() + "]");
	}

	void AutoAwayCommand(const CString &sLine) {
		SetNV("autoaway", sLine.Token(1));

		if (GetAutoAway()) {
			PutModule("Auto away when last client goes away or disconnects enabled.");
		} else {
			PutModule("Auto away when last client goes away or disconnects disabled.");
		}
	}

	void SetAwayCommand(const CString &sLine) {
		const vector<CClient*> vClients = m_pUser->GetAllClients();

		CString sHostname = sLine.Token(1);
		unsigned int count = 0;

		for (vector<CClient*>::const_iterator it = vClients.begin(); it != vClients.end(); ++it) {
			CClient *pClient = *it;

			if (sHostname.empty() || pClient->GetRemoteIP().Equals(sHostname)) {
				pClient->SetAway(true);
				++count;
			}
		}

		if (count == 1) {
			PutModule(CString(count) + " client has been set away");
		} else {
			PutModule(CString(count) + " clients have been set away");
		}
	}

	MODCONSTRUCTOR(CClientAwayMod) {
		AddHelpCommand();
		AddCommand("List", static_cast<CModCommand::ModCmdFunc>(&CClientAwayMod::ListCommand),
			"", "List all clients");
		AddCommand("Reason", static_cast<CModCommand::ModCmdFunc>(&CClientAwayMod::SetAwayReasonCommand),
			"[reason]", "Prints and optionally sets the away reason.");
		AddCommand("AutoAway", static_cast<CModCommand::ModCmdFunc>(&CClientAwayMod::AutoAwayCommand),
			"yes or no", "Should we auto away you when the last client goes away or disconnects");
		AddCommand("SetAway", static_cast<CModCommand::ModCmdFunc>(&CClientAwayMod::SetAwayCommand),
				"hostname", "Set any clients matching hostname as away/unaway");
	}

	virtual void OnClientLogin() {
		if (GetAutoAway() && m_pNetwork && m_pNetwork->IsIRCAway()) {
			PutIRC("AWAY");
		}
	}

	virtual void OnClientDisconnect() {
		if (GetAutoAway() && m_pNetwork && !m_pNetwork->IsIRCAway() && !m_pNetwork->IsUserOnline()) {
			PutIRC("AWAY :" + GetAwayReason());
		}
	}

	virtual void OnIRCConnected() {
		if (GetAutoAway() && !m_pNetwork->IsUserOnline()) {
			PutIRC("AWAY :" + GetAwayReason());
		}
	}

	virtual EModRet OnUserRaw(CString& sLine) {
		CString sCmd = sLine.Token(0);

		if (sCmd.Equals("AWAY")) {
			if (m_pClient->IsAway()) {
				m_pClient->SetAway(false);
				m_pClient->PutClient(":irc.znc.in 305 " + m_pClient->GetNick() + " :You are no longer marked as being away");

				if (m_pNetwork) {
					const vector<CChan*>& vChans = m_pNetwork->GetChans();
					vector<CChan*>::const_iterator it;

					for (it = vChans.begin(); it != vChans.end(); ++it) {
						// Skip channels which are detached or we don't use keepbuffer
						if (!(*it)->IsDetached() && (*it)->AutoClearChanBuffer()) {
							(*it)->ClearBuffer();
						}
					}

#if (VERSION_MAJOR >= 1) && (VERSION_MINOR == 6)
          std::for_each(GetNetwork()->GetQueries().begin(), GetNetwork()->GetQueries().end(), std::default_delete<CQuery>());
#else
					m_pNetwork->ClearQueryBuffer();
#endif

					if (GetAutoAway() && m_pNetwork->IsIRCAway()) {
						PutIRC("AWAY");
					}
				}
			} else {
				m_pClient->SetAway(true);
				m_pClient->PutClient(":irc.znc.in 306 " + m_pClient->GetNick() + " :You have been marked as being away");

				if (GetAutoAway() && m_pNetwork && !m_pNetwork->IsIRCAway() && !m_pNetwork->IsUserOnline()) {
					// Use the supplied reason if there was one
					CString sAwayReason = sLine.Token(1, true).TrimPrefix_n();

					if (sAwayReason.empty()) {
						sAwayReason = GetAwayReason();
					}

					PutIRC("AWAY :" + sAwayReason);
				}
			}

			return HALTCORE;
		}

		return CONTINUE;
	}

	virtual EModRet OnRaw(CString& sLine) {
		// We do the same as ZNC would without the OnRaw hook,
		// except we do not forward 305's or 306's to clients

		CString sCmd = sLine.Token(1);

		if (sCmd.Equals("305")) {
			m_pNetwork->SetIRCAway(false);
			return HALTCORE;
		} else if (sCmd.Equals("306")) {
			m_pNetwork->SetIRCAway(true);
			return HALTCORE;
		}

		return CONTINUE;
	}
};

template<> void TModInfo<CClientAwayMod>(CModInfo& Info) {
	Info.SetWikiPage("clientaway");
	Info.AddType(CModInfo::NetworkModule);
}

USERMODULEDEFS(CClientAwayMod, "This module allows you to set clients away independently, and auto away")