#!/usr/bin/env perl

use 5.016;

use strict;
use warnings;

### dependencies :
### libssl-dev
### LWP::Protocol::https

use File::Path qw(make_path rmtree);
use File::Spec;
use File::Spec::Functions qw(catdir catfile tmpdir);
use LWP::UserAgent ();
use URI		();
use HTTP::Date ();
use JSON::MaybeXS qw(decode_json encode_json);
#use LWP::Simple;
use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
use File::Copy;
use Term::ANSIColor qw(:constants);


our $VERSION = '00.98.13';
our $execname = "kateb";
our $shown;  # have we called the show() function yet

#----------------------- COLORS -----------------------#
our %c =
(
	byellow => (BOLD YELLOW),
	bpurple => (BOLD MAGENTA),
	bblue   => (BOLD BLUE),
	bgreen  => (BOLD GREEN),
	bold    => (BOLD),
	bred    => (BOLD RED),
	bcyan   => (BOLD CYAN),
	reset   => (RESET),
);


#----------------------- GLOBAL VARIABLES -----------------------#
# crate empty envs
my $json_file;

# Home directory
my $home_dir = $ENV{HOME} || $ENV{LOGDIR} || (getpwuid $<)[7] || `echo -n ~`;

# Configuration directory
my $config_dir = catdir($ENV{XDG_CONFIG_HOME} || catdir($home_dir, '.config'), $execname);

# download fonts directory
my $cache_font_dir = catdir($config_dir, 'fonts');

# user font directory [target directory]
my $root_font_dir = catdir('/', 'usr', 'share', 'fonts', 'truetype', 'farsifreefont');
my $user_font_dir = catdir($home_dir, '.local', 'share', 'fonts', 'farsifreefont');
my $target_dir = $> == 0 ? $root_font_dir : $user_font_dir;


# temp dir for downloads
my $temp_dir = File::Temp->newdir();
$temp_dir = catdir($temp_dir, 'kateb');
make_path $temp_dir;

# Configuration file
# my $config_file = catfile($config_dir, "$execname.conf");

# json Database file
my $json_data_file = catfile($config_dir, "$execname.json");

# check for exist config dir
if (not -d $config_dir)
{
	make_path($config_dir)
	  or note(":: Unable to create directory <<$config_dir>>: {!$!!}");
}

# check for exist taget directory
if (not -d $target_dir)
{
	make_path($target_dir)
	  or note(":: Unable to create directory <<$target_dir>>: {!$!!}");
}

# check json data base file
my $installed_versions;

if (not -e $json_data_file)
{
	_reset_json_file($json_data_file);
	$json_file = _read_file($json_data_file);
	$installed_versions = decode_json($json_file);
} else
{
	$json_file = _read_file($json_data_file);
	eval
	{
		$installed_versions = decode_json($json_file)
	}; if ($@)
	{
		_reset_json_file($json_data_file);
		$json_file = _read_file($json_file);
		$installed_versions = decode_json($json_file);
	}
}

#----------------------- WORK AREA -----------------------#
# github apis
my %apis =
(
	'vazir-font'      => 'https://api.github.com/repos/rastikerdar/vazir-font/tags',
	'samim-font'      => 'https://api.github.com/repos/rastikerdar/samim-font/tags',
	'tanha-font'      => 'https://api.github.com/repos/rastikerdar/tanha-font/tags',
	'shabnam-font'    => 'https://api.github.com/repos/rastikerdar/shabnam-font/tags',
	'gandom-font'     => 'https://api.github.com/repos/rastikerdar/gandom-font/tags',
	'parastoo-font'   => 'https://api.github.com/repos/rastikerdar/parastoo-font/tags',
	'sahel-font'      => 'https://api.github.com/repos/rastikerdar/sahel-font/tags',
	'vazir-code-font' => 'https://api.github.com/repos/rastikerdar/vazir-code-font/tags',
	'nahid-font'      => 'https://api.github.com/repos/rastikerdar/nahid-font/tags'
);

# rastikerdar github repos
my $rastikerdar_github_user_address = "https://github.com/rastikerdar";

# font base
my $online_available_fonts = {};

# start user agent
my $user_agent = LWP::UserAgent->new
(
	ssl_opts => { verify_hostname => 1 },
	keep_alive => 1
);

