package Javascript::Closure;
use 5.008008;
use strict;
use warnings;
use Carp;
use LWP::UserAgent;
our $VERSION = 0.04;

use constant {
    WHITESPACE_ONLY         => 'WHITESPACE_ONLY',
    SIMPLE_OPTIMIZATIONS    => 'SIMPLE_OPTIMIZATIONS',
    ADVANCED_OPTIMIZATIONS  => 'ADVANCED_OPTIMIZATIONS',
	QUIET					=> 'QUIET',
	DEFAULT					=> 'DEFAULT',
	VERBOSE					=> 'VERBOSE',
    COMPILED_CODE           => 'compiled_code',
    WARNINGS                => 'warnings',
    ERRORS                  => 'errors',
    STATISTICS              => 'statistics',
    TEXT                    => 'text',
    JSON                    => 'json',
    XML                     => 'xml',
    CLOSURE_COMPILER_SERVICE=>'http://closure-compiler.appspot.com/compile'
};

my  @compilation_level = qw(WHITESPACE_ONLY SIMPLE_OPTIMIZATIONS ADVANCED_OPTIMIZATIONS);
my  @output_info       = qw(COMPILED_CODE WARNINGS ERRORS STATISTICS);
my  @output_format     = qw(TEXT JSON XML);
my  @warning_level     = qw(QUIET DEFAULT VERBOSE);

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK   = ('minify',@compilation_level,@output_info,@output_format,@warning_level);
our %EXPORT_TAGS = (CONSTANTS => [@compilation_level,@output_info,@output_format,@warning_level]);

our $TIMEOUT     = 5;

sub minify {
  my %args = @_;

  my %send                 = ();
  $send{output_info}       = _verify(\@output_info,      $args{output_info}      ,'lower') || COMPILED_CODE;
  $send{output_format}     = _verify(\@output_format,    $args{output_format}    ,'lower') || TEXT;  
  $send{compilation_level} = _verify(\@compilation_level,$args{compilation_level}        ) || WHITESPACE_ONLY; 
  $send{warning_level}     = _verify(\@warning_level    ,$args{warning_level}            ) || DEFAULT;  

  my $js                   = $args{input} || 'var test=true;//this is a test';
  #create the user agent
  my $ua   = _create_ua();
  my $source               = _cleanup_code_source($ua,$js);

  $send{js_code}  = $source->{js_code}  if(@{ $source->{js_code}  }  > 0);
  $send{code_url} = $source->{code_url} if(@{ $source->{code_url} }  > 0);

  return  _compile($ua,\%send);
}

#hacky i know
sub _verify {
  my ($vars,$value,$lower) = @_;

  return undef if(!$value);#set default value

  $value = [$value] if(ref($value) ne 'ARRAY');

  my $choice = join(',',@$vars);
  $choice    = "\L$choice" if($lower);

  foreach my $val (@$value){
  	  croak $val.' is not in:'.$choice if($val && !grep(/^$val$/i,@$vars));
	  $val = ($lower) ? "\L$val" :"\U$val";
  }
  return $value;
}

sub _create_ua {
  my $ua = LWP::UserAgent->new;
     $ua-> agent(__PACKAGE__."/".$VERSION);
     $ua-> timeout($TIMEOUT);
  return $ua;
}

sub _compile {
  my ($ua,$args)=@_;

  my $res = $ua->post(CLOSURE_COMPILER_SERVICE,$args);
  return $res->content if ($res->is_success);

  croak 'Fail to connect to '.CLOSURE_COMPILER_SERVICE.':'.$res->as_string;
}

