###RUN_PERL: #!/usr/bin/perl

# /bsta/coin
# chat.pl is generated from chat.1.pl.
#
# The coincidence interface
#
# Copyright (C) 2016, 2017, 2023  Balthasar Szczepański
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

use strict;
#use warnings;
###PERL_LIB: use lib /botm/lib/bsta
use bsta_lib qw(failpage gethttpheader getcgi entityencode readdatafile writedatafile urlencode);
use  File::Copy;

###PERL_CGI_PATH:           CGI_PATH           = /bsta/
###PERL_CGI_COIN_PATH:      CGI_COIN_PATH      = /bsta/coin
###PERL_CGI_CSS_PATH:       CGI_CSS_PATH       = /bsta/bsta.css
###PERL_CGI_LOGO_PATH:      CGI_LOGO_PATH      = /bsta/botmlogo.png

###PERL_DATA_CHAT_PATH:     DATA_CHAT_PATH     = /botm/data/bsta/chat
###PERL_DATA_COIN_PATH:     DATA_COIN_PATH     = /botm/data/bsta/coincidence

###PERL_WEBSITE:            WEBSITE            = 1190.bicyclesonthemoon.info
###PERL_WEBSITE_NAME:       WEBSITE_NAME       = Bicycles on the Moon
###PERL_FAVICON_PATH:       FAVICON_PATH       = /img/favicon.png

my %http;
my %cgi;
my %coin;
my %chat;

my $time = time();
srand ($time-$$);

my $method;
my $IP;
my $page;
my $words;
my $username;
my $action;
my $password;
my $chatfile;
my $state;
my $passwordOK;
my @chatlines;
my $chatstate;
my $message;
my $chatid;
my $lastid;

delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;

if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
	$method=$1;
}
else{
	exit failpage("Status: 405 Method Not Allowed\nAllow: GET, POST, HEAD\n","405 Method Not Allowed","The interface does not support the $ENV{'REQUEST_METHOD'} method.",$method);
}

%http = gethttpheader (\%ENV);
%cgi = getcgi($ENV{'QUERY_STRING'});

if ($method eq 'POST') {
	if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
		my %cgipost=getcgi( <STDIN> );
		foreach my $ind (keys %cgipost) {
			$cgi{$ind}=$cgipost{$ind};
		}
	}
	# multipart not supported
	else{
		exit failpage("Status: 415 Unsupported Media Type\n","415 Unsupported Media Type","Unsupported Content-type: $http{'content-type'}.");
	}
}

if ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
	$page=int($1);
}
else {
	$page=-1;
}

if ($ENV{'REMOTE_ADDR'} =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
	$IP=$1;
}
else {
	$IP='0.0.0.0';
}

if ($cgi{'words'} ne '') {
	$words=$cgi{'words'};
}
if ($cgi{'username'} ne '') {
	$username=$cgi{'username'};
}
if ($cgi{'join'} ne '') {
	$action=1;
}
elsif ($cgi{'leave'} ne '') {
	$action=2;
}
elsif ($cgi{'nopost'} ne '') {
	$action=3;
}
elsif ($cgi{'file'} ne '') {
	$action=4;
}
else {
	$action=0;
}
if ($cgi{'p'} ne '') {
	$password=$cgi{'p'};
}

%coin=readdatafile(DATA_COIN_PATH);

if($password eq $coin{'password'}){
	$passwordOK = 1;
}
else{
	$passwordOK = 0;
	$username = '';
}

