##################################################################
package Geo::OSM::SegmentList;
##################################################################

use Exporter;
@ISA = qw( Exporter );
use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
@EXPORT = qw( LoadOSM_segment_csv
	      reduce_segments_list
	      read_osm_file
	      load_segment_list
	      );

use strict;
use warnings;

use Math::Trig;

use Data::Dumper;
use Geo::Geometry;
use Geo::OSM::Planet;
use Utils::Debug;
use Utils::File;
use Utils::Math;
use Geo::Filter::Area;

sub load_segment_list($;$){
    my $do_filter_against_osm = shift;
    my $bounds  = shift;

    my $osm_segments;
    print STDERR "load_segment_list from: '$do_filter_against_osm'\n";
    if (  $do_filter_against_osm =~ m/^postgis|mapnik$/ ) {
	$osm_segments = Geo::OSM::SegmentList::LoadOSM_segment_postgis($bounds);
    } elsif ( -s $do_filter_against_osm ) {
	if (  $do_filter_against_osm =~ m/\.csv/ ) {
	    $osm_segments = Geo::OSM::SegmentList::LoadOSM_segment_csv($do_filter_against_osm, $bounds);
	} elsif ( $do_filter_against_osm =~ m/\.osm/ ) {
	    $osm_segments = Geo::OSM::SegmentList::read_osm_file($do_filter_against_osm, $bounds);
	} else {
	    die "Unknown Datatype for $do_filter_against_osm\n";
	}
	#print Dumper(\$osm_segments ) if $DEBUG;
    } elsif (  $do_filter_against_osm !~ m/^\d*$/ ) {
	    die "Unknown Datatype for $do_filter_against_osm\n";
    } else {

	# later we search in:
	#  ~/.osm/data/planet.osm.csv
	# /var/data/osm/planet.osm.csv

	my $home = $ENV{HOME}|| '~/';
	my $path=	planet_dir();
	my $osm_filename = "${path}/csv/osm-segments.csv";
	$osm_filename =~ s,\~/,$home/,;
	printf STDERR "check $osm_filename for loading\n" if $DEBUG;
	
	die "Cannot open $osm_filename\n" unless -s $osm_filename;
	$osm_segments = Geo::OSM::SegmentList::LoadOSM_segment_csv($osm_filename, $bounds);
    };
    return $osm_segments
}

# ------------------------------------------------------------------
# reduce osm Segments to only those inside the bounding box
# This make comparison faster
sub reduce_segments_list($$) {
    my $all_osm_segments = shift;
    my $bounds           = shift;

    my $start_time=time();

    #printf STDERR "reduce_osm_segments(".Dumper(\$bounds).")\n" if $DEBUG;

    my $osm_segments = [];
    my $count=0;
    my $all_count=0;
    for my $segment ( @{$all_osm_segments} ) {
	$all_count++;
	next unless $segment->[0] >= $bounds->{lat_min};
	next unless $segment->[0] <= $bounds->{lat_max};
	next unless $segment->[1] >= $bounds->{lon_min};
	next unless $segment->[1] <= $bounds->{lon_max};
	next unless $segment->[2] >= $bounds->{lat_min};
	next unless $segment->[2] <= $bounds->{lat_max};
	next unless $segment->[3] >= $bounds->{lon_min};
	next unless $segment->[3] <= $bounds->{lon_max};
	$count++;
	push(@{$osm_segments},$segment);
    }
    if ( $VERBOSE > 3 || $DEBUG > 3 ) {
	printf STDERR "		Reduced OSM Segment list to $count( $all_count) OSM-Segments ";
	print_time($start_time);
    }

    return $osm_segments;
}

