#!/usr/bin/env perl

use strict;
use warnings;
use LWP::Simple;
use XML::Simple;
use File::Temp ( 'tempfile', 'tempdir' );
use Scalar::Util 'reftype';
use Digest::MD5;
use version;

# ----------------------------
exit main();

# ----------------------------

sub printUsage() {
	print STDERR "Usage: eclipse2abs <template> <updatesite>\n";
        print STDERR "The output is written to the file PKGBUILD. Therefore you must not use the file PKGBUILD as template!\n";
}

sub main {
	if ( @ARGV != 2 ) {
		printUsage();
		return 1;
	}

	my $templatefile = $ARGV[0];
	my $updatesite   = $ARGV[1];
	$updatesite =~ s|/$||;

	my $sources      = [];
	my $md5sums      = [];
	my $plugin_jars  = [];
	my $feature_jars = [];

    my $site = get_site("$updatesite/site.xml");
	my @associatedSites = get_associated_sites($site);
	
	my $features = get_features( $site );
	$features = filter_features_with_same_id($features);
	
	for my $feature ( @{$features} ) {
		if($feature->{id} !~ /org\.eclipse\..*/) {
			my $tempdir            = tempdir( CLEANUP => 1 ); 
			
			my $jarfile            = download_feature_jar("$updatesite/$feature->{'url'}");
			my $feature_xml        = extract_feature_xml($jarfile);
			my $plugin_definitions = extract_plugin_definitions($feature_xml);
			my $feature_sources    = build_sources_array($feature, $plugin_definitions );

            my $files              = download_files($tempdir, $feature_sources, $updatesite, @associatedSites);
            
            push( @{$sources},      build_source_urls($feature_sources, $files) );
			push( @{$plugin_jars},  build_plugin_jars($feature_sources) );
            push( @{$feature_jars}, build_feature_jars($feature_sources) );
            push( @{$md5sums},      build_md5sums($feature_sources, $files) );
			
            
			push( @{$features}, extract_included_features($feature_xml));
		}
	}

	my $options = {
		pkgver       => $features->[0]->{'version'},
		source       => $sources,
		noextract    => $sources,
		md5sums      => $md5sums,
		plugin_jars  => "'" . join( "' '", @{$plugin_jars} ) . "'",
		feature_jars => "'" . join( "' '", @{$feature_jars} ) . "'"
	};
	configure_pkgbuild( $templatefile, $options );

	return 0;
}

sub get_site {
    my ($updatesite) = @_;

    my $xml_page = get($updatesite) or die "Cannot load updatesite $updatesite: $!";
    my $xml_document = XMLin( $xml_page, KeyAttr => [], ForceArray => 1 )
      or die "Cannot parse site.xml from updatesite: $!";
    	
	return $xml_document;
}

sub get_associated_sites {
    my ($site) = @_;	
    my $xml_url = $site->{'associateSitesURL'};
    my @urls;
    
    return () if ! $xml_url;
    
    my $xml_page = get($xml_url) or die "Cannot load associated sites $xml_url: $!";
    my $xml_document = XMLin( $xml_page, KeyAttr => [], ForceArray => 1 )
      or die "Cannot xml file from $xml_url: $!";

    for my $site (@{$xml_document->{'associateSite'}}) {
    	push( @urls, $site->{'url'});
    }
    
    return @urls;
}

sub get_features {
	my ($site) = @_;

	my @features = $site->{'feature'};
	return \@{ $features[0] };
}

sub filter_features_with_same_id {
	my ($features) = @_;
	my @filtered_features;
	my %feature_id_to_feature;
	my $feature;
	
	for $feature (@{$features}) {
			if(exists $feature_id_to_feature{$feature->{'id'}}) {
					my $existing_feature = $feature_id_to_feature{$feature->{'id'}};
                    print "WARN: Found duplicate feature [$feature->{'id'}] with versions: [$feature->{'version'}] <-> [$existing_feature->{'version'}]\n";
                    if(version->is_lax($feature->{'version'}) && version->is_lax($existing_feature->{'version'})) {
                        print "Compare versions numerically...";
    					if(version->parse($feature->{'version'}) > version->parse($existing_feature->{'version'})) {
                            print "use second version [$feature->{'version'}]\n";
     						$feature_id_to_feature{$feature->{'id'}} = $feature;
	    				} else {
                            print "use first version [$existing_feature->{'version'}]\n";
                        }
                    } else {
                        print "Cannot compare the versions numerically. Use the second one: [$existing_feature->{'version'}].\n";
                        $feature_id_to_feature{$feature->{'id'}} = $feature;
                    }
			}
			else {
				$feature_id_to_feature{$feature->{'id'}} = $feature;
			}
	}
	
	for $feature (@{$features}) {
			if(exists $feature_id_to_feature{$feature->{'id'}}) {
					push(@filtered_features, $feature_id_to_feature{$feature->{'id'}});
					delete  $feature_id_to_feature{$feature->{'id'}};
			}
	}
	
	return \@filtered_features;
}

sub download_feature_jar {
	my ($url) = @_;
	my ( $fh, $filename ) = tempfile();

    $url =~ s#([^:])//#$1/#g;
	print "Download feature jar from $url...";
	if ( getstore( $url, $filename ) != 200 ) {
		die "Cannot load $url to $filename: $!";
	}
	print "ok [$filename]\n";
	return $filename;
}

