#!/usr/bin/perl
###################################################################################################
# Copyright 2013/2014 by Marcel Greter
# This file is part of OCBNET-CSS3 (GPL3)
####################################################################################################

use strict;
use warnings;

# load modules
use File::Slurp;
use OCBNET::CSS3;

# parse options
use Pod::Usage;
use Getopt::Long;
use Cwd qw(realpath);
use File::Basename;

# init options
my $force = 0;
my $cbuster = 1;
my $imports = 1;
my $cleanup = 1;
my $compress = 0;

# define a sub to print out the version (mimic behaviour of node.js blessc)
sub version { print "blessc $OCBNET::CSS3::VERSION (CSS Post-Processor) [Perl]"; exit 0; };

# get options
GetOptions (
	'force|f!' => \ $force,
	"imports!" => \$imports,
	"cleanup!" => \$cleanup,
	'compress|x!' => \$compress,
	"cache-buster!" => \$cbuster,
	'help|h' => sub { pod2usage(1); },
	'version|v' => \ &version,
);

# counters
my @imports;
my @selectors;

# create the actual cache buster
$cbuster = $cbuster ? '?z=' . time : '';

# set the output path
# will be reduced to path
my $outpath = $ARGV[1] || $ARGV[0];

# some minimalistic error handling
die "blessc: no input file\n" unless $ARGV[0];
die "blessc: no output file\n" unless $ARGV[1];

# check if the output file can probably be written to
die "blessc: output directory does not exist\n" unless -d dirname($ARGV[1]);
die "blessc: output directory is not writable\n" unless -w dirname($ARGV[1]);

# check if we are overwriting the same file without force
if (-f $ARGV[0] && realpath($ARGV[0]) eq realpath($outpath))
{ die "blessc: use --force or -f to modify input file\n" unless $force; }

# read the passed filename (or read from standard input)
my $code = $ARGV[0] ne '-' ? read_file($ARGV[0]) : join("", <>);

# parse for the file extension (to insert number)
my $outext = $outpath =~ s/(\.[a-z]+)\z//i ? $1 : '';
my $outfile = $outpath =~ s/([^\/\\]+)\z//i ? $1 : '';

# create a new instance and parse the loaded code
my $sheet = OCBNET::CSS3::Stylesheet->new->parse($code);

# runs recursive
sub blessc
{

	# get passed arguments
	my ($node, $parent, $state) = @_;

	# create the status array
	# add more stylesheets as needed
	$state = [ $node ] unless $state;

	# process each child in current node
	foreach my $child (@{$node->{'children'}})
	{
		# special handling for selectors
		if ($child->type eq 'selector')
		{

			# get the additional selectors to add
			my @additional = split(/,/, $child->text);

			# check if we would exceed the ie limit if processing
			if (scalar(@selectors) + scalar(@additional) > 4095)
			{
				# extend everything to a new stylesheet
				my $extend = OCBNET::CSS3::Stylesheet->new;
				# define variables to clone dom tree
				my @tree; my $cur = $child->parent;
				# process tree
				while($cur)
				{
					# insert clone into tree array
					unshift(@tree, $cur->clone);
					# move up in dom tree
					$cur = $cur->parent;
				}
				# add tree to new sheet
				my $prev = $extend;
				# process previous tree
				foreach my $tree (@tree)
				{
					# connect new nodes
					$prev->add($tree);
					# move in dom tree
					$prev = $tree;
				}
				# reset counter
				@selectors = ();
				# remember new sheet
				push(@{$state}, $extend);
				# reset pointer
				$parent = $prev;
			}
			# EO exceed limit

			# count additional selectors
			push @selectors, @additional;

		}
		# EO is selector

		# clone the current child
		my $clone = $child->clone;
		# call blessc recursively
		blessc($child, $clone, $state);
		# add clone to parent
		$parent->add($clone);

	}
	# EO each children

	# return all sheets
	return @{$state};

}
# EO sub blessc

# clone the top node
my $clone = $sheet->clone;

# start blessc on top node
# returns a list of all sheets
my @sheets = reverse blessc($sheet, $clone);

# process resulting sheets in reverse
for (my $i = $#sheets; $i != -1; $i --)
{

	# create the generated filename for this stylesheet
	my $file = $i == 0 ? join("", $outpath, $outfile, $outext)
		: join("", $outpath, $outfile, sprintf('.%02d', $i), $outext);

	# write this stylesheet to the disk as is
	write_file( $file, { binmode => ':raw' }, $sheets[$i]->render ) ;

	# give a status message
	printf "written %s\n", $file;

	# stop here for last sheet
	next if $i == 0 || ! $imports;

	# create a new import statement
	my $import = OCBNET::CSS3::DOM::Extended::Import->new;
	# set the import url to be inserted in previous sheet
	$import->set(sprintf('@import url("%s")', $file . $cbuster));
	# set suffix delimiter (maybe save newline)
	$import->suffix = $compress ? ';' : ";\n";

	# prepend import to previous sheet
	$sheets[$i-1]->prepend($import);

}
# EO each sheet

# process resulting sheets in reverse
my $i = $#sheets; while ($cleanup && $i ++)
{

	# create the generated filename for this stylesheet
	my $file = $i == 0 ? join("", $outpath, $outfile, $outext)
		: join("", $outpath, $outfile, sprintf('.%02d', $i), $outext);

	# remove superflous files
	last unless unlink $file;

}

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

__END__

=head1 NAME

blessc - CSS post-processor to get around IE limitiations

=head1 SYNOPSIS

blessc [options] [ source | - ] [destination]

 Options:
   -v, --version      print version
   -h, --help         print this help
   -f, --force        overwrite input file
   -x, --compress     "minify" @import
   --no-cleanup       don\'t remove old css file before overwriting
   --no-imports       disable @import on stylesheets
   --no-cache-buster  turn off the cache buster

=head1 OPTIONS

=over 8

=item B<-help>

Print a brief help message with options and exits.

=back

=head1 DESCRIPTION

B<This program> is a CSS post-processor to get around IE limitiations

=cut