# -------------------------------------------------------
# Load the csv Version of a segment list
# Args: $filename, {lat_min=> .., lat_max => ..., lon_min => .., lon_max => .. }
sub LoadOSM_segment_csv($;$){
    my $filename = shift;
    my $bounds  = shift;
    printf STDERR "Reading OSM-Segment-csv File: $filename, ($bounds->{lat_min} ... $bounds->{lat_max} , $bounds->{lon_min} ... $bounds->{lon_max})\n"
	if $DEBUG || $VERBOSE;
    my $start_time=time();

    my $segments;
    my $check_bounds = 1 if defined $bounds;
    $main::dont_write_osm_storable=1 ||  $check_bounds;
    $main::dont_read_osm_storable=1;

    if ( -s "$filename.storable" && 
	 ! file_needs_re_generation($filename,"$filename.storable")
	 && ! $main::dont_read_osm_storable ) {
	# later we should compare if the file also is newer than the source
	$filename .= ".storable";
	printf STDERR "Reading OSM File as storable: $filename\n"
	    if $DEBUG || $VERBOSE;
	$segments = Storable::retrieve($filename);
	if ( $VERBOSE >1 || $DEBUG) {
	    printf STDERR "Reading $filename done";
	    print_time($start_time);
	}
    } else {
	my $fh = data_open($filename);
	my $count=0;
	my $count_read=0;

	die "Cannot open $filename in LoadOSM_segment_csv.\n".
	    "Please create it first to use the option --osm.\n".
	    "See --help for more info"  unless $fh;

	while ( my $line = $fh ->getline() ) {
	    $count++;
	    chomp $line;
	    my @segment;
	    my $dummy;
	    ($segment[0],$segment[1],$segment[2],$segment[3],$segment[4]) = split(/,/,$line,5);
	    #print STDERR Dumper(\@segment);

	    if ( $check_bounds ) {
		next unless $segment[0] >= $bounds->{lat_min};
		next unless $segment[0] <= $bounds->{lat_max};
		next unless $segment[1] >= $bounds->{lon_min};
		next unless $segment[1] <= $bounds->{lon_max};
		next unless $segment[2] >= $bounds->{lat_min};
		next unless $segment[2] <= $bounds->{lat_max};
		next unless $segment[3] >= $bounds->{lon_min};
		next unless $segment[3] <= $bounds->{lon_max};
	    }

	    push (@{$segments},\@segment);
	    $count_read++;
	}
	$fh->close();
	if ( $VERBOSE >1 || $DEBUG) {
	    printf STDERR "Read and parsed $count_read($count) Lines in $filename";
	    print_time($start_time);
	}
	if ( ! $main::dont_write_osm_storable ) {
	    $start_time=time();
	    Storable::store($segments   ,"$filename.storable");
	    if ( $VERBOSE >1 || $DEBUG) {
		printf STDERR "Wrote Storable in to $filename.storable";
		print_time($start_time);
	    }
	};
    }


    return($segments);
}