if($page < 0) {
	if (open ($chatfile,"+<",DATA_CHAT_PATH)){
		if (flock($chatfile,2)) {
			%chat=readdatafile($chatfile);
			
			$chatstate=int($chat{'state'});
			$chatid=int($chat{'id'});
			$lastid=$chatid;
			
			if($action==0 && $cgi{'words'} ne '') {
				if($chatstate < 1 && !$passwordOK) {
					$message = 'Not connected.';
				}
				else {
					if ($cgi{'words'} !~ /[\r\n]/) {
						if($username =~ /^[A-Za-z]*$/) {
							$chat{'content'}=$chat{'content'}.$username.': '.$cgi{'words'}."\n";
							if($chatstate < 2) {
								$chat{'state'} = 2;
								$chatstate = 2;
							}
							writedatafile($chatfile,%chat);
						}
						else {
							$message='Invalid username.';
						}
					}
					else {
						$message='Invalid text.';
					}
				}
			}
			elsif ($action==1) {
				if($chatstate > 0 && !$passwordOK) {
					$message = 'Already connected.';
				}
				else {
					if($username =~ /^[A-Za-z]*$/) {
						if ($passwordOK || $cgi{'words'} eq $coin{'server'}) {
							$chat{'content'}=$chat{'content'}.'join@'.$username.': '.$cgi{'words'}."\n";
							if($chatstate < 1) {
								$chat{'state'} = 1;
								$chatstate = 1;
							}
							writedatafile($chatfile,%chat);
						}
						elsif ($cgi{'words'} eq '') {
							$message='Server ID missing.';
						}
						elsif ($cgi{'words'} !~ /^[0-9]+$/) {
							$message='Invalid server ID.';
						}
						else {
							$message='No active Coincidence server with this ID.';
						}
					}
					else {
						$message = 'Invalid username.';
					}
				}
			}
			elsif ($action==2) {
				if($chatstate < 1 && !$passwordOK) {
					$message = 'Already disconnected.';
				}
				else {
					if($username =~ /^[A-Za-z]*$/) {
						$chat{'content'}=$chat{'content'}.'leave@'.$username.': '.$cgi{'words'}."\n";
						if($username ne '') {
							writedatafile($chatfile,%chat);
						}
						elsif ($chatstate > 1) {
							writedatafile(DATA_CHAT_PATH.$chatid,%chat);
							my %newchat;
							$newchat{'id'}=$chatid+1;
							$newchat{'state'}=0;
							$newchat{'content'}='';
							writedatafile($chatfile,%newchat);
						}
						else {
							my %newchat;
							$newchat{'id'}=$chatid;
							$newchat{'state'}=0;
							$newchat{'content'}='';
							writedatafile($chatfile,%newchat);
						}
					}
					else {
						$message = 'Invalid username.';
					}
				}
			}
			elsif($action==4 && $cgi{'file'} ne '' && $cgi{'words'} ne '' &&  $passwordOK) {
				if ($cgi{'words'} !~ /[\r\n]/) {
					if($username =~ /^[A-Za-z]*$/) {
						$chat{'content'}=$chat{'content'}.'file@'.$username.': '.$cgi{'words'}."\n";
						if($chatstate < 2) {
							$chat{'state'} = 2;
							$chatstate = 2;
						}
						writedatafile($chatfile,%chat);
					}
					else {
						$message='Invalid username.';
					}
				}
				else {
					$message='Invalid text.';
				}
			}
			
			@chatlines = split(/\r?\n/,$chat{'content'});
		}
		else{
			$chatstate=0;
			$message='Can\'t lock data file!';
		}
		
		
		close($chatfile);
	}
	else {
		$chatstate=0;
		$message='Can\'t open data file!';
	}
}
else {
	# %chat=readdatafile(DATA_CHAT_PATH);
	# $chatid=int($chat{'id'})-$page;
	%chat=readdatafile(DATA_CHAT_PATH);
	$lastid=int($chat{'id'});
	%chat=readdatafile(DATA_CHAT_PATH.$page);
	$chatid=int($chat{'id'});
	$chatstate=int($chat{'state'});
	@chatlines = split(/\r?\n/,$chat{'content'});
}


# print "Content-type: text/plain\n\n";
# print DATA_CHAT_PATH."\n";
# print 'state: '.$chat{'state'}."\n";
# print 'id: '.$chat{'id'}."\n\n";
# print $chat{'content'};

print "Content-type: text/html\n\n";
if($method eq 'HEAD') {
	exit;
}

print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
print '<html lang="en"><head>'."\n";
print '<title>Coincidence &bull; '.WEBSITE_NAME.'</title>'."\n";
print '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
print '<link rel="icon" type="image/png" href="'.FAVICON_PATH.'">'."\n";
print '<link rel="stylesheet" href="'.CGI_CSS_PATH.'">'."\n";
print '</head><body>'."\n";
print '<a href="/"><img id="botmlogo" src="'.CGI_LOGO_PATH.'" alt="'.WEBSITE.'"></a>'."\n";
print '<div id="all">'."\n";

print '<div id="inst" class="ins">'."\n";

print '<div id="title">'."\n";
print '<H1 id="titletext">Coincidence</H1>'."\n";
print '</div>'."\n";

