/*
 *  ntalk.cpp
 *
 *  This file is part of ntalk chat client distribution
 *  Copyright (C) 2001 by Krzysztof Czuba (kab@interia.pl)
 *
 *  This program 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; either version 2 of the License, or
 *  (at your option) any later version.
 *
 */

#define _REENTRANT	/* required by pthread */

#include <iostream>
#include <fstream>
#include <string> 	// C++ string
#include <algorithm>

#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <pwd.h>
#include <signal.h>
#include <ncurses.h>

#define VERSION "0.6.3"
#define MAX_DATA_SIZE 512

#define MESSAGE 	"01"
#define JOIN 		"02"
#define LEAVE 		"03"
#define DISCONNECT 	"04"
#define SHUTDOWN 	"05"
#define ONLINE 		"06"
#define AWAY 		"07"
#define AWAYOFF 	"08"
#define SERVER 		"09"
#define WHOIS 		"10"
#define SRVMESSAGE 	"11"

using namespace std;


struct color {
    int you;
    int other;
    int nick;
    int bracket;
    int status;
};

struct message {
    string leave;
    string away;
};

WINDOW *input_w, *main_w, *sts_w, *count_w, *user_w;

void *thread(void *);

int sockfd;
int len;
int result;
int tmp;
int already_sigint;
bool away_mode;
string nickname;
string tmpaddr;
string hostname;
string msgs;
string msgr;
sockaddr_in address;
pthread_t my_thread;
char msg_s[MAX_DATA_SIZE];
char msg_r[MAX_DATA_SIZE];
color cl;
message ms;


void update_all()
{
    wnoutrefresh(main_w);
    wnoutrefresh(input_w);
    wnoutrefresh(sts_w);
    wnoutrefresh(count_w);
    wclear(input_w);
    doupdate();
}

void update_main()
{
    wnoutrefresh(main_w);
    doupdate();
    wrefresh(input_w);
}

void gui_init()
{
    initscr();
    main_w = newwin(LINES - 2, COLS - 21, 1, 0);
    sts_w = newwin(1, COLS - 21, 0, 0);
    count_w = newwin(1, 20, 0, COLS - 20);
    input_w = newwin(1, COLS, LINES - 1, 0);
    user_w = newwin(LINES - 2, 20, 1, COLS - 20);

    scrollok(main_w, TRUE);
    scrollok(input_w, FALSE);
    keypad(input_w, TRUE);

    update_all();
}

void color()
{
    if(!has_colors())
    {
	endwin();
	cerr << "ntalk: ncurses: has_colors() failed" << endl;
	cerr << "This terminal doesn't support colors. Try to use different." << endl;
	exit(1);
    }

    if(start_color() != OK)
    {
	endwin();
	cerr << "ntalk: ncurses: start_color() failed" << endl;
	cerr << "Ncurses could not start its color engine. Try to use different terminal." << endl;
	exit(2);
    }

    init_pair(1, COLOR_YELLOW, COLOR_BLACK);
    init_pair(2, COLOR_WHITE, COLOR_BLACK);
    init_pair(3, COLOR_WHITE, COLOR_BLUE);
    init_pair(4, COLOR_GREEN, COLOR_BLACK);
    init_pair(5, COLOR_WHITE, COLOR_RED);
}