# -------------------------------------------------------
# Load the segment list from postgis
# Args: {lat_min=> .., lat_max => ..., lon_min => .., lon_max => .. }
# This is a test to see if we can handle all segments of the world 
# with reasonable query times
sub LoadOSM_segment_postgis($;$){
    my $bounds  = shift;
    printf STDERR "Reading OSM-Segments form PostGis($bounds->{lat_min} ... $bounds->{lat_max} , $bounds->{lon_min} ... $bounds->{lon_max})\n"
	if $DEBUG || $VERBOSE;
    my $start_time=time();

    my $segments;
    my $check_bounds = 1 if defined $bounds;
    my @row;
    my $sth;

    use DBI;
    
    my $dbname="gis";
    my $dbhost="/var/run/postgresql";
    my $dbh = DBI->connect("dbi:Pg:dbname=$dbname;host=$dbhost", "", "", {AutoCommit => 0});

    unless( $dbh) {
	warn "\n\n!!!!!!!!!!!!!!!! WARNING: Cannot Open Database for reading OSM Segments\n\n";
	return;
    };

    # WHERE  the_geom && setsrid('BOX(47.36 21.40,51.185 21.53)'::box2d, 42102)
    # WHERE GeomFromText('POINT(25.3 25.40)', 42102) && the_geom \
    #       AND distance(the_geom, GeomFromText('POINT(25.7 5.3)', 42102)) = 0
    # WHERE distance(the_geom, setsrid('BOX(4.36 2.3,1.5 2.8)'::box2d, 42102)) = 0 
    # WHERE  the_point && setsrid('BOX(295149 2315499, 465992 2163790)'::box2d, -1)

    ##############################
    # FOR DEBUG
#    $bounds->{lat_min}=10;
#    $bounds->{lon_min}=10;
#    $bounds->{lat_max}=70;
#    $bounds->{lon_max}=70;
    #
    ####################################

    my $SRID_WGS84="4326";
    my $SRID_UNKNOWN="3395";
    my $SRID_OSM="900913";

    my $polygon_text=sprintf("POLYGON((%.5f %.5f,%.5f %.5f,%.5f %.5f,%.5f %.5f,%.5f %.5f))",
			     $bounds->{lat_min}-0.1 , $bounds->{lon_min}-0.1,
			     $bounds->{lat_max}+0.1 , $bounds->{lon_min}-0.1 , 
			     $bounds->{lat_max}+0.1 , $bounds->{lon_max}+0.1 , 
			     $bounds->{lat_min}-0.1 , $bounds->{lon_max}+0.1 , 
			     $bounds->{lat_min}-0.1 , $bounds->{lon_min}-0.1);
    my $polygon="transform(GeomFromText('$polygon_text',${SRID_WGS84}),$SRID_OSM)";
   
    my $select = "select osm_id,highway,name from planet_osm_line where highway is not null and way && ".
	" $polygon ".
	"limit 5;";
    print "\nprepare: $select\n";
    $sth = $dbh->prepare($select, { pg_server_prepare => 1 });
    $sth->execute();
    while ( @row = $sth->fetchrow_array ) {
	print "postgis ROW: @row\n";
    }
    printf STDERR "---------------------------------------------------------------\n" if $DEBUG || $VERBOSE;

    
#     $select = "SELECT name ,astext(way),$distance FROM planet_osm_roads WHERE  $distance < 500 order by  $distance;";
    
    #SELECT * FROM planet_osm_roads WHERE way in ?",
    #$select = "SELECT name FROM planet_osm_roads WHERE distance(way,GeomFromText('POINT(856371.58 6683083.41)', 3395)) < 1000;";
#    my $distance="distance(way,     GeomFromText('POINT(856371.58 6683083.41)', 3395))";
    my $point_text=sprintf("POINT(%.5f %.5f)",     $bounds->{lat_min} , $bounds->{lon_min});
    my $point="transform(GeomFromText('$point_text',${SRID_WGS84}),$SRID_OSM)";
    my $distance="distance(way,$point)";

    $select = "SELECT name FROM planet_osm_line WHERE ${distance} < 1000;";
    print "\nprepare: $select\n";
    $sth = $dbh->prepare($select, { pg_server_prepare => 1 });
    $sth->execute();
    while ( @row = $sth->fetchrow_array ) {
	print "postgis ROW: @row\n";
    }
    printf STDERR "---------------------------------------------------------------\n" if $DEBUG || $VERBOSE;

    $select = "SELECT name FROM planet_osm_roads WHERE ${distance} < 1000;";
    print "\nprepare: $select\n";
    $sth = $dbh->prepare($select, { pg_server_prepare => 1 });
    $sth->execute();
    while ( @row = $sth->fetchrow_array ) {
	print "postgis ROW: @row\n";
    }
    printf STDERR "---------------------------------------------------------------\n" if $DEBUG || $VERBOSE;

    $select = "SELECT osm_id,highway,name,asText(transform(way,${SRID_WGS84})) from planet_osm_line where highway is not null  limit 5;";
    print "\nprepare: $select\n";
    $sth = $dbh->prepare($select, { pg_server_prepare => 1 });
    $sth->execute();
    while ( @row = $sth->fetchrow_array ) {
	print "postgis ROW: @row\n";
    }

    printf STDERR "---------------------------------------------------------------\n" if $DEBUG || $VERBOSE;
        
    my $count=0;
    my $count_read=0;
    while ( @row = $sth->fetchrow_array ) {
	print "postgis ROW: @row\n";

	$count++;
	my $line="";
	chomp $line;
	my @segment;
	my $dummy;
	($segment[0],$segment[1],$segment[2],$segment[3],$segment[4]) = split(/,/,$line,5);
	#print STDERR Dumper(\@segment);

	if ( $check_bounds ) {
	    next unless $segment[0] >= $bounds->{lat_min};
	    next unless $segment[0] <= $bounds->{lat_max};
	    next unless $segment[1] >= $bounds->{lon_min};
	    next unless $segment[1] <= $bounds->{lon_max};
	    next unless $segment[2] >= $bounds->{lat_min};
	    next unless $segment[2] <= $bounds->{lat_max};
	    next unless $segment[3] >= $bounds->{lon_min};
	    next unless $segment[3] <= $bounds->{lon_max};
	}

	push (@{$segments},\@segment);
	$count_read++;
    }

    $dbh->disconnect;


    return($segments);
}


# ----------------------
sub Storable_save($$){
    my $filename = shift;
    my $segments = shift;
    eval{
	Storable::store($segments   ,"$filename.storable");
	};
    if ( $@ ) {
	#warn Dumper(\$segments);
	die "Storable_save(): $@\n";
    }
    printf STDERR "Stored OSM File: $filename as storable\n"
	if $DEBUG || $VERBOSE;
}

# ----------------------
sub Storable_load($){
    my $filename = shift;
    $filename .= ".storable";
    my $segments  = Storable::retrieve($filename);
    printf STDERR "Loaded OSM File: $filename as storable\n"
	if $DEBUG || $VERBOSE;
    return $segments;
}

##################################################################
# read Segment list from osm File
##################################################################

