#!/usr/bin/perl -wT

# CWZ.pm
# A Perl module for common CWZ functionality

package CWZ;
require Exporter;

use strict;
use DBI;
use CGI::Fast qw( :standard );
use HTML::Template;
use JSON;
use Locale::Currency::Format;
use List::Util qw( min max );
no warnings 'uninitialized';

our @ISA	= qw( Exporter );
our @EXPORT	= qw( wAuth wSync wPage wConsoleLog wAuditLog qDb_do qDb_hr qDb_ar qDb_sc qDb_i wFmt wFmtInt unFmt pgError
						%Conf %Sess $SquadRef $ToonRef $CryptoRef $LocRef $DBH);
our @EXPORT_OK	= qw( $DBH );
our $VERSION	= 1.00;

our ($DBH, %Conf, %Sess, $SquadRef, $ToonRef, $CryptoRef, $LocRef);

BEGIN {
   # Connect to DB
   $DBH = DBI->connect("DBI:mysql:CWZ_main", "cwcryptowz", "CherRyCwz1!2!", {'RaiseError' => 1, AutoCommit => 1});
   $DBH->{'mysql_auto_reconnect'} = 1;

   # Secure down CGI
   $CGI::POST_MAX = 64 * 1024;
   $CGI::DISABLE_UPLOADS = 1;

   # Reroute errors
   #$SIG{__DIE__} = sub { pgError("DIE", "@_"); exit; };

   # Location of HTML::Template files
   $Conf{HTML_TEMPLATE_ROOT} = '/var/www/live/cwzgame/tmpl';

   # Some defaults
   $Conf{MAX_TURNS} = 5000;
   $Conf{MAX_OUTS} = 12;
   $Conf{MAX_NEWB} = 5000;
   $Conf{MAX_HODLER} = 4000;
   $Conf{MAX_WHALE} = 3000;
   $Conf{MAX_SHILL} = 10000;
   $Conf{MAX_SHILL_ALT} = 15000;
   $Conf{MAX_WHALE_4ALT} = 1000;
   $Conf{SHILL_COST} = 250;
   $Conf{SHILL_EAT} = 50;
   $Conf{MAX_YOUTUBER} = 2000;
   $Conf{MAX_REDDITOR} = 2000;
   $Conf{MAX_ANON} = 1000;
   $Conf{MAX_KAREN} = 2000;
   $Conf{MAX_REPORTER} = 2000;
   $Conf{MAX_POLITICIAN} = 1000;
	 $Conf{INVESTOR_EARN_PWR} = 0.004;
   $Conf{INFLUENCER_EARN_PWR} = 0.0065;
   $Conf{DOUBTER_EARN_PWR} = 0.0085;
	 $Conf{MAX_NODE} = 500;
   $Conf{MAX_FAC} = 250;
	 $Conf{MAX_JETS} = 100;
	 $Conf{NODE_COST} = 50000;
   $Conf{FAC_COST} = 100000;
	 $Conf{AIR_COST} = 1000000;
   $Conf{MAX_BANK} = 5000000;
	 $Conf{NODE_PAYOUT} = 100;

   # Load the Crypto table once now
   $CryptoRef = $DBH->selectall_arrayref("SELECT cd, longnm, price_now FROM Crypto_Game UNION SELECT cd, longnm, price_now FROM Crypto_Live", { Slice => {} }, @_);

    $Conf{CACHE} = 1;

   # And Locations
   $LocRef = $DBH->selectall_arrayref("SELECT * FROM Location WHERE id > 0 ORDER BY id", { Slice => {} }, @_);
}

# Authentication and Session Variable functions
sub wAuth($) {
	my ($sess_hash) = @_;

	# Do we have a Session already?
	my $sesstxt = qDb_hr("SELECT a.sess FROM Account a, Session s WHERE a.id = s.login_id AND sess_hash = ?", $sess_hash);
	unless ( $sesstxt ) { pgError("Authentication Failed", "Please Login", "/"); return 0; }

	# Unpack the Session variables into %Sess
	%Sess = ( );
	foreach ( split (',', $sesstxt->{sess}) ) {
		my ($key, $val) = split ':';
		$Sess{$key} = $val;
	}

	# Trap for Character creation
	# Idk if I can do this here without creating special exceptions for fm_create...
	#unless ( exists $Sess{created} || param('fm') eq 'fm_create' )
	#	{ print redirect( -location => '/cgi/login.cgi?pg=create' ); return 0; }

	my $roundInfo = qDb_hr("SELECT *, UNIX_TIMESTAMP(start_date) AS start, UNIX_TIMESTAMP(end_date) AS end FROM Round WHERE start_date <= CURDATE() ORDER BY start_date DESC LIMIT 1");
	my $timestamp = time();

	my $requestedPage = $ENV{REQUEST_URI};
		my @allowedPages = ('/cgi/round-complete.cgi', '/cgi/hall-of-fame.cgi', '/cgi/password-reset.cgi', '/cgi/unsubscribe.cgi', '/cgi/login.cgi', '/cgi/verify.cgi', '/cgi/reset.cgi', '/');

	if (($timestamp <= $roundInfo->{start} || $timestamp >= $roundInfo->{end}) && !grep { $requestedPage eq $_ } @allowedPages) {
			# User cannot access the pages
			print redirect(-location => '/cgi/round-complete.cgi');
			return 0;
	} else {
			# User can access the pages
	}

	# Trap for EOR
	# ..make sure round_end.pl UPDATEs all Account.sess to add "EOR=1"
	# ..and round_reset.pl (or round_start.pl) erases all Account.sess
	#if (exists $Sess{EOR} && $ENV{REQUEST_URI} !~ m{/cgi/(round-complete|hall-of-fame|login)\.cgi$}) {
	#  print redirect(-location => '/cgi/round-complete.cgi');
	#  return 0;
	#}

	return 1;
	}