void read_config()
{
    ifstream cfile;
    string filename;
    passwd *pwd_entry;
    char buf[255];

    pwd_entry = getpwuid(getuid());
    filename = pwd_entry->pw_dir;
    filename.append("/.ntalkrc");

    cfile.open(filename.c_str());
    if(!cfile)
    {
	cerr << "ntalk: " << filename << ": Could not open configuration file for reading." << endl;
	cerr << "ntalk: Using defaults." << endl;
	return;
    }

    while(!cfile.eof())
    {
	cfile.getline(buf, 255);
	string str = buf;

	if(str[0] == '#') continue;
	else if(str[0] == 'N')
	{
	    str.erase(0, 2);
	    nickname = str;
	}
	else if(str[0] == 'H')
	{
	    str.erase(0, 2);
	    tmpaddr = str;
	}
	else if(str[0] == 'C')
	{
	    str.erase(0, 2);
	    if(str[0] == 'Y')
	    {
		str.erase(0, 2);
		cl.you = atoi(str.c_str());
	    }
	    else if(str[0] == 'O')
	    {
		str.erase(0, 2);
		cl.other = atoi(str.c_str());
	    }
	    else if(str[0] == 'N')
	    {
		str.erase(0, 2);
		cl.nick = atoi(str.c_str());
	    }
	    else if(str[0] == 'B')
	    {
		str.erase(0, 2);
		cl.bracket = atoi(str.c_str());
	    }
	    else if(str[0] == 'S')
	    {
		str.erase(0, 2);
		cl.status = atoi(str.c_str());
	    }
	}
	else if(str[0] == 'M')
	{
	    str.erase(0, 2);
	    if(str[0] == 'L')
	    {
		str.erase(0, 2);
		ms.leave = str;
	    }
	    else if(str[0] == 'A')
	    {
		str.erase(0, 2);
		ms.away = str;
	    }
	}
    }

    cfile.close();
}

void save_config()
{
    fstream cfile, newcfile;
    string filename, newfilename, str;
    passwd *pwd_entry;
    char buf[255];

    pwd_entry = getpwuid(getuid());
    filename = pwd_entry->pw_dir;
    filename.append("/.ntalkrc");
    newfilename = "/tmp/ntalkrc_tmp";

    cfile.open(filename.c_str(), ios::in);
    if(!cfile)
    {
	cerr << "ntalk: "<< filename << ": Could not open configuration file for reading." << endl;
	return;
    }

    newcfile.open(newfilename.c_str(), ios::out);
    if(!newcfile)
    {
	cerr << "ntalk: " << newfilename << ": Could not open file for writing." << endl;
	return;
    }

    while(!cfile.eof())
    {
	strcpy(buf, "");
	cfile.getline(buf, 255);

	switch(buf[0]) {
	    case '#':
		newcfile << buf << endl;
		break;
	    case 'N':
		newcfile << "N=" << nickname << endl;
		break;
	    case 'H':
		newcfile << "H=" << tmpaddr << endl;
		break;
	    case 'C':
		if(buf[2] == 'Y')
		    newcfile << "C:Y=" << cl.you << endl;
		else if(buf[2] == 'O')
		    newcfile << "C:O=" << cl.other << endl;
		else if(buf[2] == 'N')
		    newcfile << "C:N=" << cl.nick << endl;
		else if(buf[2] == 'B')
		    newcfile << "C:B=" << cl.bracket << endl;
		else if(buf[2] == 'S')
		    newcfile << "C:S=" << cl.status << endl;
		break;
	    case 'M':
		if(buf[2] == 'L')
		    newcfile << "M:L=" << ms.leave << endl;
		else if(buf[2] == 'A')
		    newcfile << "M:A=" << ms.away << endl;
		break;
	    default:
		newcfile << buf << endl;
	}
    }

    cfile.close();
    newcfile.close();

    cfile.open(filename.c_str(), ios::out);
    newcfile.open(newfilename.c_str(), ios::in);

    while(!newcfile.eof())
    {
	newcfile.getline(buf, 255);
	cfile << buf << endl;
    }

    newcfile.close();
    cfile.close();
    unlink(newfilename.c_str());
}

string createStr(string com, string data, string to = "all")
{
    string m;
    m = nickname;
    m.append("@");
    m.append(hostname + ":");
    m.append(com + ":");
    m.append(to + ":");
    m.append(data);
    m.append("\n");

    return m;
}

void away_off()
{
    msgs = createStr(AWAYOFF, "I'm back.");
    strcpy(msg_s, msgs.c_str());

    write(sockfd, &msg_s, sizeof(msg_s));
    away_mode = 0;
    nocbreak();
    echo();
    mvwprintw(sts_w, 0, 0, "ntalk - user: %s\n", nickname.c_str());
    wrefresh(sts_w);
}