# read api and store: 1-font version, 2-download .zip url in
# $online_available_fonts->{fontname} : {version} / {url}
foreach my $font_name (keys %apis)
{
	##########################################################
	#say "check github for $font_name";
	##########################################################

	my $json_api_response = $user_agent->get($apis{$font_name});
	my $tags = decode_json( $json_api_response->{_content} );
	my $version;
	eval
	{
		$version = $tags->[0]->{name};
	}; if ($@)
	{
		say "$c{bred}github API rate limit exceeded. This limit is 50 times per hour, plz try again in about an hour$c{reset}";
		exit;
	}

	my $url =
		$rastikerdar_github_user_address . "/" .
		$font_name .
		"/releases/download/" .
		$version . "/" .
		$font_name . "-" . $version .
		".zip"
	;

	$online_available_fonts->{$font_name}  =
	{
		'version' => $version,
		'url' => $url
	};
}



# download if new version available
foreach my $key (keys %{$online_available_fonts})
{
	if ($installed_versions->{$key} ne $online_available_fonts->{$key}->{version})
	{
		##########################################################
		say "$c{bred}$key, $c{reset}new version available: $c{byellow}$online_available_fonts->{$key}->{version}$c{reset}";
		#say "...downloading $online_available_fonts->{$key}->{url}";
		##########################################################
		#say "...downloading " . $online_available_fonts->{$key}->{url};

		my $archive_file = catfile($temp_dir, "$key-$online_available_fonts->{$key}->{version}.zip");
		_download( $online_available_fonts->{$key}->{url}, $archive_file );
#		getstore( $online_available_fonts->{$key}->{url}, $archive_file);
		$installed_versions->{$key} = $online_available_fonts->{$key}->{version};

		my $zip = Archive::Zip->new();
		unless ( $zip->read( $archive_file ) == AZ_OK )
		{
			die 'read error';
		}

		# unzip fonts
		foreach my $file (grep { m"^((?!/).)*\.ttf$"g } $zip->memberNames())
		{
			$zip->extractMember( $file, catfile($cache_font_dir, $file) );
		}

		# unzip parastoo-font
		if ($key eq 'parastoo-font')
		{
			foreach my $file (grep { m"(web/((?!/).)*\.ttf$)|(print/((?!/).)*\.ttf$)"g } $zip->memberNames())
			{
				my ($volume, $directories, $file_name) = File::Spec->splitpath( $file );
				$zip->extractMember( $file, catfile($cache_font_dir, $file_name) );
			}
		}
	} else
	{
		##########################################################
		say "$c{bgreen}$key is up date, version: $installed_versions->{$key}$c{reset}";
		##########################################################
	}
}

# copy fonts
my $glob = catfile($cache_font_dir, "*.ttf");
my @downloaded_fonts = glob $glob;
foreach my $font (@downloaded_fonts)
{
	my ($volume, $directories, $file) = File::Spec->splitpath( $font );
	my $target = catfile($target_dir, $file);
	##########################################################
#	say "...copy $file to $target_dir";
	##########################################################

	copy($font, $target);
}

##########################################################
##########################################################

# update json Data File
my $json = encode_json( $installed_versions );
_write_file($json_data_file, $json);

# finish




#####################
##### Functions #####
#####################

sub _read_file {
	my $entry = shift;
	open my $fh, "<:encoding(UTF-8)", $entry or die $!;
	local $/ = undef;
	my $content = <$fh>;
	close $fh;
	return $content;
}

sub _write_file {
	my $file = shift;
	my $json = shift;
	open my $fh, '>:encoding(UTF-8)', $file || die;
	print $fh $json;
	close $fh;
}

sub _reset_json_file {
	my $file = shift;
	my %hash = (
		'vazir-font'      => '',
		'samim-font'      => '',
		'tanha-font'      => '',
		'shabnam-font'    => '',
		'gandom-font'     => '',
		'parastoo-font'   => '',
		'sahel-font'      => '',
		'vazir-code-font' => '',
		'nahid-font'      => ''
	);
	my $json = encode_json( \%hash );
	_write_file($file, $json);
}


###################################
############ Download #############
###################################