sub wSync() {
	# Marshall %Sess hash back into the DB
	my $sess_txt = "";
	$sess_txt .= "$_:$Sess{$_}," foreach ( keys %Sess );
	$sess_txt =~ s/,+$//;												# chomp last comma

	qDb_do("UPDATE Account SET sess = ? WHERE id = ?", $sess_txt, $Sess{login_id});
}

sub wConsoleLog($;$$$$) {
	my ($msg, $chan, $aid, $sid, $pid) = @_;
	$chan = 4 if !defined($chan);
	$aid = $Sess{aid} if !defined($aid);
	$sid = $Sess{sid} if !defined($sid);
	$pid = $Sess{pid} if !defined($pid);

	qDb_do("INSERT INTO Console VALUES(null, NOW(), ?, ?, ?, ?, ?)", $chan, $aid, $sid, $pid, $msg);
	return 1;
}

sub wAuditLog($;$$$) {
	# Special audit log message for sus activity
	my ($msg, $aid, $sid, $pid) = @_;
	$aid = $Sess{aid} if !defined($aid);
	$sid = $Sess{sid} if !defined($sid);
	$pid = $Sess{pid} if !defined($pid);

	qDb_do("INSERT INTO Console VALUES(null, NOW(), 5, ?, ?, ?, ?)", $aid, $sid, $pid, $msg);
}

# Page output routines
sub wPage($$) {
	my ($tmpl, $pg) = @_;

	# Fetch the Console/Chat stuff to put in quickChat
	my $con = qDb_ar("SELECT * FROM (
								(SELECT 1 AS tid, id, ts AS rt, DATE_FORMAT(ts, '%h:%i %p') AS ts, msg FROM Console
										WHERE (chan = 0 OR chan = 1 OR (chan = 2 AND aid = $Sess{aid})	OR (chan = 3 AND sid = $Sess{sid}) OR (chan = 4 AND pid = $Sess{pid}))
										ORDER BY id DESC LIMIT 18)
								UNION ALL
								 (SELECT 2 AS tid, id, ts AS rt, DATE_FORMAT(ts, '%h:%i %p') AS ts, msg FROM Chat
								 		WHERE (chan = 0 OR chan = 1 OR (chan = 2 AND aid = $Sess{aid})	OR (chan = 3 AND sid = $Sess{sid}) OR (chan = 4 AND pid = $Sess{pid}))
								 		ORDER BY id DESC LIMIT 6)
								) sub ORDER BY rt, id");

	# Some ugly trickery to retrieve both table last ids
	foreach ( @$con ) {
		$Sess{con_lastmsg} = $_->{id} if $_->{tid} == 1;
		$Sess{chat_lastmsg} = $_->{id} if $_->{tid} == 2;
	}

	# Output console to template
	$tmpl->param( console => $con );

	# The BIG one for loading player/squad data:
	$SquadRef = qDb_ar(
		q{SELECT	p.pid AS pid,
			p.sid AS sid,
			s.squadnm AS squadnm,
			p.slot AS slot,
			p.charnm AS charnm,
			p.chartag AS chartag,
			p.loc AS loc,
			l.shortnm AS loc_shortnm,
			l.longnm AS loc_longnm,
			p.glob_rank AS glob_rank,
			p.loc_rank AS loc_rank,
			p.turns AS turns,
			p.turns_faucet AS turns_faucet,
			p.outs AS outs,
			p.ins AS ins,
			p.market AS market,
			p.cash AS cash,
			FORMAT(p.market, 2) AS market_fmt,
			FORMAT(p.cash, 2) AS cash_fmt,
			p.bank AS bank,
			FORMAT(p.bank, 2) AS bank_fmt,
			p.market + p.cash AS value,
			ABBV(p.market + p.cash) AS value_abbv,
			FORMAT((p.market + p.cash), 2) AS value_fmt,
			p.f_newb AS f_newb,
			p.f_hodler AS f_hodler,
			p.f_whale AS f_whale,
			p.f_shill AS f_shill,
			p.f_youtuber AS f_youtuber,
			p.f_redditor AS f_redditor,
			p.f_anon AS f_anon,
			p.f_karen AS f_karen,
			p.f_reporter AS f_reporter,
			p.f_politician AS f_politician,
			p.it_by AS it_by,
			p.it_exp AS it_exp,
			p.a51_by AS a51_by,
			p.fud_by AS fud_by,
			p.fud_exp AS fud_exp,
			p.bog_by AS bog_by,
			p.bog_exp AS bog_exp,
			p.flight_exp AS flight_exp,
			p.shill_exp AS shill_exp,
			p.payouts AS payouts
		FROM Player p
		JOIN Location l ON l.id = p.loc
		LEFT JOIN Squad s ON s.sid = p.sid
		WHERE login_id = ?; }, $Sess{login_id});


		# Find our current slot
		$ToonRef = findToon($Sess{slot});
		$Sess{sid} = $ToonRef->{sid} ? $ToonRef->{sid} : 0;
		$Sess{squadnm} = $ToonRef->{squadnm};
		$Sess{charnm} = $ToonRef->{charnm};

		$ToonRef->{avatar} = qDb_sc("SELECT Avatar FROM Account WHERE id = ?", $Sess{login_id});
		# Check if the result is null or empty, and assign a placeholder if needed
		if (!defined $ToonRef->{avatar} || $ToonRef->{avatar} eq '') {
		    $ToonRef->{avatar} = 'placeholder'; # Set the placeholder value
		}
		# Top Switcher
		# ..mostly uses SquadRef (through TMPL_VAR squad tab), but do we show unlock more?
		$ToonRef->{activeslot} = 1;
		$tmpl->param(unlockmore => 1) if $Sess{slots} < 25;

		# Fill the Player Info Section
		#my ($squadln, $charln);
		#$squadln = "[<a href='#'>$Sess{squadnm}</a>] " if $Sess{squadnm};
		#$charln = "<a href='#'>$Sess{charnm}</a>";

		$tmpl->param(#pi_title => $squadln . $charln,
					    squadnm => $ToonRef->{squadnm},
						 charnm => $ToonRef->{charnm},
						 glob_rank => $ToonRef->{glob_rank},
						 loc_rank => $ToonRef->{loc_rank},
						 turns => $ToonRef->{turns},
						 turns_faucet => $ToonRef->{turns_faucet},
						 market_fmt => wFmt($ToonRef->{market}),
						 cash_fmt => wFmt($ToonRef->{cash}),
						 cash => $ToonRef->{cash},
						 value_fmt => wFmt($ToonRef->{value}),
						 ins => $ToonRef->{ins},
						 outs => $ToonRef->{outs},
						 bank => wFmt($ToonRef->{bank}),
						 avatar => $ToonRef->{avatar},
				 );

		# Fill the Character List Section
      $tmpl->param(squadtab => $SquadRef);

      # Fill sidebar crypto select list
      $tmpl->param(crypto_dd => $CryptoRef);

		$Sess{pg} = $pg;
		wSync();
}