void refresh_user_w(const string messg)
{
    string msg = messg;
    int a, x;

    wclear(user_w);
    wrefresh(user_w);

    a = count(msg.begin(), msg.end(), ';');
    for(x = 0; x < a; x++)
    {
	msg.replace(msg.find(';'), 1, "\n");
    }

    mvwprintw(user_w, 0, 0, "%s", msg.c_str());
    wrefresh(user_w);
}

void parse_user_command(const char *com)
{
    string _com = com;
    string c = _com.substr(0, _com.find(' '));
    string arg = _com.erase(0, _com.find(' ') + 1);

    wattron(sts_w, COLOR_PAIR(cl.status));

    if(c.compare("/q") == 0)
    {
	msgs = createStr(LEAVE, ms.leave);
	strcpy(msg_s, msgs.c_str());
	write(sockfd, &msg_s, sizeof(msg_s));
	endwin();
	save_config();
	exit(0);
    }
    else if(c.compare("/help") == 0)
    {
	mvwprintw(sts_w, 0, 0, "See README for more information\n");
	wrefresh(sts_w);
	wattron(main_w, COLOR_PAIR(2));
	wprintw(main_w, "-----------------------------------------------\n");
	wprintw(main_w, "Edit your ~/.ntalkrc to change default messages\n");
	wprintw(main_w, "and color schemes.\n");
	wprintw(main_w, "-----------------------------------------------\n");
	wprintw(main_w, "/help         - command list\n");
	wprintw(main_w, "/server       - information about server\n");
	wprintw(main_w, "/clear        - clear main window\n");
	wprintw(main_w, "/sclear       - clear status bar\n");
	wprintw(main_w, "/away         - switch to 'away' mode\n");
	wprintw(main_w, "/online       - refresh users box\n");
	wprintw(main_w, "/whois [nick] - simple information about user\n");
	wprintw(main_w, "/about        - about ntalk\n");
	wprintw(main_w, "/q            - quit ntalk\n");
	wprintw(main_w, "-----------------------------------------------\n");
	wnoutrefresh(main_w);
	doupdate();
    }
    else if(c.compare("/online") == 0)
    {
	msgs = createStr(ONLINE, "");
	strcpy(msg_s, msgs.c_str());
	write(sockfd, &msg_s, sizeof(msg_s));
    }
    else if(c.compare("/away") == 0)
    {
	msgs = createStr(AWAY, ms.away);
	strcpy(msg_s, msgs.c_str());
	write(sockfd, &msg_s, sizeof(msg_s));

	mvwprintw(sts_w, 0, 0, "Press 'x' to exit away mode.\n");
	wrefresh(sts_w);
	away_mode = 1;
	cbreak();
	noecho();
	while(away_mode == 1)
	{
	    tmp = wgetch(input_w);
	    if(tmp == 'x') away_off();
	}
    }
    else if(c.compare("/clear") == 0)
    {
	wclear(main_w);
	update_main();
    }
    else if(c.compare("/sclear") == 0)
    {
	mvwprintw(sts_w, 0, 0, "ntalk %s\n", VERSION);
	wrefresh(sts_w);
    }
    else if(c.compare("/about") == 0)
    {
	mvwprintw(sts_w, 0, 0, "Ncurses Talk, version %s by Krzysztof Czuba <kab@interia.pl>\n", VERSION);
	wrefresh(sts_w);
    }
    else if(c.compare("/server") == 0)
    {
	msgs = createStr(SERVER, "");
	strcpy(msg_s, msgs.c_str());
	write(sockfd, &msg_s, sizeof(msg_s));
    }
    else if(c.compare("/whois") == 0)
    {
	if(c.compare(arg) == 0)
	{
	    mvwprintw(sts_w, 0, 0, "Command usage: /whois [nick]\n");
	    wrefresh(sts_w);
	    return;
	}

	msgs = createStr(WHOIS, arg);
	strcpy(msg_s, msgs.c_str());
	write(sockfd, &msg_s, sizeof(msg_s));
    }
    else
    {
	wattron(sts_w, COLOR_PAIR(cl.status));
	mvwprintw(sts_w, 0, 0, "No such command: %s\n", c.c_str());
	wrefresh(sts_w);
    }
}