print '<div id="storypuzzle">'."\n";
if ($page >= 0) {
	print 'Before: '.$chatid."\n";
}
elsif ($chatstate>0){
	print 'Connected to server <span class="br">'.entityencode($coin{'server'}).'</span> as user <span class="ni">'.entityencode(($username ne '')?$username:$coin{'name'}).'</span> (<span class="ni">'.entityencode(abbrname(($username ne '')?$username:$coin{'name'})).'</span>), public key <span class="br">'.entityencode($coin{'key'}).'</span>.'."\n";
}
else{
	print 'Not connected.';
}
print '</div>'."\n";
print '<div id="command">'."\n";
if ($message ne '') {
	print '<span class="br">'.entityencode($message).'</span>'."\n";
}
if ($page < 0) {
	print '<form method="post" action="'.CGI_COIN_PATH.'">'."\n";
	if ($passwordOK) {
		print '<input class="intxc" type="text" name="words">'."\n";
		print '<input class="inbt" type="submit" value="Send">'."\n";
		print "|\n";
		print '<input class="intx" type="text" name="username" value="'.entityencode($username).'">'."\n";
		print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
		print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
		print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
		print '<input class="inbt" type="submit" name="file" value="Send file">'."\n";
		print '<input type="hidden" name="p" value="'.entityencode($coin{'password'}).'">'."\n";
	}
	elsif ($chatstate>0) {
		print '<input class="intxc" type="text" name="words">'."\n";
		print '<input class="inbt" type="submit" value="Send">'."\n";
		print "|\n";
		print '<input class="inbt" type="submit" name="nopost" value="Refresh">'."\n";
		print '<input class="inbt" type="submit" name="leave" value="Disconnect">'."\n";
	}
	else {
		print '<input class="intx" type="text" name="words">'."\n";
		print '<input class="inbt" type="submit" name="join" value="Connect">'."\n";
	}
	print '</form>'."\n";
}
print '</div>'."\n";

print '</div><div id="insb" class="ins">'."\n";

print '<div id="chat">'."\n";
if ($page < 0) {
	for (my $i = @chatlines-1; $i>=0; --$i) {
		print chatline($chatlines[$i])."<br>\n";
	}
}
else {
	for (my $i = 0; $i<@chatlines; ++$i) {
		print chatline($chatlines[$i])."<br>\n";
	}
}
print '</div>'."\n";

print '<div id="underlinks">'."\n";
print '<a href="'.CGI_PATH.'">BSTA</a> | <a href="'.CGI_COIN_PATH.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Once again</a>';
if ($chatid > 0) {
	print ' | <a href="'.CGI_COIN_PATH.'/'.($chatid-1).($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Before</a>';
}
if ($chatid < $lastid) {
	print ' | <a href="'.CGI_COIN_PATH.'/'.(($chatid < $lastid-1)?($chatid +1):'').($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Unbefore</a>';
}
if ($chatid > 0) {
	print ' | <a href="'.CGI_COIN_PATH.'/0'.($passwordOK?('?p='.urlencode($coin{'password'})):'').'">Initially</a>';
}
print ' | (This interface is only a demo, a proof of concept. It is very limited. No autorefresh, no private chat, etc. For full functionality use the actual Coincidence client.)'."\n";
print "\n";
print '</div>'."\n";

print '</div>'."\n";
print '</div>'."\n";
print '<a href="/" class="cz">'.WEBSITE.'</a>'."\n";
print '</body></html>'."\n";



# print DATA_CHAT_PATH."\n";
# print 'state: '.$chat{'state'}."\n";
# print 'id: '.$chat{'id'}."\n\n";
# print $chat{'content'};

sub abbrname {
	(my $name) = @_;
	my $abbr;
	
	if($name !~ /^[A-Za-z]+$/) {
		return '?';
	}
	
	$abbr = uc(substr($name,0,1));
	$name = substr($name,1);
	while($name =~ m/([A-Z])/g) {
		$abbr = $abbr.$1;
	}
	return $abbr;
}

sub chatline {
	(my $line) = @_;
	my $action;
	my $name;
	my $text;
	
	if ($line =~ /^([a-z]*@)?([A-Za-z]*): (.*)$/) {
		$action=$1;
		$name=$2;
		$text=$3;
		
		if($action ne ''){
			if ($action eq 'join@') {
				return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') joined the public chat on server '.entityencode($coin{'server'}).'.';
			}
			elsif ($action eq 'leave@') {
				return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') left the public chat on server '.entityencode($coin{'server'}).'.';
			}
			elsif ($action eq 'file@') {
				return entityencode(($name ne '')?$name:$coin{'name'}).' ('.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).') sent the file '.entityencode($text).'.';
			}
			else {
				return 'E:E:E';
			}
		}
		else {
			return '<span class="'.(($name ne '')?'br':'ni').'">'.entityencode(abbrname(($name ne '')?$name:$coin{'name'})).': '.entityencode($text).'</span>';
		}
	}
	else {
		return 'E:E:E';
	}
}