sub _cleanup_code_source {
   my ($ua,$code_source) = @_;

   $code_source = [$code_source] if(ref($code_source) ne 'ARRAY');

   my (@str,@urls);
   foreach my $js (@$code_source) {
       if($js!~m{^http://}){
           push @str,$js; next;
       }
	   if($ua->get($js)->is_success){
		   push @urls,$js;
	   }
	   else {
		   carp 'The following url could not be fetched:'.$js;
	   }
   }
   return {
	   js_code  =>\@str,
	   code_url =>\@urls
   };
}

"The earth is blue like an orange.";

__END__


=pod

=head1 NAME

Javascript::Closure - compress your javascript code using Google online service of Closure Compiler 

=head1 VERSION

0.04

=head1 SYNOPSIS


    #nothing is imported by default
    use Javascript::Closure qw(minify :CONSTANTS); 

    #open a file
    open (FILE,'<','jscript.js') or die $!;
    my @lines = <FILE>;
    close FILE;
	
    #compress the code. most of the time it will be all you need!
    my $compressed   = minify(input=>join('',@lines));
    
    #output the result in another file
    open FILE,'>','closure-jscript.js' or die $!;
    print FILE $compressed;
    close FILE;

    #further settings:
    my $compressed = minify(input            => [$string,'http://www.domain.com/my.js',$string2,'http://www.domain2.com/my2.js'],
                            output_format    => XML,
                            output_info      => [STATISTICS,WARNINGS,COMPILED_CODE],
                            compilation_level=> SIMPLE_OPTIMIZATIONS,
							warning_level	 => VERBOSE
    );


=head1 DESCRIPTION

This package allows you to compress your javascript code by using the online service of Closure Compiler offered by Google
via a REST API. 
See L<http://closure-compiler.appspot.com/> for further information.


=head2 MOTIVATION

Needed a package to encapsulate a coherent API for a future Javascript::Minifier::Any package.

=head2 ADVANTAGES

Gives you access to the closure compression algo with an unified API.
It also gives you access to code analyze via errors, warnings and authorizing via statistics.

=head1  SUBROUTINE


=head2 minify

=over

Takes an hash with the following parameters(parameters ended with * are optionals):

=item input

Specify the javascript source to be compressed/analysed. 
It can be either an url or a scalar.

You can also use an array reference containing multiple urls or raw source code.

Example:

    use Javascript::Closure qw(minify :CONSTANTS); 
	
    my $compressed   = minify(input=>$jscode);
    my $compressed   = minify(input=>'http://www.yourdomain.com/yourscript.js');    
    my $compressed   = minify(input=>['http://www.yourdomain.com/yourscript.js','http://www.yourdomain.com/yourscript2.js',$jscode]);     


=item compilation_level

Specifies the algorithm use to compress the javascript code. 
You can specify them by using one of the following constants:

    WHITESPACE_ONLY
    SIMPLE_OPTIMIZATIONS (default)
    ADVANCED_OPTIMIZATIONS

Example:

    use Javascript::Closure qw(minify :CONSTANTS); 
	
    my $compressed   = minify(input=>$jscode,compilation_level=>WHITESPACE_ONLY);

	#if you do not import the constants:
    my $compressed   = minify(input=>$jscode,compilation_level=>Javascript::Closure::WHITESPACE_ONLY);

See CONSTANTS section for further information about the algorithms.
    
=item output_info

Specify the informations you will get back from the service.
it accepts either a scalar or an array reference.
You can specify them by using the following constants:

     COMPILED_CODE (default)
     WARNINGS
     ERRORS
     STATISTICS

Example:

    use Javascript::Closure qw(minify :CONSTANTS); 
	
	#you just want to get the code analysed before compression
    my $warnings   = minify(input=>$jscode,output_info=>WARNINGS);

	#only the errors in your code
    my $errors   = minify(input=>$jscode,output_info=>ERRORS);

	#everything was ok so you want to compressed code and some statistics about the efficiency of the compresssion
    my $errors   = minify(input=>$jscode,output_info=>[COMPILED_CODE,STATISTICS]);
     
 - output_format

Specify the format of the response.
It can be one of the following constants:

     TEXT (default)
     JSON
     XML

Example:

    use Javascript::Closure qw(minify :CONSTANTS); 
	
	#the default
    my $warnings   = minify(input=>$jscode,output_format=>TEXT);

	#get back the response as XML
    my $errors   = minify(input=>$jscode,output_format=>XML);

	#get back the response as JSON
    my $errors   = minify(input=>$jscode,output_format=>JSON);

Specifying the format can be useful if you want not only the compiled code but warnings,errors or statistics.
Though minify only returns the compressed version of the javascript code as a scalar,you can easily json-ify it 
to retrieve the information (you could do so with a corresponding XML module):

Example:

    use Javascript::Closure qw(minify :CONSTANTS); 
	use JSON;
	
	#the default
    my $output     = minify(input=>$jscode,output_format=>JSON,output_info=>[COMPILED_CODE,STATISTICS]);

	#change the raw string into perl structure
	my $response   = from_json($output);

	my $statistics = $response->{statistics};
	say $statistics->{originalSize};
	say $statistics->{compressedSize};


Javascript::Closure does not offer shortcuts access to errors,warnings or statistics for now.
Might add a Javascript::Closure::Response::JSON to make thing even sweeter but access through an hash is certainly faster
and the overhead of function call does not seem necessary...

=item warning_level

Specifies the amount of information you will get when you set the output_info to WARNINGS. 
You can specify them by using one of the following constants:

    QUIETE
    DEFAULT (default)
    VERBOSE

Example:

    use Javascript::Closure qw(minify :CONSTANTS); 
	
    my $compressed   = minify(input            => $jscode,
                              compilation_level=> WHITESPACE_ONLY,
                              output_info      => [COMPILE_CODE,WARNINGS],
                              warning_level    => VERBOSE
    );


See CONSTANTS section for further information about the algorithms.

=back

=head1  CONSTANTS

Each optional parameter to minify can be specified via constants.
If you do not import the :CONSTANTS, you will have to write Javascript::Closure::NAME_OF_THE_CONSTANT;

=item B<compilation level related>

 - WHITESPACE_ONLY
   remove space and comments from javascript code.

 - SIMPLE_OPTIMIZATIONS
   compress the code by renaming local variables

 - ADVANCED_OPTIMIZATIONS
   compress all local variables, do some clever stripping down of the code (unused functions are removed) 
   but you need to setup external references to do it properly.

See L<http://code.google.com/intl/ja/closure/compiler/docs/api-tutorial3.html> for further information.

=item B<output format related>

 - XML
   return the output in XML format with the information set with your output_info settings

 - JSON
   return the output in JSON format with the information set with your output_info settings

 - TEXT
   return the output in raw text format with the information set with your output_info settings

See L<http://code.google.com/intl/ja/closure/compiler/docs/api-ref.html#out> for further information.

=item B<output info related>

 - COMPILED_CODE
   return only the raw compressed javascript source code.

 - WARNINGS
   return any warnings found by the Closure Compiler (ie,code after a return statement)

 - ERRORS
   return any errors in your javascript code found by the Closure Compiler

 - STATISTICS
   return some statistics about the compilation process (original file size, compressed file size, time,etc)

See L<http://code.google.com/intl/ja/closure/compiler/docs/api-ref.html#output_info> for further information.

=item B<warning level related>

 - QUIET

 - DEFAULT

 - VERBOSE


See L<http://code.google.com/intl/ja/closure/compiler/docs/api-ref.html#warn> for further information.


=over 

=head1 PACKAGE PROPERTY

=head2 TIMEOUT

=over

Set by default to 10s. You can modify it if you need too:

    $Javascript::Closure::Timeout=15;

=back


=head1 DIAGNOSTICS

=head2 croak

=over

=item C<< Fail to connect to http://closure-compiler.appspot.com/compile:... >>

The module could not connect and successfully compress your javascript. 
See the detail error to get a hint.

=item C<< .... is not in:... >>

One of the optional parameter received a value that is not expected. 

=back

=head2 carp

=over

=item C<< The following url could not be fetched::... >>

The module could not connect to the url. 
See the detail error to get a hint.

=back

=head1 TODO

=over

=item B<optional parameters>

none of the following optional parameters are supported:

 - use_closure_library
 - formatting
 - output_file_name
 - exclude_default_externs
 - externs_url
 - js_externs


=back


=head1  SEE ALSO

=item B<other related modules>

L<WebService::Google::Closure>

L<JavaScript::Minifier>

L<JavaScript::Packer>

L<JavaScript::Minifier::XS>

L<http://closure-compiler.appspot.com/>

=back

=head1  CONFIGURATION AND ENVIRONMENT

none


=head1  DEPENDENCIES

L<LWP::UserAgent>

=head1  INCOMPATIBILITIES

none

=head1 BUGS AND LIMITATIONS

If you do me the favor to _use_ this module and find a bug, please email me
i will try to do my best to fix it (patches welcome)!

=head1 AUTHOR

shiriru E<lt>shiriru0111[arobas]hotmail.comE<gt>

=head1 LICENSE AND COPYRIGHT

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
