/*
 * This file is part of blabouncer (https://www.blatech.co.uk/l_bratch/blabouncer).
 * Copyright (C) 2019 Luke Bratch <luke@bratch.co.uk>.
 *
 * Blabouncer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * Blabouncer 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with blabouncer. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef FUNCTIONS_H_INCLUDED
#define FUNCTIONS_H_INCLUDED

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <stdarg.h>
#include <limits.h>

#include "sockets.h"
#include "structures.h"
#include "replay.h"
#include "config.h"

// getstdin() return codes
#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2

#define DEBUG_CRIT 0
#define DEBUG_SOME 1
#define DEBUG_FULL 2

#define EXCEPT_NONE 0

#define MAXDATASIZE 513 // Maximum number of bytes we can get at once (RFC2812 says 512, plus one for null terminator)
#define MAXCLIENTS 32 // Maximum number of clients that can connect to the bouncer at a time
#define MAXCHANNELS 1024 // Let's assume 1024 is reasonable for now (it's configured per IRCd)
#define MAXRFCNICKLEN 9 // From RFC 1459

#define MAXTOKENS 100 // For strsep string splitting

#define VERSION "0.2-git" // Blabouncer version

// Write debug string to file.
// Debug level is provided by level, set to one of DEBUG_CRIT, DEBUG_SOME or DEBUG_FULL.
// Debug is only written if the global int "debug" is greater than or equal to the level.
void debugprint(int level, char *format, ...);

// Get stdin line with buffer overrun protection
int getstdin(char *prompt, char *buff, size_t sz);

// Append CR-LF to the end of a string (after cleaning up any existing trailing CR or LF)
void appendcrlf(char *string);

// Remove leading colon ':' which is the starting character of a prefix in an IRC message
// If no leading colon present, string is left unchanged.
// "debug" is used to determine whether stripprefix() should produce all debug output,
// used as debug output with stripprefix() can be particularly noisy.
void stripprefix(char *string, int debug);

// Extract final parameter from IRC message, removing the leading colon ':'
void extractfinalparameter(char *string);

// Extract the IRC nick from a prefix
// e.g. given this string:
// ":foo!bar@baz"
// We want to end up with:
// "foo"
// "debug" is used to determine whether extractnickfromprefix() should produce all debug output,
// used as debug output with extractnickfromprefix() can be particularly noisy.
void extractnickfromprefix(char *string, int debug);

// Update an existing nickuserhost string with a new nick
void updatenickuserhost(char *nickuserhost, char *nick);

// Update an existing 001 greeting with a new nickuserhost
void updategreetings(char *greeting001, char *greeting002, char *greeting003, char *greeting004, char *greeting005a, char *greeting005b, char *greeting005c, char *newnickuserhost, char *oldnickuserhost, char *newnick, char *oldnick);

// Return index of requested client FD within the clients array.
// Returns 0 or more on success, or -1 on failure.
// TODO - Use this wherever we are calculating the position (various places) instead of
// duplicating code.
int arrindex(struct client *clients, int clientfd);

// Send whatever string to a specific client by providing the FD
// If "bypass" == 1 then permit sending to client even if unauthenticated (for instance for a CAP LS response)
int sendtoclient(int fd, char *strsrc, struct client *clients, struct settings *settings, int bypass);

// Relay/send message to all clients (optionally except one)
// "except" is used to send to all clients _except_ the fd provided (except = 0 (EXCEPT_NONE) avoids this, i.e. sends to all)
// "except" is really the "sourcefd" and is also used as part of the authentication check - this is messy and they should perhaps be two separate arguments.
int sendtoallclients(struct client *clients, char *strsrc, int except, struct settings *settings);

// Send whatever string to the real IRC server
// Client FD and arrays needed to make sure anything relayed from a client is from an authenticated client.
// clientfd of "0" means trusted, used when we are sending things ourselves that weren't relayed
// from a real client.
int sendtoserver(SSL *server_ssl, char *strsrc, int str_len, int clientfd, struct client *clients, struct settings *settings);

// Disconnect the client fd "fd" by close()ing it and remove
// it from the array of clients.
// Also set its authentication and registration statuses to 0.
// Also set the pending statuses to 0
int disconnectclient(int fd, struct client *clients, struct ircdstate *ircdstate, struct settings *settings, struct clientcodes *clientcodes);

int createchannel(struct channel *channels, struct ircdstate *ircdstate, char *name, char *topic, char *topicwho, char *topicwhen);

int setchanneltopicwhotime(struct channel *channels, int maxchannelcount, char *channelname, char *who, char *when);

int setchanneltopic(struct channel *channels, int maxchannelcount, char *channelname, char *topic);

int getchannelcount(struct channel *channels, int maxchannelcount);

int removechannel(struct channel *channels, int maxchannelcount, char *name);

// Check if we have the NAMES for the channel 'name' already.
// Return the 1 if we do, 0 if we don't, or -1 if there's an error.
int channelgotnames(struct channel *channels, int maxchannelcount, char *name);

// Check if we are in a channel named "name" or not.
// Return 1 if we are, or 0 if not.
int inchannel(struct channel *channels, int maxchannelcount, char *name);

// Returns the array index in the 'channels' array of the channel
// named 'channel'.
// Returns -1 if there was an error.
int channelindex(struct channel *channels, int maxchannelcount, char *name);

// Send the auto replay to the requested client, where 'sourcefd' is the client
// to send to.  The type of replay will depend on the user's settings.
// Returns 1 for success or 0 for failure.
int doautoreplay(int sourcefd, struct client *clients, struct settings *settings, struct ircdstate *ircdstate, struct channel *channels);

// Return a count of the number of connected clients
int numclients(struct client *clients);

// Join any channels that were configured to be automatically
// joined in the configuration file.
// Returns 1 on success or 0 on failure.
int joinautochannels(SSL *server_ssl, struct client *clients, struct settings *settings);

// Try to make a new nick if no configured are available or liked by the server
// Do this by sticking a number on the end of the current nick and trying numbers
// 1 through to 9.
void tryautonick(struct ircdstate *ircdstate);

// Exit the program cleanly - tell clients, tell the server, then exit(0)
// Optional quit message string "quitmsg"
// "sourcefd" of 0 means the request didn't come from a client
void cleanexit(SSL *server_ssl, struct client *clients, int sourcefd, struct ircdstate *ircdstate, struct settings *settings, char *quitmsg);

// Re-read the configuration file, setting 'failuremsg' to a failure message on failure.
// Returns 1 on success or 0 on failure.
int rehash(struct settings *settings, char *failuremsg);

// Check the password provided in the string 'str' against what is in
// the settings structure 'settings'.
// Return 0 for password mismatch, or 1 for password match.
int checkpassword(char *password, struct settings *settings);

// Adds a client code to the clientcode structure if it doesn't already exist.
// On success, copy it to the client's clientcode field.
// Returns 1 on adding a new code, 0 if the code already existed, or -1 on error.
int addclientcode(int sourcefd, char *code, struct clientcodes *clientcodes, struct client *clients);

// Sets a given client code as last disconnecting at the current time.
// Returns 1 on success or 0 on failure.
int setclientcodetime(char *code, struct clientcodes *clientcodes);

// Return the timestamp that a given client last disconnected, or 0 on failure.
int getclientcodetime(char *code, struct clientcodes *clientcodes);

// Replace any instances of "find" with "replace" in the string "str"
void replacechar(char *str, char find, char replace);

// Add nick (passed as a :nick!user@host) to channel 'channel'
// Returns 1 on success or 0 on failure
int addnicktochannel(char *nickuserhost, char *channel, struct channel *channels, int maxchannelcount);

// Remove nick(passed as a :nick!user@host) from channel 'channel'
// Returns 1 on success or 0 on failure
int removenickfromchannel(char *nickuserhost, char *channel, struct channel *channels, int maxchannelcount);

// Remove nick (passed as a :nick!user@host) from all channels
// Returns 1 on success or 0 on failure
int removenickfromallchannels(char *nickuserhost, struct channel *channels, int maxchannelcount);

// Update old nick (passed as a :nick!user@host) to 'newnick' in all channels
// Returns 1 on success or 0 on failure
int updatenickinallchannels(char *nickuserhost, char *newnick, struct channel *channels, int maxchannelcount);

// Check if "nick" is in a channel or not.
// Return 1 if it is, or 0 if not.
int isnickinanychannel(struct channel *channels, int maxchannelcount, char *nick);

// Populate our channels struct with all nicks in a RPL_NAMREPLY
// Returns 1 on success or 0 on failure
int addnamereplytochannel(char *namereply, struct channel *channels, int maxchannelcount);

// Strips all leading prefixes (colons, user modes) from a nick
void stripprefixesfromnick(char *nick);

// Convert the given 'string' into lowercase
void strlower(char *string);
#endif