sub extract_feature_xml {
	my ($jarfile) = @_;

	my $tempdir = tempdir( CLEANUP => 1 );
    `unzip $jarfile -d $tempdir`
	  or die "Cannot extract jarfile $jarfile to $tempdir: $!";

	print "feature.xml extracted to $tempdir/feature.xml\n";
	return "$tempdir/feature.xml";
}

sub extract_plugin_definitions {
	my ($feature_xml) = @_;
	my $xml_document = XMLin( $feature_xml, KeyAttr => [], ForceArray => 1 )
	  or die "Cannot parse $feature_xml: $!";
	my $plugins = [];

	for my $plugin ( @{ $xml_document->{'plugin'} } ) {
		if ( ref $plugin && reftype($plugin) eq "HASH" ) {
			print "Adding plugin $plugin->{'id'}\n";
			push( @{$plugins}, $plugin );
		} else {
			print STDERR "Warning: $plugin is not a hash reference!\n";
		}
	}

	return $plugins;
}

sub extract_included_features {
	my ($feature_xml) = @_;
	
	my $xml_document = XMLin( $feature_xml, KeyAttr => [], ForceArray => 1 )
	  or die "Cannot parse $feature_xml: $!";
	my $features = [];

	for my $feature ( @{ $xml_document->{'includes'} } ) {
		if ( ref $feature && reftype($feature) eq "HASH" ) {
			$feature->{'url'} = "features/$feature->{'id'}_$feature->{'version'}.jar";
			push( @{$features}, $feature );
		}
	}

	return @{$features};
}

sub configure_pkgbuild {
	my ( $template, $options ) = @_;

    print "Configure PKGBUILD\n";
	open my $template_handle, '<', $template  or die "Cannot open template file $template: $!";
	open my $output_handle,   '>', 'PKGBUILD' or die "Cannot create PKBUILD: $!";

	while ( my $line = <$template_handle> ) {
		while ( my ( $key, $value ) = each %{$options} ) {
			if ( ref $value && reftype($value) eq 'ARRAY' ) {
				$value = "'" . join( "'\n\t'", @{$value} ) . "'";
			}

			$line =~ s/\$\{$key\}/$value/g;
		}
		print $output_handle "$line";
	}

	close($template_handle);
	close($output_handle);
}

sub build_sources_array {
	my ($feature_hash, $plugin_definitions ) = @_;
	my @sources;

	# /features/ is necessary for later matches
	push( @sources, "/$feature_hash->{'url'}" );
	for my $plugin ( @{$plugin_definitions} ) {
		# /plugins/ is necessary for later matches
		push( @sources, "/plugins/$plugin->{'id'}_$plugin->{'version'}.jar" );
	}

	return \@sources;
}

sub download_files {
	my ($tempdir, $sources, @downloadSites) = @_;
	my $files = {};
	
	my $i = 0;
    for my $source ( @{$sources} ) {
        my $url = download_source( $source, "$tempdir/$i", @downloadSites );
        $files->{$source} = {
            url => $url,
            file => "$tempdir/$i"
        };
        $i += 1;
    }
    
    return $files;
}

sub download_source {
    my ($source, $destination, @downloadSites) = @_;

    for my $baseurl (@downloadSites) {
        my $url = "$baseurl/$source";
        
        $url =~ s#([^:])//#$1/#g;
        print "Download $url";
        my $code = getstore( $url, $destination );
        if( $code == 200 ) {
            print "    OK [$destination]\n";
            return $url;
        } else {
            print "    FAILED ($code)\n";
        }
    }
    
    die "Cannot download $source from @downloadSites!\n";
}

sub build_md5sums {
	my ($sources, $files) = @_;
	my @md5sums;

	my $i = 0;
	for my $source ( @{$sources} ) {
		push( @md5sums, build_md5sum($files->{$source}->{'file'}) );
		$i += 1;
	}

	return @md5sums;
}

sub build_md5sum {
	my ($file) = @_;

	open my $file_handle, '<', $file or die "Can't open '$file': $!";
	binmode($file_handle);

	my $md5 = Digest::MD5->new;
	return $md5->addfile(*$file_handle)->hexdigest;
}

sub build_source_urls {
	my ($sources, $files) = @_;
    my $jar;
    my $url;
    my @source_urls;

	for my $source ( @{$sources} ) {
		( $jar = $source ) =~ s|.*/([^/]*)$|$1|;
		if ( $source =~ m|/plugins/| ) {
			$url = "plugin_${jar}::$files->{$source}->{'url'}";
		} elsif ( $source =~ m|/features/| ) {
			$url = "feature_${jar}::$files->{$source}->{'url'}" ;
        }
        
        $url =~ s#([^:])//#$1/#g;
        push( @source_urls, $url );
	}

    return @source_urls;
}

sub build_plugin_jars {
	my ($sources) = @_;
	my $plugin_jar;
	my @plugin_jars;

	for my $source ( @{$sources} ) {
		if ( $source =~ m|/plugins/| ) {
			( $plugin_jar = $source ) =~ s|.*/([^/]*)$|$1|;
			push( @plugin_jars, "plugin_$plugin_jar" );
		}
	}

	return @plugin_jars;
}

sub build_feature_jars {
	my ($sources) = @_;
	my $feature_jar;
	my @feature_jars;

	for my $source ( @{$sources} ) {
		if ( $source =~ m|/features/| ) {
			( $feature_jar = $source ) =~ s|.*/([^/]*)$|$1|;
			push( @feature_jars, "feature_$feature_jar" );
		}
	}

	return @feature_jars;
}