void parse_server_command(const string messg)
{
    string msg = messg;
    const string c_msg = messg;
    string nick, addr, com, to, data;

    nick = msg.substr(0, msg.find('@'));
    msg.erase(0, msg.find('@') + 1);
    addr = msg.substr(0, msg.find(':'));
    msg.erase(0, msg.find(':') + 1);
    com = msg.substr(0, msg.find(':'));
    msg.erase(0, msg.find(':') + 1);
    to  = msg.substr(0, msg.find(':'));
    msg.erase(0, msg.find(':') + 1);
    data = msg.substr(0, msg.find('\n'));

    wattron(main_w, COLOR_PAIR(2));

    if(com.compare(MESSAGE) == 0)
    {
	wattron(main_w, COLOR_PAIR(cl.bracket));
	wprintw(main_w, "<");
	wattron(main_w, COLOR_PAIR(cl.nick));
	wprintw(main_w, "%s", nick.c_str());
	wattron(main_w, COLOR_PAIR(cl.bracket));
	wprintw(main_w, ">");
	if(nick.compare(nickname) == 0) wattron(main_w, COLOR_PAIR(cl.you));
	else wattron(main_w, COLOR_PAIR(cl.other));
	wprintw(main_w, " %s\n", data.c_str());
	update_main();
    }
    else if(com.compare(JOIN) == 0)
    {
	//wprintw(main_w, "---> %s@%s has joined the room\n", nick.c_str(), addr.c_str());
	wprintw(main_w, "---> %s joined the room\n", nick.c_str());
	update_main();

	msgs = createStr(ONLINE, "");
	strcpy(msg_s, msgs.c_str());
	write(sockfd, &msg_s, sizeof(msg_s));
    }
    else if(com.compare(LEAVE) == 0)
    {
	wprintw(main_w, "---> %s left the room (%s)\n", nick.c_str(), data.c_str());
	update_main();
    }
    else if(com.compare(SERVER) == 0)
    {
	wprintw(main_w, "---> %s\n", data.c_str());
	update_main();
    }
    else if(com.compare(AWAY) == 0)
    {
	wprintw(main_w, "---> %s is away from computer: %s\n", nick.c_str(), data.c_str());
	update_main();
    }
    else if(com.compare(AWAYOFF) == 0)
    {
	wprintw(main_w, "---> %s is back.\n", nick.c_str());
	update_main();
    }
    else if(com.compare(ONLINE) == 0)
    {
	refresh_user_w(data);
    }
    else if(com.compare(DISCONNECT) == 0)
    {
	endwin();
	cout << "\nDisconnected: " << data << endl;
	exit(0);
    }
    else if(com.compare(WHOIS) == 0)
    {
	wprintw(main_w, "---> %s is: %s@%s (since %s)\n", nick.c_str(), nick.c_str(), addr.c_str(), data.c_str());
	update_main();
    }
    else if(com.compare(SRVMESSAGE) == 0)
    {
	wprintw(main_w, "---> %s\n", data.c_str());
	update_main();
    }
    else if(com.compare(SHUTDOWN) == 0)
    {
	save_config();
	endwin();
	cout << "ntalk: Server shutdown" << endl;
	exit(0);
    }
}

string get_host(string hostname)
{
    char **addr;
    hostent *host_info;

    host_info = gethostbyname(hostname.c_str());
    if(!host_info)
    {
	cerr << "ntalk: gethostbyname() failed" << endl;
	exit(1);
    }

    addr = host_info->h_addr_list;
    string str = inet_ntoa(*(in_addr *)*addr);

    return str;
}