#our $read_osm_nodes;
our $read_osm_segments;
#our $read_osm_obj;
our (%MainAttr,$Type,%Tags, @WaySegments);
# Stats
our %AllTags;
# Stored data
our (%Nodes, %Segments, %Stats);
our $AREA_FILTER;
$AREA_FILTER = Geo::Filter::Area->new( area => "world" );
my $from_node=0;

# Function is called whenever an XML tag is started
#----------------------------------------------
sub DoStart()
{
    my ($Expat, $Name, %Attr) = @_;
    
    if($Name eq "node"){
	undef %Tags;
	%MainAttr = %Attr;
	$Type = "n";
    }
    if($Name eq "segment"){
	undef %Tags;
	%MainAttr = %Attr;
	$Type = "s";
    }
    if($Name eq "way"){
	undef %Tags;
	undef @WaySegments;
	%MainAttr = %Attr;
	$Type = "w";
    }
    if($Name eq "tag"){
	# TODO: protect against id,from,to,lat,long,etc. being used as tags
	$Tags{$Attr{"k"}} = $Attr{"v"};
	$AllTags{$Attr{"k"}}++;
	$Stats{"tags"}++;
    }
    if($Name eq "way"){
	$from_node=0;
    }
    if($Name eq "nd" ) {
	my $to_node   =  $Attr{"ref"};
	if ( $from_node &&
	     defined($Nodes{$from_node}) &&
	     defined($Nodes{$to_node}) 
	     ) {
	    my ($lat1,$lon1)=split(",",$Nodes{$from_node});
	    my ($lat2,$lon2)=split(",",$Nodes{$to_node});
	    my $angle = angle_north_relative(
				       { lat => $lat1 , lon => $lon1 },
				       { lat => $lat2 , lon => $lon2 });
	    push (@{$read_osm_segments},[$lat1,$lon1,$lat2,$lon2,$angle]);
    }
	$from_node = $to_node;
    }
}

# Function is called whenever an XML tag is ended
#----------------------------------------------
sub DoEnd(){
    my ($Expat, $Element) = @_;
    my $ID = $MainAttr{"id"};
    
    if($Element eq "node"){
	my $node={};
	$node->{"lat"} = $MainAttr{"lat"};
	$node->{"lon"} = $MainAttr{"lon"};
	
	if ( $AREA_FILTER->inside($node) ) {
	    $Nodes{$ID} = sprintf("%f,%f",$MainAttr{lat}, $MainAttr{lon});
	    foreach(keys(%Tags)){
		$node->{$_} = $Tags{$_};
	    }
	}
    }

    if($Element eq "segment"){
	my $from = $MainAttr{"from"};
	my $to   = $MainAttr{"to"};
	if ( defined($Nodes{$from}) && defined($Nodes{$to}) ) {
	    $Segments{$ID}{"from"} = $from;
	    $Segments{$ID}{"to"} = $to;
	}
    }

    if($Element eq "way"){
	if ( @WaySegments ) {
	    foreach my $seg_id( @WaySegments ){ # we only have the needed ones in here
	    }
	}
    }
}

# Function is called whenever text is encountered in the XML file
#----------------------------------------------
sub DoChar(){
    my ($Expat, $String) = @_;
}

# --------------------------------------------
sub read_osm_file($;$) { # Insert Segments from osm File
    my $filename = shift;
    my $bounds  = shift;

    print("Reading OSM Segment from File $filename\n") if $VERBOSE || $DEBUG;
    if ( file_needs_re_generation($filename,"$filename.storable")) {
	print "$filename:	".(-s $filename)." Bytes\n" if $DEBUG;
	
	print STDERR "Parsing file: $filename\n" if $DEBUG;
	my $p = new XML::Parser( Handlers => {
	    Start => \&DoStart, 
	    End => \&DoEnd, 
	    Char => \&DoChar,
			     },
				 ErrorContext => 10,
	    );
	
	my $fh = data_open($filename);
	if (not $fh) {
	    print STDERR "WARNING: Could not open osm data from $filename\n";
	    return;
	}
	my $content;
	eval {
	    $content = $p->parse($fh);
	};
	if (not $p) {
	    print STDERR "WARNING: Could not parse osm data from $filename\n";
	    return;
	}
	#warn Dumper(\$read_osm_segments);
	Storable_save($filename,$read_osm_segments);  	
    } else {
	$read_osm_segments=Storable_load($filename);
    }
	return($read_osm_segments);
}

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

1;

=head1 NAME

Geo::OSM::SegmentList

=head1 COPYRIGHT

Copyright 2006, Jörg Ostertag

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

=head1 AUTHOR

Jörg Ostertag (planet-count-for-openstreetmap@ostertag.name)

=head1 SEE ALSO

http://www.openstreetmap.org/

=cut