sub _download {
	my $url	 = shift;
	my $argfile = shift;

	$url = URI->new($url);

	my $ua = LWP::UserAgent->new(
		agent	  => "kateb::$VERSION ",
		keep_alive => 1,
		env_proxy  => 1,
	);

	my $file;	   # name of file we download into
	my $length;	 # total number of bytes to download
	my $flength;	# formatted length
	my $size = 0;   # number of bytes received
	my $start_t;	# start time of download
	my $last_dur;   # time of last callback
	my $FILE_HANDLE;

	$shown = 0;  # have we called the show() function yet

	$SIG{INT} = sub { die "Interrupted\n"; };

	$| = 1;		 # autoflush

	my $res = $ua->request(
		HTTP::Request->new(GET => $url),
		sub {
			unless (defined $file) {
				my $res = $_[1];

				$file = $argfile;

				print "Saving to '$file'...\n";
				use Fcntl qw(O_WRONLY O_EXCL O_CREAT);
				sysopen($FILE_HANDLE, $file, O_WRONLY | O_EXCL | O_CREAT)
					|| die "Can't open $file: $!";

				unless (fileno($FILE_HANDLE)) {
					open($FILE_HANDLE, ">", $file) || die "Can't open $file: $!\n";
				}
				binmode $FILE_HANDLE;
				$length   = $res->content_length;
				$flength  = fbytes($length) if defined $length;
				$start_t  = time;
				$last_dur = 0;
			}

			print $FILE_HANDLE $_[0] or die "Can't write to $file: $!\n";
			$size += length($_[0]);

			if (defined $length) {
				my $dur = time - $start_t;
				if ($dur != $last_dur) {	# don't update too often
					$last_dur = $dur;
					my $perc = $size / $length;
					my $speed;
					$speed = fbytes($size / $dur) . "/sec" if $dur > 3;
					my $secs_left = fduration($dur / $perc - $dur);
					$perc = int($perc * 100);
					my $show = "$perc% of $flength";
					$show .= " (at $speed, $secs_left remaining)" if $speed;
					show($show, 1);
				}
			}
			else {
				show(fbytes($size) . " received");
			}
		}
	);

	if (fileno($FILE_HANDLE)) {
		close($FILE_HANDLE) || die "Can't write to $file: $!\n";

		show("");	# clear text
		print "\r";
		print fbytes($size);
		print " of ", fbytes($length) if defined($length) && $length != $size;
		print " received";
		my $dur = time - $start_t;
		if ($dur) {
			my $speed = fbytes($size / $dur) . "/sec";
			print " in ", fduration($dur), " ($speed)";
		}
		print "\n";

		if (my $mtime = $res->last_modified) {
			utime time, $mtime, $file;
		}

		if ($res->header("X-Died") || !$res->is_success) {
			if (my $died = $res->header("X-Died")) {
				print "$died\n";
			}
			if (-t) {
				print "Transfer aborted.  Delete $file? [n] ";
				my $ans = <STDIN>;
				if (defined($ans) && $ans =~ /^y\n/) {
					unlink($file) && print "Deleted.\n";
				}
				elsif ($length > $size) {
					print "Truncated file kept: ", fbytes($length - $size),
						" missing\n";
				}
				else {
					print "File kept.\n";
				}
				exit 1;
			}
			else {
				print "Transfer aborted, $file kept\n";
			}
		}
	}

	# Did not manage to create any file
#	print "\n" if $shown;
	if (my $xdied = $res->header("X-Died")) {
		print "$execname: Aborted\n$xdied\n";
	}
	else {
		print $c{bblue}, $url, " downloaded$c{reset}\n";
	}
}

sub fbytes {
	my $n = int(shift);
	if ($n >= 1024 * 1024) {
		return sprintf "%.3g MB", $n / (1024.0 * 1024);
	}
	elsif ($n >= 1024) {
		return sprintf "%.3g KB", $n / 1024.0;
	}
	else {
		return "$n bytes";
	}
}

sub fduration {
	use integer;
	my $secs = int(shift);
	my $hours = $secs / (60 * 60);
	$secs -= $hours * 60 * 60;
	my $mins = $secs / 60;
	$secs %= 60;
	if ($hours) {
		return "$hours hours $mins minutes";
	}
	elsif ($mins >= 2) {
		return "$mins minutes";
	}
	else {
		$secs += $mins * 60;
		return "$secs seconds";
	}
}


BEGIN {
	my @ani = qw(- \ | /);
	my $ani = 0;

	sub show {
		my ($mess, $show_ani) = @_;
		print "\r$mess" . (" " x (75 - length $mess));
		my $msg = $show_ani ? $ani[$ani++]. "\b" : ' ';
		print $msg;
		$ani %= @ani;
		$shown++;
	}
}