static void signal_interrupt(int sig)
{
    msgs = createStr(LEAVE, "Interrupted...");
    strcpy(msg_s, msgs.c_str());

    if(!already_sigint)
    {
	already_sigint = 1;
	write(sockfd, &msg_s, sizeof(msg_s));
	endwin();
	exit(0);
    }
}

int main(int argc, char *argv[])
{
    int opt;
    passwd *pwd_entry;
    char hostn[256];

    // Some defaults...

    tmpaddr = "127.0.0.1";
    pwd_entry = getpwuid(getuid());
    nickname = pwd_entry->pw_name;

    cl.you 	= 1;
    cl.other 	= 2;
    cl.nick 	= 2;
    cl.bracket 	= 4;
    cl.status   = 3;

    ms.leave = "Leaving...";
    ms.away = "Busy...";

    read_config();

    while((opt = getopt(argc, argv, "n:h:v?")) != -1)
    {
	if(opt == 'h' && optarg != "") tmpaddr = optarg;
	else if(opt == 'n' && optarg != "")
	{
	    if((strlen(optarg) > 21) || (strlen(optarg) < 2))
	    {
		cout << "Error: Your nick must contain 2 - 20 charackters\n";
		exit(0);
	    }
	    nickname = optarg;
	}
	else if(opt == 'v')
	{
	    cout << "version " << VERSION << " (ncurses 5.2)\n(C) Krzysztof Czuba <kab@interia.pl>\n";
	    exit(0);
	}
	else if(opt == '?')
	{
	    cout << "\nUsage: client [option]\n\nOptions:\n"
                   "-h\t\tspecify server address\n"
		   "-n\t\tspecify nickname\n"
                   "-v\t\tshow version information\n"
		   "-?\t\tdisplay this information\n\n";
	    exit(0);
	}
	else exit(0);
    }

    if(gethostname(hostn, 255) == -1)
    {
	cerr << "ntalk: gethostname() failed" << endl;
	cerr << "Ntalk could not obtain your computer's name." << endl;
	exit(0);
    }

    hostname = hostn;

    tmpaddr = get_host(tmpaddr);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(tmpaddr.c_str());
    address.sin_port = htons(9734);
    len = sizeof(address);
    result = connect(sockfd, (sockaddr *)&address, len);

    if(result == -1)
    {
	endwin();
	cerr << "ntalk: connect() failed" << endl;
	cerr << "ntalk: Make sure that the address is valid and the server is running" << endl;
	exit(1);
    }

    gui_init();
    color();

    wattron(sts_w, COLOR_PAIR(cl.status));
    wattron(count_w, COLOR_PAIR(3));
    mvwprintw(sts_w, 0, 0, "ntalk %s - user: %s | type /help for command list\n", VERSION, nickname.c_str());
    mvwprintw(count_w, 0, 0, "      Online:      \n");
    wrefresh(sts_w);
    wrefresh(count_w);

    msgs = createStr(JOIN, "");
    strcpy(msg_s, msgs.c_str());
    write(sockfd, &msg_s, sizeof(msg_s));

    already_sigint = 0;
    signal(SIGINT, signal_interrupt);

    if(pthread_create(&my_thread, NULL, thread, (void *)"watek") != 0)
    {
	cerr << "ntalk: pthread_create() failed" << endl;
	cerr << "Could not create thread." << endl;
	exit(1);
    }

    while(1)
    {
	char input[MAX_DATA_SIZE];
	wgetnstr(input_w, input, MAX_DATA_SIZE);
	input[MAX_DATA_SIZE - 1] = '\0';
	if(input[0] == '/')
	{
	    parse_user_command(input);
	    wprintw(input_w, "\n");
	    continue;
	}

	msgs = createStr(MESSAGE, string(input));
	strcpy(msg_s, msgs.c_str());

	write(sockfd, &msg_s, sizeof(msg_s));
	wprintw(input_w, "\n");
    }

    close(sockfd);
    exit(0);
}

void *thread(void *watek)
{
    while(1)
    {
	read(sockfd, &msg_r, sizeof(msg_r));
	string msg = msg_r;
	parse_server_command(msg);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1