# Database Query routines
# Query, $DBH->do() method
sub qDb_do(@) {
	my $sql = shift @_;

	return ( $DBH->do($sql, undef, @_) );
}

# Query, return an arrayref to multiple rows
sub qDb_ar(@) {
	my $sql = shift @_;

	my $rowref = $DBH->selectall_arrayref($sql, { Slice => {} }, @_);

	return ( @$rowref ? $rowref : 0 );
}

# Query, return a collapsed row of data into one scalar (useful for single columns)
sub qDb_sc(@) {
	my $sql = shift @_;

	my $rowref = $DBH->selectall_arrayref($sql, undef, @_);

	return ( @$rowref ? join(' ', @{ $rowref->[0] }) : undef );
}

# Query, return a hashref to single row
sub qDb_hr(@) {
	my $sql = shift @_;

	my $rowref = $DBH->selectall_arrayref($sql, { Slice => {} }, @_);

	return ( ref $rowref eq 'ARRAY' ? $rowref->[0] : 0 );
}

# Query, return number of rows affected.
# ..good for true/false queries
sub qDb_i(@) {
	my $sql = shift @_;

	my $rows = $DBH->do($sql, undef, @_);

	return ( ($rows > 0) ? $rows : 0 );
}

# Output an error page
sub pgError($$;$$) {
	my ($title, $content, $link, $critical) = @_;

	print header;
	print start_html("Crypto-Warz - $title");
	print "<h1>$title</h1>";
	print "<p>$content";
	print "<p><a href='$link'>Back</a>" if $link;
	print end_html;

	# Since we can't skip to the bottom of the CGI::Fast loop from here
	# ..we will experiment with how exiting the FastCGI program works
	exit if $critical;
}

# Find us (slot) in the SquadRef and return a single hashref
sub findToon($) {
	my ( $find ) = @_;
	my $mytoon;

	foreach ( @$SquadRef ) {
		$mytoon = $_;

		last if $mytoon->{slot} == $find;
	}

	return $mytoon;
}

# Currency formatting
sub wFmt($) {
	my $dol = shift;
	return $dol >= 0 ? currency_format('usd', $dol, FMT_SYMBOL) : ('-' . currency_format('usd', abs($dol), FMT_SYMBOL));
}

sub wFmtInt($) {
	return substr(wFmt(shift), 0, 3);
}

sub unFmt($) {
	my ( $num ) = @_;
	$num =~ tr/\$|,//d;
	return $num;
}

END {
   $DBH->disconnect;
}


1;

__END__
