#!/usr/bin/perl
#    gcp - GeoCachingPlus
#    Copyright (C) 2003 by the gtk2-perl team (see the file AUTHORS for the full
#    list)
#    Copyright (C) 2006 - 2010  Michal Grezl
#
#    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; version 2 of the License.
#
#    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.

#you need to get this page http://www.geocaching.com/my/logs.aspx?s=1
#http://www.kryogenix.org/code/browser/sorttable/sorttable.js - sortable tables
#'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November'  'December'.

#version 0.1.51

#http://www.geocaching.com/seek/cache_details.aspx?pf=y&guid=3b3da7b9-ebb9-42a9-8f1d-7393aaa55f99&decrypt=y&log=y&numlogs=5
#http://www.geocaching.com/seek/cache_details.aspx?pf=y&wp=GCZXGZ&decrypt=y&log=y&numlogs=5

use HTML::Parser ();
use Text::Trim;
use XML::Parser;
use Data::Dumper;
use POSIX;
use GD;
use GD::Graph::Data;
use GD::Graph::bars;
use HTML::HeadParser;
use Date::Manip;
use XML::Writer;
use Math::Trig;
use Encode;
use Geo::Inverse;
use utf8;
use Getopt::Popt qw(:all);
use Data::Dumper;
use Gtk2 -init;
use Gtk2::SimpleList;
use File::Basename;
use List::Util qw/ min max sum /;

$kml_style = '
  <Style id="t">
   <IconStyle>
    <Icon>
     <href>http://walley.org/geocaching/t/t.png</href>
    </Icon>
   </IconStyle>
   <LabelStyle>
    <color>eeeeeeee</color>
    <scale>1</scale>
   </LabelStyle>
  </Style>
  <Style id="m">
   <IconStyle>
    <Icon>
     <href>http://walley.org/geocaching/t/m.png</href>
    </Icon>
   </IconStyle>
   <LabelStyle>
    <color>eeeeeeee</color>
    <scale>1</scale>
   </LabelStyle>
  </Style>
  <Style id="u">
   <IconStyle>
    <Icon>
     <href>http://walley.org/geocaching/t/u.png</href>
    </Icon>
   </IconStyle>
   <LabelStyle>
    <color>eeeeeeee</color>
    <scale>1</scale>
   </LabelStyle>
  </Style>
  <Style id="v">
   <IconStyle>
    <Icon>
     <href>http://walley.org/geocaching/t/v.png</href>
    </Icon>
   </IconStyle>
   <LabelStyle>
    <color>eeeeeeee</color>
    <scale>1</scale>
   </LabelStyle>
  </Style>
  <Style id="e">
   <IconStyle>
    <Icon>
     <href>http://walley.org/geocaching/t/e.png</href>
    </Icon>
   </IconStyle>
   <LabelStyle>
    <color>eeeeeeee</color>
    <scale>1</scale>
   </LabelStyle>
  </Style>
  <Style id="w">
   <IconStyle>
    <Icon>
     <href>http://walley.org/geocaching/t/w.png</href>
    </Icon>
   </IconStyle>
   <LabelStyle>
    <color>eeeeeeee</color>
    <scale>1</scale>
   </LabelStyle>
  </Style>
';

#lon x
#lat y

my $NET_ACCESS = 0;
my $PROCESS_ROUTE = 0;
my $INPUT = 0;
my $GPX_EXPORT = 0;
my $KML_EXPORT = 0;
my $GRAPH_EXPORT = 0;
my $INTERACTIVE = 0;
my $GPS_IMPORT = 0;
my $HTML_EXPORT = 0;

$verbose = 1;
$debug_print = 0;

$all_caches_found = 0;

$map_x1 = 12.15;#3289511.09 5378083.47
$map_y1 = 51.0; #3773849.47 5659466.34
$map_x2 = 18.9;
$map_y2 = 48.5;

$map_size_x = 2000;
$map_size_y = 1140;

#$map_size_x = 1025;
#$map_size_y = 586;

$map_file = "export/all.png";
$my_map_file = "export/my.png";
$my_kml_file = "export/my.kml";
$others_kml_file = "export/rest.kml";
$all_kml_file = "export/all.kml";
$html_file = "export/index.html";

$gpx_file   = "export/gpx-a.gpx";
$gpx_file_t = "export/gpx-t.gpx";
$gpx_file_f = "export/gpx-f.gpx";
$gpx_file_n = "export/gpx-n.gpx";

$graph_d_file = "export/graphdaily.png";
$graph_m_file = "export/graphmonthly";
$cachemap_file = "export/cachemap.html";

my @optionsTable = (
  {
    longName => "input",
    shortName => 'i', 
    argInfo => POPT_ARG_STRING, 
    arg => \$input, 
    val => "i",
    descrip => "input html file", 
    argDescrip => "HTML"
  },
  {
    longName => "routekml",
    shortName => 'r', 
    argInfo => POPT_ARG_STRING, 
    arg => \$routekml, 
    val => "r",
    descrip => "caches along the route, route file", 
    argDescrip => "KML"
  },
  {
    longName => "enable-network",
    shortName => 'n', 
    argInfo => 0, 
    val => "n",
    descrip => "enable access to geocaching.com", 
    argDescrip => "net"
  },
  {
    longName => "export-gpx",
    shortName => 'x', 
    argInfo => 0, 
    val => "x",
    descrip => "export gpx file (with garmin extensions, pq file)", 
    argDescrip => "x?"
  },
  {
    longName => "export-kml",
    shortName => 'k', 
    argInfo => 0, 
    val => "k",
    descrip => "will export google-earth .kml", 
    argDescrip => "k?"
  },
  {
    longName => "export-graph",
    shortName => 'g', 
    argInfo => 0, 
    val => "g",
    descrip => "create nice graphs", 
    argDescrip => "g?"
  },
  {
    longName => "export-html",
    shortName => 'h', 
    argInfo => 0, 
    val => "h",
    descrip => "html statistics", 
    argDescrip => "h?"
  },
  {
    longName => "interactive",
    shortName => 'a', 
    argInfo => 0, 
    val => "a",
    descrip => "X program", 
    argDescrip => "a?"
  },
  POPT_AUTOHELP,
);

$popt = new Getopt::Popt(argv => \@ARGV,options => \@optionsTable);
$popt->setOtherOptionHelp("[OPTIONS]*");

my $val;
eval {
  while(defined($val = $popt->getNextOptChar())) { 
    if ($val eq 'r') { print "rok\n"; $PROCESS_ROUTE = 1}
    if ($val eq 'n') { print "nok\n"; $NET_ACCESS = 1;}
    if ($val eq 'x') { print "xok\n"; $GPX_EXPORT = 1;}
    if ($val eq 'k') { print "kok\n"; $KML_EXPORT = 1;}
    if ($val eq 'g') { print "gok\n"; $GRAPH_EXPORT = 1;}
    if ($val eq 'i') { print "iok\n"; $INPUT = 1;}
    if ($val eq 'a') { print "aok\n"; $INTERACTIVE = 1;}
    if ($val eq 'h') { print "hok\n"; $HTML_EXPORT = 1;}
  }
};

if (!$INPUT and !$PROCESS_ROUTE ) {
  die "No input file entered. \n use --help \nI did die"; 
}

# check for errors:
if($@) {
  print "bad argument: $@\n"; 
}

if ($GPS_IMPORT) {
  &inform("Reading data from gps(file)\n");
  &get_gps_info();
  #&show_caches(\%caches, \%gps_poi);
  #die;
}

&inform("Geting info about all caches from gpx files\n");
&get_caches_info()
&inform("Geting cache info about my caches from gc.html\n");

my %found_caches = &get_my_caches_info($input);

#print "START of caches dump\n";
#print Dumper(%caches);
#print "START of found caches dump\n";
#print Dumper(%found_caches);
#print "END of dumps\n";

&mark_found_caches(\%caches, %found_caches);
##############

if ($HTML_EXPORT) {
  &inform("Exporting HTML\n");
  &export_html(\%caches, $html_file);
}  

if ($GPX_EXPORT) {
  &inform("Exporting gpx\n");
  &export_gpx(\%caches, $gpx_file  , "*");
  &export_gpx(\%caches, $gpx_file_t, "T");
  &export_gpx(\%caches, $gpx_file_f, "X");
  &export_gpx(\%caches, $gpx_file_n, "!");
}  

if ($PROCESS_ROUTE) {
  &inform("Path\n");
  &caches_along_route(\%caches, $routekml);
}

&inform("Making map of my caches\n");
&draw_caches(\%caches, $my_map_file);
&inform("Making map of all caches\n");

&draw_caches(\%caches, $map_file);
&draw_cachesx(\%caches, $map_file);

#&inform("Exporting Cachemap\n");
#&export_cachemap(\%caches, $cachemap_file);

if ($GPX_EXPORT) {
  &inform("Exporting gpx\n");
  &export_gpx(\%caches, $gpx_file  , "*");
  &export_gpx(\%caches, $gpx_file_t, "T");
  &export_gpx(\%caches, $gpx_file_f, "X");
  &export_gpx(\%caches, $gpx_file_n, "!");
}  

if ($KML_EXPORT) {
  &export_kml2(\%caches, "export/kml-a.kml", "*");
  &export_kml2(\%caches, "export/kml-n.kml", "!");
  &export_kml2(\%caches, "export/kml-f.kml", "X");
  &export_kml2(\%caches, "export/kml-t.kml", "T");
}

if ($GRAPH_EXPORT) {
  &inform("Exporting graph");
  &inform("daily");
 # &export_graph($graph_d_file, "daily");
  &inform(", monthly");
  &export_graph($graph_m_file, "monthly");
  &inform(".\n");
}

#print Dumper(%my_caches);
#print Dumper(%caches);
#print Dumper(%graph_data);
#print Dumper(%gps_poi);

if ($INTERACTIVE) {
  &show_caches(\%caches, \%gps_poi);
}

&inform("Bye.\n");

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


################################################################################
sub inform()
################################################################################
{
  if ($verbose) {print @_[0]};
}

################################################################################
sub debug()
################################################################################
{
  if ($debug_print) {print @_[0]};
}

################################################################################
#sub mark_found_caches()
################################################################################
#{
#  my ($cache, @guids) = @_;
#  my $found = 0;
#
#  print "mark_found_caches():start\n";
#  foreach $i (keys %{$cache}) {
#    $found = 0;
#    foreach $j (@guids) {
#      if ($cache->{$i}{guid} eq $j) {
#        $found = 1;
#        print "if ($cache->{$i}{guid} eq $j) {$found = 1;}\n";
#      }
#    }
#    $cache->{$i}{found} = $found;
#  }
#  print "mark_found_caches():end\n";
#}

################################################################################
sub mark_found_caches()
################################################################################
{
  my ($cache, %guids) = @_;
  my $found = 0;

  print "mark_found_caches():start\n";
  foreach $i (keys %{$cache}) {
    $found = 0;
    foreach $j (keys %guids) {
      if ($cache->{$i}{guid} eq $j) {
        print "mark_found_caches():found cache with guid==$j\n";
        $found = 1;
        $cache->{$i}{date} = $guids{$j}{date};
        $cache->{$i}{my_id} = $guids{$j}{my_id};
      }
    }
    $cache->{$i}{found} = $found;
  }
  print "mark_found_caches():end\n";
}

################################################################################
sub get_my_caches_info()
################################################################################
{
  &erase_flags_my();
  $flag_my_found = 0;
  $all_caches_found = 0;
  local $my_id = 0;
  local @my_caches_wpt;
  local %my_caches;

  local $flag_my_cache_link = 0;
  local $flag_my_country_found = 0;
  local $flag_my_date_found = 0;
  local $flag_my_tr_start = 0;
  local $flag_my_td_found = 0;
  local $my_log_date_text = "";

  $p = HTML::Parser->new(api_version => 3,
                         start_h => [\&start_my, "tagname, attr, attrseq, text"],
                         text_h  => [\&text_my,  "dtext"],
                         end_h   => [\&end_my,   "self, tagname"],
                         marked_sections => 1,);
  $p->parse_file("gc.html");
  $flag_my_start_log = 0;
  &erase_flags_my();
#  print "get_my_caches_info():",$my_caches_wpt,"\n";
#  print Dumper(@my_caches_wpt);
#  return @my_caches_wpt;
  return %my_caches;
}

################################################################################
sub start_my
################################################################################
{
  my ($tag, $attr, $attrseq, $origtext) = @_;


#  if ($tag eq "span" and $attr->{id} eq "lbLogFilters") {
  if ($tag eq "span" and $attr->{id} eq "ctl00_ContentBody_lbLogFilters") {
    $flag_my_start_log = 1;
  }

  if ($flag_my_start_log and $tag eq "tr") {
    $flag_my_tr_start = 1;
    $flag_my_td_found = 0;

    $cache_name_from_gchtml = "";
    
  } 

  if ($flag_my_tr_start and $tag eq "td") {
    $flag_my_td_found++;
    # 1 success ?
    if ($flag_my_td_found == 2) {$flag_my_date_found = 1;}
    if ($flag_my_td_found == 4) {$flag_my_country_found = 1;}
  }

  if ($flag_my_td_found == 1) {
    $flag_my_found = 0;
    if ($tag eq "img") {
      if ($attr->{alt} eq "Found it" || $attr->{alt} eq "Webcam Photo Taken" || $attr->{alt} eq "Attended") {
        $flag_my_found = 1;
        $all_caches_found++;
      }
    }
  } 

  if ($tag eq "a" && $flag_my_td_found == 3 && $attr->{href} =~ "cache_details") {
    $my_guid = (split ("=", $attr->{href}))[1];
#    print "start_my(): my guid $my_guid ($attr->{href})\n";
    $my_cache_name = "XXX";
    $my_type = "XXX";
    $my_size = "XXX";
    $junk = "XXX";
    $my_hint = "XXX";

#    ($my_cache_name, $my_type, $my_size, $junk, $my_hint) = &get_cache_info_from_web($my_guid);

    $flag_my_cache_link = 1;
    &debug("start_my(): My caches => ($my_cache_name,$my_type,$my_size,$my_guid)\n");
  }

  if ($tag eq "a" && $flag_my_td_found == 5 && $attr->{href} =~ "log.aspx") {
    $my_luid = (split ("=", $attr->{href}))[1];

    $my_log_date = $my_log_date_text;
#    $log_date = get_log_date($luid);
    $parsed_date = substr(ParseDate($my_log_date), 0, 8);
    $graph_data{$parsed_date}++;

#    $m = substr($my_cache_name, 0, 9);
#    $m =~ /\((.*)\)/;
#    $my_wpt = $1;

    if ($flag_my_found) {
      $my_id++;
#      push @my_caches_wpt, $my_guid; 
      $my_caches{$my_guid}{my_id} = $my_id;
      $my_caches{$my_guid}{date} = $parsed_date;
    }    
  }
}

################################################################################
sub text_my
################################################################################
{
  my ($dtext, $x) = @_;

  if ($flag_my_cache_link) {
    $flag_my_cache_link = 0;
    $cache_name_from_gchtml = $dtext;
    if ($cache_name eq "--unknown--" or $cache_name =~ "cache is archived") {
      $cache_name ="(XX".int(rand()*10000).")".$cache_name_from_gchtml." ".$cache_name;
    }
  }  

  if ($flag_my_date_found) {
    $my_log_date_text = $dtext;
    $flag_my_date_found = 0;
  }

  if ($flag_my_country_found) {
    $my_country_text = $dtext;
    $flag_my_country_found = 0;
  }
}

################################################################################
sub end_my
################################################################################
{
  my ($self, $tag) = @_;

  if ($tag eq "tr" && $flag_my_tr_start) {
    &erase_flags_my();
  }  
}

################################################################################
sub erase_flags_my
################################################################################
{
  $flag_my_cache_link = 0;
  $flag_my_country_found = 0;
  $flag_my_date_found = 0;
  $flag_my_tr_start = 0;
  $flag_my_td_found = 0;
  $my_log_date_text = "";
}


################################################################################
sub start_log
################################################################################
{
  my ($tag, $attr, $attrseq, $origtext) = @_;

  if ($tag eq "span" && $attr->{id} =~ "LogBookPanel1_LogDate") {
      $date_found = 1;
  } 
}

################################################################################
sub text_log
################################################################################
{
  my ($dtext, $x) = @_;
  if ($date_found) {
    $log_date_text = $dtext;
    $date_found = 0;
  }
}

################################################################################
sub end_log 
################################################################################
{
  my($self, $tagname) = @_;
}


################################################################################
sub get_cache_info_from_web()
################################################################################
{ 
  my $cache_guid = @_[0];
  my $cache_wpt  = @_[1];
  local $name_guid = "";
  local $size = "";
  local $type = "";
  local $flag_hint = 0;
  local $hint_text = "";

  $title_text = "--unknown--";

  if ($cache_guid ne "") {
    if (!&get_file("http://www.geocaching.com/seek/cache_details.aspx?guid=$cache_guid", "$cache_guid.cache", "cache.html")){
      return "not known", "x", "x", "x", "x";
    }
  } 

  if ($cache_wpt ne "") {
    if (!&get_file("http://www.geocaching.com/seek/cache_details.aspx?wp=$cache_wpt", "$cache_wpt.cache", "cache.html")) {
      return "not known", "x", "x", "x", "x";
    }
  } 

  $cache_page = HTML::Parser->new(api_version => 3);
  $cache_page->handler( start => \&start_name, "tagname, self, attr, attrseq, text, dtext");
  $cache_page->handler( text  => \&text_name, "tagname, text, dtext");
  $cache_page->handler( end  => \&end_name, "tagname");
  $cache_page->parse_file("cache.html");
  trim $title_text;

  &debug("get_cache_info_from_web(): $title_text, $type, $size, $name_guid;\n");

  return $title_text, $type, $size, $name_guid, $hint_text;
}
   
################################################################################
sub start_name 
################################################################################
{
    my ($tag, $self, $attr, $attrseq, $origtext, $dtext) = @_;
 
    if ($tag eq "title") {
      $cache_name_title = 1;
    }

    if ($tag eq "span" && $attr->{"id"} eq "Hints") {
      $flag_hint = 1;
    }

    if ($attr->{alt} eq "Multi-cache") {
      $type = "M";
    }
    if ($attr->{alt} eq "Traditional Cache") {
      $type = "T";
    }
    if ($attr->{alt} eq "Unknown Cache") {
      $type = "U";
    }
    if ($attr->{alt} eq "Virtual Cache") {
      $type = "V";
    }
    if ($attr->{alt} eq "Event Cache") {
      $type = "E";
    }
    if ($attr->{alt} eq "Webcam Cache") {
      $type = "W";
    }
    
    if ($attr->{id} eq "lnkPrintFriendly") {
      $name_guid = (split('&', $attr->{href}))[1];
      $name_guid =~ s/guid=//g;
      #href="./cache_details.aspx?pf=y&amp;guid=4ab14194-1d29-480a-8666-056c17aa17fe&amp;log=n&amp;decrypt=";
    }
    
}

################################################################################
sub text_name 
################################################################################
{
  my ($t, $tt, $dtext) = @_;
  if ($cache_name_title) {
    $title_text = $dtext;
    chomp $title_text;
    trim $title_text;
    
    if ($title_text =~ /Error Occurred/) {
      $title_text = "(XX".int(rand()*10000).") cache is archived or pm";
    }
  } 
  
  if ($flag_hint) {
    $hint_text = $dtext;
    $hint_text =~ y/A-Za-z/N-ZA-Mn-za-m/;
    $flag_hint = 0;
  }
  
  if ($dtext eq "Micro")   {$size = 'M';}
  if ($dtext eq "Small")   {$size = 'S';}
  if ($dtext eq "Regular") {$size = 'R';}
  if ($dtext eq "Large")   {$size = 'L';} 
}

################################################################################
sub end_name 
################################################################################
{
  my ($t) = @_;
  if ($t eq "title") {
    $cache_name_title = 0;
  }
}

################################################################################
sub get_log_date($luid)
################################################################################
{
  $luid = @_[0];
  $log_date_text = "unknown";
  &get_file("http://www.geocaching.com/seek/log.aspx?LUID=$luid", "$luid.log", "log.html");

  my $log_page = HTML::Parser->new( api_version => 3,
                        start_h => [\&start_log, "tagname, attr, attrseq, text"],
                        text_h  => [\&text_log, "dtext"],
#                        end_h   => [\&end_log, "self, tagname"],
                        marked_sections => 1,
                      );
  $log_page->parse_file("log.html");
  trim $log_date_text;
  return $log_date_text;
}

################################################################################
sub get_file ()
################################################################################
{

  my ($url, $file, $outfile) = @_;
  if ( -e "$outfile") {
    system("mv $outfile $outfile.old");
  }
  if ( -e "cache/$file") {
    system("cp cache/$file $outfile");
  } else {
    if ($NET_ACCESS) {
      &inform("  downloading ....\n");
      print ("wget --quiet -O cache/$file $url\n");
      system ("wget --quiet -O cache/$file $url");
      sleep 1;
      system ("cp cache/$file $outfile");
    } else {
      &inform("  net disabled ....\n");
      return 0;
    }
  }
  return 1;
}

################################################################################
sub get_caches_info
################################################################################
{
  my $xmlfile;
  local $convert, @tag_stack;
  local $info_terrain, $info_difficulty, $info_hint, $info_type, $info_size;

  $wpt = 0;
  my $parser = new XML::Parser;

  $parser->setHandlers(Start => \&start_caches_info,
                         End => \&end_caches_info,
                        Char => \&data_caches_info,
                     Default => \&default);

  #geocaching.gpx from old geocaching.cz datafile (all czech caches)
  my $xmlfile = "gpx/geocaching.gpx";
  if (-f $xmlfile) {
    $convert = 1;
    $gpx_type = "wpt_is_desc";
    &inform("["); 
    $parser->parsefile($xmlfile);
    &inform("gcz]"); 
  }

  #CZ-Geocaching-traditional.gpx from poi.cz
  $xmlfile = "gpx/CZ-Geocaching-traditional.gpx";
  if (-f $xmlfile) {
    $convert = 0;
    $gpx_type = "name_is_both";
    &inform("["); 
    $parser->parsefile($xmlfile);
    &inform("poi.cz T]"); 
  }  

  #gpx converted from geocaching.com loc files
  opendir(DIRHANDLE, "./gpx") or die "Can't open dirhandle: $! !";
  my @files;

  $convert = 0;
  foreach(readdir(DIRHANDLE)) {
    if (/^.*gpx$/ && $_ ne "geocaching.gpx" && $_ ne "CZ-Geocaching-traditional.gpx") {
      print;
      print "\n";
      $gpx_type = "wpt_is_name";
      &inform("["); 
      $parser->parsefile("gpx/$_");
      &inform(".]"); 
    }
  }
  &inform("\n");
}

################################################################################
sub start_caches_info 
################################################################################
{
  my($parseinst, $element, %attrs) = @_;

  if ($element eq "wpt") {
    $wpt = 1;
    $tag_groundspeak_cache = 0;
    $tag_groundspeak_difficulty = 0;
    $tag_groundspeak_terrain = 0;
    $desc = 0;
    $url = 0;
    $name = 0;
    $guid = "unknown guid";
    $gpx_lat = $attrs{lat};
    $gpx_lon = $attrs{lon};
    $cache_name = "unknown";
    $cache_url = "http://localhost";
    
    $info_terrain = "";
    $info_difficulty = "";
    $info_hint = "";
    $info_type = "";
    $info_size = "";
    $info_guid = "";
     
  } elsif ($element eq "desc") {$desc = 1;}
#nacist parametr groundspeak:cache guid
  elsif ($element eq "url") {$url = 1;}
  elsif ($element eq "name") {$name = 1;}
  elsif ($element eq "type") {$tag_type = 1;}
  elsif ($element eq "urlname") {$tag_urlname = 1;}
  elsif ($element eq "groundspeak:cache") {
    $tag_groundspeak_cache = 1;  
#id="1168489" 
    $info_guid = $attrs{guid};
    print "info_guid $info_guid\n";
#cpWPT="GC1P0P4" 
#available="True" 
#archived="False" 
#cpCoords="N 49&#176;44.744' E 017&#176;18.618'"

  }
  elsif ($element eq "groundspeak:difficulty") {$tag_groundspeak_difficulty = 1;  }
  elsif ($element eq "groundspeak:terrain") {$tag_groundspeak_terrain = 1;}
  elsif ($element eq "groundspeak:encoded_hints") {$tag_groundspeak_encoded_hints = 1;}
  elsif ($element eq "groundspeak:type") {
    my $last = @tag_stack;
#    print "stack: ",$tag_stack[$last - 1],"\n";
    if ($tag_stack[$last - 1] eq "groundspeak:cache") {
      $tag_groundspeak_type = 1;
    }
  
  }
  elsif ($element eq "groundspeak:container") {$tag_groundspeak_container = 1;}

  push @tag_stack, $element;
#  print "!!: @tag_stack, $element; \n";

}

################################################################################
sub end_caches_info 
################################################################################
{
  my( $parseinst, $element ) = @_;
  my ($info_title_text, $info_guid);

  pop(@tag_stack);

  if ($element eq "wpt") {

#    if ($type eq "X" or $size eq "X" or !$terrain or !$difficulty) {
#      ($info_title_text, $info_type, $info_size, $info_guid, $info_hint) = &get_cache_info_from_web("","$cache_desc");
#    } else {
#      $info_type = $type;
#      $info_size = $size;
#      $info_terrain = $terrain;
#      $info_difficulty = $difficulty;
#      $info_guid = $url;
#    }

#    $info_type = $type;
#    $info_size = $size;
#    $info_terrain = $terrain;
#    $info_difficulty = $difficulty;
    
    if ($info_guid eq "") {
      $info_guid = (split ("=", $cache_url))[1];
    }
    
    if ($convert) {
      Encode::from_to($cache_name, "iso-8859-1", "utf8");
    }
     
    $caches{$cache_desc}{wpt}        = $cache_desc;
    $caches{$cache_desc}{lat}        = $gpx_lat;
    $caches{$cache_desc}{lon}        = $gpx_lon;
    $caches{$cache_desc}{name}       = $cache_name;
    $caches{$cache_desc}{url}        = $cache_url;
    $caches{$cache_desc}{type}       = $info_type;
    $caches{$cache_desc}{size}       = $info_size;
    $caches{$cache_desc}{hint}       = $info_hint;
    $caches{$cache_desc}{terrain}    = $info_terrain;
    $caches{$cache_desc}{difficulty} = $info_difficulty;
    $caches{$cache_desc}{guid}       = $info_guid;
    $caches{$cache_desc}{found}      = 0;
  }

  if ($element eq "wpt") {
    $wpt = 0;
    $desc = 0;
    $url = 0;
    $name = 0;
    $tag_urlname = 0;
    $tag_groundspeak_difficulty = 0;
    $tag_groundspeak_terrain = 0;
  }
  if ($element eq "desc") {$desc = 0;}
  elsif ($element eq "url")  {$url = 0;}
  elsif ($element eq "urlname")  {$tag_urlname = 0;}
  elsif ($element eq "name") {$name = 0;}
  elsif ($element eq "type") {$tag_type = 0;}
  elsif ($element eq "groundspeak:difficulty") {$tag_groundspeak_difficulty = 0;}
  elsif ($element eq "groundspeak:terrain") {$tag_groundspeak_terrain = 0;}
  elsif ($element eq "groundspeak:encoded_hints") {$tag_groundspeak_encoded_hints = 0;}
  elsif ($element eq "groundspeak:type") {$tag_groundspeak_type = 0;}
  elsif ($element eq "groundspeak:container") {$tag_groundspeak_container = 0;}
  elsif ($element eq "groundspeak:cache") {$tag_groundspeak_cache = 0;}
}

################################################################################
sub data_caches_info 
################################################################################
{
  my ($parseinst, $data) = @_;

  $data =~ s/\n|\t//g;

  if ($url) {
    $cache_url = $data;
  }

  if ($tag_groundspeak_difficulty) {
    $info_difficulty = $data;
  }
  
  if ($tag_groundspeak_terrain) {
    $info_terrain = $data;
  }

  if ($tag_groundspeak_encoded_hints) {
    $info_hint = $data;
  }

  if ($tag_groundspeak_type) {
    $info_type = $data;
  }
  
  if ($tag_groundspeak_container) {
    $info_size = $data;
  }
  
#########
  if ($desc and $gpx_type eq "wpt_is_desc") {
    $cache_desc = $data;
  }

  if ($name and $gpx_type eq "wpt_is_desc") {
    $cache_name = substr($data, 5);
    $type = substr $data, 0, 1;
    $size = substr($data, 1, 1);
    $terrain = substr $data, 2, 1;
    $difficulty = substr $data, 3, 1;
  }
#########
  if ($name and $gpx_type eq "wpt_is_name") {
    $cache_desc = $data;
  } 

  if ($desc and $gpx_type eq "wpt_is_name") {
    $cache_name = $data;
    $type = "X";
    $size = "X";
    $terrain = 0;
    $difficulty = 0;
  } 
#########
  if ($name and $gpx_type eq "name_is_both") {
    #GCYRJJ CM_Cache [T-1/1,5]
    my @x = split(" ", $data);
    $kk = (scalar @x) - 1;

    $cache_desc = $x[0]; #wpt
    
    $cache_name = "";
    for (my $i = 1; $i < (scalar @x) - 1; $i++) {$cache_name .= $x[$i]." ";}

    $x[$kk] =~ s/\[//g; 
    $x[$kk] =~ s/\]//g; 
    $x[$kk] =~ s/,/\./g; 
    $x[$kk] =~ s/\-/ /g; 
    $x[$kk] =~ s/\// /g; 
    
    my @xx = split(" ", $x[scalar @x - 1]);

    $size = "";
    $type = $xx[0];
    $terrain = $xx[1];
    $difficulty = $xx[2];
    print ">$kk".$x[$koko]."| $cache_desc ($cache_name) $type $terrain $difficulty <\n";
  }
}

################################################################################
sub default 
################################################################################
{
  my ($parseinst, $data) = @_;
}

################################################################################
sub get_gps_info
################################################################################
{
  my $xmlfile = "gps/gps.gpx";
  local $convert;
  die "Cannot find file \"$xmlfile\"" unless -f $xmlfile;
  $wpt = 0;
  my $parser = new XML::Parser;

  $parser->setHandlers(Start => \&start_gps_info,
                         End => \&end_gps_info,
                        Char => \&data_gps_info,
                     Default => \&default);

  &inform("[>>"); 
  $parser->parsefile($xmlfile);
  &inform("GPS<<]"); 
  &inform("\n");
}

################################################################################
sub start_gps_info 
################################################################################
{
  my($parseinst, $element, %attrs) = @_;
  if ($element eq "wpt") {
    $wpt = 1;
    $poi_lat = $attrs{lat};
    $poi_lon = $attrs{lon};
    $poi_name = "unknown";
    $poi_ele = "unknown";
    $poi_sym = "unknown";
  } elsif ($element eq "name") {$name = 1;}
  elsif ($element eq "ele") {$ele = 1;}
  elsif ($element eq "sym") {$sym = 1;}
}

################################################################################
sub end_gps_info 
################################################################################
{
  my($parseinst, $element) = @_;

  if ($element eq "wpt") {
  print "$poi_name\n";
    $gps_poi{$poi_name}{lat}  = $poi_lat;
    $gps_poi{$poi_name}{lon}  = $poi_lon;
    $gps_poi{$poi_name}{name} = $poi_name;
    $gps_poi{$poi_name}{ele}  = $poi_ele;
    $gps_poi{$poi_name}{sym}  = $poi_sym;
    $wpt = 0;
    $name = 0;
    $ele = 0;
    $sym = 0;
  }

  if ($element eq "name") {$name = 0;}
  if ($element eq "ele") {$ele = 0;}
  if ($element eq "sym") {$sym = 0;}
}

################################################################################
sub data_gps_info 
################################################################################
{
  my ($parseinst, $data) = @_;

  $data =~ s/\n|\t//g;

  if ($name) {
    $poi_name = $data;
  }  
  if ($ele) {
    $poi_ele = $data;
  }
  if ($sym) {
    $poi_sym = $data;
  }

}

################################################################################
sub default 
################################################################################
{
  my ($parseinst, $data) = @_;
}

################################################################################
sub caches_along_route
################################################################################
{
  my($cache, $xmlfile) = @_;
  local $convert;
  die "Cannot find file \"$xmlfile\"" unless -f $xmlfile;
  my $flag_path = 0;
  local $path_coordinates;
  my $parser = new XML::Parser;
  $parser->setHandlers(Start => \&start_path,
                         End => \&end_path,
                        Char => \&data_path,
                      );
  $parser->parsefile($xmlfile);
  
  chomp $path_coordinates;

  @path_points = split(" ",$path_coordinates);
    my $obj = Geo::Inverse->new(); # default "WGS84"
  foreach $ii (@path_points) {
    @x = split(",",$ii);
    foreach $i (keys %{$cache}) {
    
      my $lat = $cache->{$i}{lat};
      my $lon = $cache->{$i}{lon};
#print "caches_along_route(): $x[0],$x[1],$lon,$lat";
      my ($faz, $baz, $dist) = $obj->inverse($x[0], $x[1], $lon, $lat);
      if ($dist < 2000) {
        $path_wpt_output{$i}->{lat}  = $cache->{$i}{lat};
        $path_wpt_output{$i}->{lon}  = $cache->{$i}{lon};
        $path_wpt_output{$i}->{name} = $cache->{$i}{name};
        $path_wpt_output{$i}->{wpt}  = $cache->{$i}{wpt};
        $path_wpt_output{$i}->{hint} = $cache->{$i}{hint};
        $path_wpt_output{$i}->{type} = $cache->{$i}{type};
        $path_wpt_output{$i}->{size} = $cache->{$i}{size};
        
      }
    }
    $path_out_file = basename($xmlfile)."_out.";
    &export_gpx(\%path_wpt_output, $path_out_file."gpx", "*");
    &export_kml2(\%path_wpt_output, $path_out_file."kml", "*");
  }
  
}

################################################################################
sub start_path 
################################################################################
{
  my($parseinst, $element, %attrs) = @_;
  if ($element eq "coordinates") {
    $flag_path = 1;
  }
}

################################################################################
sub end_path 
################################################################################
{
  my($parseinst, $element, %attrs) = @_;

  if ($element eq "coordinates") {
    $flag_path = 0;
  }
}

################################################################################
sub data_path
################################################################################
{
  my ($parseinst, $path_data) = @_;
  if ($flag_path) {
    $path_data =~ s/\n|\t//g;
    $path_coordinates .= $path_data." ";
  }
}

################################################################################
sub Transform_WGS84_to_JTSK
################################################################################
{
my ($SD1, $SPREDDES1, $SDES1, $DD1, $DPREDDES1, $DDES1, $ALT1) = @_;

    my $p1 ;
    my $p2 ;
    my $p3 ;
    my $p31 ;
    my $p4 ;
    my $p5 ;
    my $p6 ;
    my $p61 ;
    my $vyska ;
    my $fi_st ;
    my $lambda_st ;
    my $fi ;
    my $lambda ;
    my $e_2 ;
    my $ro_wgs ;
    my $X_wgs ;
    my $Y_wgs ;
    my $Z_wgs ;
    my $wx ;
    my $wy ;
    my $wz ;
    my $wy_wgs ;
    my $wz_wgs ;
    my $x_jtsk ;
    my $y_jtsk ;
    my $z_jtsk ;
    my $b_bess ;
    my $a2_b ;
    my $e_2_bess ;
    my $p ;
    my $theta ;
    my $sint ;
    my $cost ;
    my $t ;
    my $fi_jtsk ;
    my $lambda_jtsk ;
    my $e ;
    my $sinL ;
    my $sinF ;
    my $e_sin_fi ;
    my $dalsi ;
    my $mocnina ;
    my $na ;
    my $cislot ;
    my $nb ;
    my $nc ;
    my $nd ;
    my $U ;
    my $V ;
    my $sin_u_q ;
    my $cos_u_q ;
    my $d_v ;
    my $cos_d_v ;
    my $sin_d_v ;
    my $SinS ;
    my $SinD ;
    my $S ;
    my $D ;
    my $So ;
    my $tan_one ;
    my $tan_two ;
    my $podil ;
    my $ro ;
    my $e_2s ;
    my $X ;
    my $Y ;
    
    my $pi = 3.141592653589;
    my $a_wgs = 6378137;
    my $f_1_wgs = 298.257223563;
    my $a_bess = 6377397.15508;
    my $f_1_bess = 299.152812853;
    my $n = 0.97992470462083;
    my $ro_o = 1298039.00467899;
    my $k = 1.00341916396658;
    my $alfa = 1.00059749837154;
    my $dx = -570.69;
    my $dy = -85.69;
    my $dz = -462.84;
    my $m = -0.000003546;

    $p1 = $SD1;
    $p2 = $SPREDDES1 / 60;
    if (length($SDES1) == 1 ) { $p3 = (($SDES1 / 10) * 60);}
    if (length($SDES1) == 2 ) { $p3 = (($SDES1 / 100) * 60);}
    if (length($SDES1) == 3 ) { $p3 = (($SDES1 / 1000) * 60);}
    $p31 = $p3 / 3600;
    $fi_st = $p1 + $p2 + $p31;
    $fi = $fi_st * $pi / 180;
     
    $p4 = $DD1;
    $p5 = $DPREDDES1 / 60;
    if (length($DDES1) == 1 ) { $p6 = (($DDES1 / 10) * 60);}
    if (length($DDES1) == 2 ) { $p6 = (($DDES1 / 100) * 60);}
    if (length($DDES1) == 3 ) { $p6 = (($DDES1 / 1000) * 60);}
    $p61 = $p6 / 3600;
    $lambda_st = $p4 + $p5 + $p61;
    $lambda = $lambda_st * $pi / 180;
      
    $vyska = $ALT1;

    $e_2 = 1 - ((1 - 1 / $f_1_wgs) ** 2);
    $ro_wgs = $a_wgs / sqrt(1 - $e_2 * ((sin($fi)) ** 2));
    $X_wgs = ($ro_wgs + $vyska) * cos($fi) * cos($lambda);
    $Y_wgs = ($ro_wgs + $vyska) * cos($fi) * sin($lambda);
    $Z_wgs = (((1 - $e_2) * $ro_wgs + $vyska) * sin($fi));
    
    $wx = 4.99821 / 3600 * $pi / 180;
    $wy = 1.58676 / 3600 * $pi / 180;
    $wz = 5.2611 / 3600 * $pi / 180;
    $x_jtsk = $dx + (1 + $m) * ($X_wgs + $wz * $Y_wgs - $wy * $Z_wgs);
    $y_jtsk = $dy + (1 + $m) * (-$wz * $X_wgs + $Y_wgs + $wx * $Z_wgs);
    $z_jtsk = $dz + (1 + $m) * ($wy * $X_wgs - $wx * $Y_wgs + $Z_wgs);
    
    $b_bess = $a_bess - ($a_bess / $f_1_bess);
    $a2_b = (($a_bess ** 2) / ($a_bess - ($a_bess / $f_1_bess)));
    
    $e_2_bess = 1 - ((1 - 1 / $f_1_bess) ** 2);
    $p = sqrt((($x_jtsk) ** 2) + (($y_jtsk) ** 2));
    
    $theta = atan(($a_bess * $z_jtsk) / (($a_bess - ($a_bess / $f_1_bess)) * $p));
    
    $sint = sin($theta);
    $cost = cos($theta);
    $t = ($z_jtsk + ($e_2_bess * $a2_b * $sint ** 3)) / ($p - ($e_2_bess * $a_bess * $cost ** 3));

    $fi_jtsk = atan($t);
    $lambda_jtsk = 2 * (atan($y_jtsk / ($p + $x_jtsk)));
        
    $e = sqrt($e_2_bess);
    
    $sinL = sin($lambda_jtsk);
    $sinF = sin($fi_jtsk);
    $e_sin_fi = $e * $sinF;
    $dalsi = (1 - $e_sin_fi) / (1 + $e_sin_fi);
    $mocnina = ($e / 2);
    $na = $dalsi ** $mocnina;
    $cislot = ($fi_jtsk / 2) + ($pi / 4);
    $nb = tan($cislot);
    $nc = $na * $nb;
    $nd = $nc ** $alfa;
        
    $U = 2 * (atan($k * $nd)) - ($pi / 2);
    
    $V = $alfa * ($lambda_jtsk + ((53 / 3) * ($pi / 180)));
    
    $sin_u_q = sin(1.04216856379752);
    $cos_u_q = cos(1.04216856379752);
    $d_v = ($alfa * ((42.5 * $pi) / 180)) - $V;
    $cos_d_v = cos($d_v);
    $sin_d_v = sin($d_v);
    
    $SinS = (($sin_u_q * sin($U)) + ($cos_u_q * cos($U) * $cos_d_v));
    $S = atan($SinS / (sqrt(1 - $SinS ** 2)));
    $SinD = $sin_d_v * cos($U) / cos($S);

    $D = atan($SinD / (sqrt(1 - $SinD ** 2)));
    
    $So = (78.5 * $pi) / 180;
    $tan_one = tan(($So / 2) + ($pi / 4));
    $tan_two = tan(($S / 2) + ($pi / 4));
    $podil = ($tan_one / $tan_two) ** $n;
            
    $ro = $ro_o * $podil;
    
    $eps = $n * $D;
    
    $YDes = $ro * sin($eps) * (-1);
    $XDes = $ro * cos($eps) * (-1);
        
    return $XDes, $YDes;
}

################################################################################
sub convert_coord
################################################################################
{
  my ($coord) = @_;

  $d = floor($coord);
  $dd = $coord - $d;
  $m = floor($dd * 60.0);
  $dm = floor((($dd * 60.0) - $m) * 1000);
  $s = (($dd * 60.0) - $m) * 60;
#  print "$coord: $d,  $dd, $m, $dm, $s\n";
  
  return $d, $dd, $m, $dm, $s;
} 

################################################################################
sub get_cache_type_string()
################################################################################
{
  my ($type) = @_;
  if ($type eq "M") { 
    return "Multi-Cache";
  } elsif ($type eq "T") {
    return "Traditional Cache";
  } elsif ($type eq "U") {
    return "Unknown Cache";
  } elsif ($type eq "V") {
    return "Virtual Cache";
  } elsif ($type eq "E") {
    return "Event Cache";
  } elsif ($type eq "W") {
    return "Webcam Cache";
  } else {
    return "Error Cache";
  }
}

################################################################################
sub get_cache_size_string()
################################################################################
{
  my ($type) = @_;
  if ($type eq "M") {
    return "Micro";
  } elsif ($type eq "S") {
    return "Small";
  } elsif ($type eq "R") {
    return "Regular";
  } elsif ($type eq "L") {
    return "Large";
  } else {
    return "Not Chosen";
  }
}

################################################################################
################################################################################
################################################################################
## EXPORT FUNCTIONS                                                           ##
################################################################################
################################################################################
################################################################################

sub round {
    my($number) = shift;
    return int($number + .5);
}

################################################################################
sub draw_caches
################################################################################
{
  my ($cache, $image_file) = @_;

  GD::Image->trueColor(1);
  $im = new GD::Image("mapa.png") || die;
  $m_image = new GD::Image("m.png") || die;
  $t_image = new GD::Image("t.png") || die;
  $u_image = new GD::Image("u.png") || die;
  $w_image = new GD::Image("w.png") || die;
  $v_image = new GD::Image("v.png") || die;
  $e_image = new GD::Image("e.png") || die;
  $x_image = new GD::Image("x.png") || die;

  $factor_x = $map_size_x / abs($map_x2 - $map_x1);
  $factor_y = $map_size_y / abs($map_y2 - $map_y1);

  foreach $i (keys %{$cache}) {
    $cx = $cache->{$i}{lon};
    $cy = $cache->{$i}{lat};

    $sx = &round(abs($cx - $map_x1) * $factor_x);
    $sy = &round(abs($cy - $map_y1) * $factor_y);

    if ($cx < $map_x1 or $cx > $map_x2) {next;}
    if ($cy < $map_y2 or $cy > $map_y1) {next;}

    if ($cache->{$i}{type} eq "M") {
      $im->copy($m_image,$sx,$sy,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "T") {
      $im->copy($t_image,$sx,$sy,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "U") {
      $im->copy($u_image,$sx,$sy,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "V") {
      $im->copy($v_image,$sx,$sy,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "E") {
      $im->copy($e_image,$sx,$sy,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "W") {
      $im->copy($w_image,$sx,$sy,0,0,15,15);
    } else {
      $im->copy($x_image,$sx,$sy,0,0,15,15);
    }
  }
  open(DATA,">$image_file") or die;
  print DATA $im->png;
  close(DATA);
}

################################################################################
sub draw_cachesx
################################################################################
{
  my ($cache, $image_file) = @_;

  $image_file = "krovak.png";

  GD::Image->trueColor(1);
  $im = new GD::Image("mapa.png.hzs") || die;
  $m_image = new GD::Image("m.png") || die;
  $t_image = new GD::Image("t.png") || die;
  $u_image = new GD::Image("u.png") || die;
  $w_image = new GD::Image("w.png") || die;
  $v_image = new GD::Image("v.png") || die;
  $e_image = new GD::Image("e.png") || die;

  $map_size_x = 1035;
  $map_size_y = 648;


  my $map_x1 =  295568.98554729 ;
  my $map_x2 =  767884.983832656 ;
  my $map_y1 =  935348.864799078 ;
  my $map_y2 = 1226731.2675081;

#  $map_x1 =  432115.016167344;
#  $map_x2 =  904431.01445271  ;
#  $map_y1 =  935348.864799078 ;
#  $map_y2 = 1226731.2675081  ;


  $factor_x = $map_size_x / abs($map_x2 - $map_x1);
  $factor_y = $map_size_y / abs($map_y2 - $map_y1);

  $fxmin = 1000000 ;
  $fxmax = 0  ;
  $fymin = 1000000 ;
  $fymax = 0 ;
  
  foreach $i (keys %{$cache}) {

    $cx = $cache->{$i}{lon};
    $cy = $cache->{$i}{lat};
    
    if ($cx == "0" and $cy == "0") {next;}
    if ($cx eq "" and $cy eq "") {next;}
    
    my ($d, $dd, $m, $dm, $s) = &convert_coord($cx);
    my ($d2, $dd2, $m2, $dm2, $s2) = &convert_coord($cy);

#    my ($d, $dd, $m, $dm, $s) = &convert_coord(50);
#    my ($d2, $dd2, $m2, $dm2, $s2) = &convert_coord(17);

    my ($jtsk_x, $jtsk_y) = &Transform_WGS84_to_JTSK($d2, $m2, $dm2, $d, $m, $dm, 1);

    my $fx = -$jtsk_y;
    my $fy = -$jtsk_x;

#    print "$i vysledek = $fx, $fy ($cx,$cy)\n";

    $osa = 600000;
    if ($fx <= $osa) {$fx = $osa + ($osa - $fx);}
    elsif ($fx > $osa) {$fx = $osa - ($fx - $osa);}

    if ($fx < $fxmin) {$fxmin = $fx;}
    if ($fx > $fxmax) {$fxmax = $fx;}
    if ($fy < $fymin) {$fymin = $fy;}
    if ($fy > $fymax) {$fymax = $fy;}
    

#   $fx = abs(&round($fx))-220;
#   $fy = abs(&round($fy))-2090;

#    print "($fx - $map_x1) ",($fx - $map_x1),"\n";

    $fx = &round(abs($fx - $map_x1) * $factor_x);

    $fy = &round(abs($fy - $map_y1) * $factor_y);

#    print "$i vysledek po uprave = $fx, $fy\n";

    if ($cache->{$i}{type} eq "M") {
      $im->copy($m_image,$fx-7,$fy-7,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "T") {
      $im->copy($t_image,$fx-7,$fy-7,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "U") {
      $im->copy($u_image,$fx,$fy,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "V") {
      $im->copy($v_image,$fx,$fy,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "E") {
      $im->copy($e_image,$fx,$fy,0,0,15,15);
    } elsif ($cache->{$i}{type} eq "W") {
      $im->copy($w_image,$fx,$fy,0,0,15,15);
    }
  }
  
  open(DATA,">$image_file") or die;
  print DATA $im->png;
  close(DATA);
}

################################################################################
sub export_html
################################################################################
{
  my ($cache, $image_file) = @_;
  my $wpt, $out;
  my $row_ = "even";
  my $name;
  
  $out = "<html>\n";
  $out .= "<head>\n";
  $out .= "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>\n";
  $out .= "<script src='sorttable.js'></script>\n";
  $out .= "<title>kuk</title>\n";
  $out .= "<style type='text/css'>\n";
  $out .= "th {\n";
  $out .= "background-color: #CCCC99;\n";
  $out .= "}\n";
  $out .= "tr.even {\n";
  $out .= "background-color: #ECECCC;\n";
  $out .= "}\n";
  $out .= "tr.odd {\n";
  $out .= "background-color: white;\n";
  $out .= "}\n";
  $out .= "</style>\n";
  $out .= "</head>\n";
  $out .= "<body>\n";

  $out .= "<h1>Geocaching statistics</h1>\n";
  $out .= "<h2>Maps</h2>\n";
  $out .= "<table style='margin: 0 auto;width:50%'>\n";
  $out .= "<tr>\n";
  $out .= "<td><a href='all.png'><img width='100%' src='all.png' alt='all caches'></a></td>\n";
  $out .= "<td><a href='my.png'><img width='100%' src='my.png' alt='my caches'></a></td>\n";
  $out .= "</tr>\n";
  $out .= "</table>\n";
  $out .= "\n";

  $out .= "<h2>Year Graphs</h2>\n";
  $out .= "<table style='margin: 0 auto;width:50%'>\n";
 
  my @y_c = &years_cached();
  print @y_c;
  foreach $y (@y_c) {
    $out .= "<tr>\n";
    $out .= "<td><a href='graphmonthly$y.png'><img width='50%' src='graphmonthly$y.png' alt='monthly finds graph for $y'></a></td>\n";
    $out .= "</tr>\n";
  }

  $out .= "</table>\n";

  $out .= "<h2>Stats</h2>\n";
  $out .= &day_stats();

  $out .= "<h2>Google earth maps old</h2>\n";
  $out .= "<ul>\n";
  $out .= "<li><a href='my.kml'>Caches found by me (kml)</a></td>\n";
  $out .= "<li><a href='rest.kml'>The Rest (kml)</a></td>\n";
  $out .= "<li><a href='rest.kml'>All known caches (kml)</a></td>\n";
  $out .= "</ul>\n";

  $out .= "<h2>Google earth maps</h2>\n";
  $out .= "<ul>\n";
  $out .= "<li><a href='kml-f.kml'>Caches found by me (kml)</a></td>\n";
  $out .= "<li><a href='kml-n.kml'>The Rest (kml)</a></td>\n";
  $out .= "<li><a href='kml-a.kml'>All known caches (kml)</a></td>\n";
  $out .= "<li><a href='kml-t.kml'>All tradicional caches (kml)</a></td>\n";
  $out .= "</ul>\n";

  $out .= "<h2>GPX</h2>\n";
  $out .= "<ul>\n";
  $out .= "<li><a href='gpx-f.gpx'>Caches found by me (gpx)</a></td>\n";
  $out .= "<li><a href='gpx-n.gpx'>The Rest (gpx)</a></td>\n";
  $out .= "<li><a href='gpx-a.gpx'>All known caches (gpx)</a></td>\n";
  $out .= "<li><a href='gpx-t.gpx'>All tradicional caches (gpx)</a></td>\n";
  $out .= "</ul>\n";


  $out .= "<h2>Caches</h2>\n";

  $out .= "<table class='sortable' id='unique_id1'>\n";

  $out .= "<th>#</th>\n";
  $out .= "<th>Waypoint</th>\n";
  $out .= "<th>Type</th>\n";
  $out .= "<th>Name</th>\n";
  $out .= "<th>Date</th>\n";
  $out .= "<th>Lat</th>\n";
  $out .= "<th>Lon</th>\n";

  foreach $wpt (keys %{$cache}) {

  print "export_html():html $wpt:",$cache->{$wpt}{found},"\n";

    if (!$cache->{$wpt}{found}) {next;}

    $la = ($cache->{$wpt}{lat} - floor($cache->{$wpt}{lat})) * 60.0;
    $lo = ($cache->{$wpt}{lon} - floor($cache->{$wpt}{lon})) * 60.0;
 
    $la = sprintf("%.4f", $la);
    $lo = sprintf("%.4f", $lo);
     
    $la = floor($cache->{$wpt}{lat})."&deg;".$la."'";
    $lo = floor($cache->{$wpt}{lon})."&deg;".$lo."'";

    if ($row_ eq "even") {$row_ = "odd";} else {$row_ = "even";} 
    $out .= "<tr class='$row_'>";
    $c = $all_caches_found - $cache->{$wpt}{my_id};
    $out .= "<td>".++$c."</td>";
    $out .= "<td>".$cache->{$wpt}{wpt}."</td>";
    $out .= "<td>$cache->{$wpt}{type}</td>";
#    if (length($cache->{$wpt}{name}) > 9) {$name = trim(substr($cache->{$wpt}{name}, 9));}
    $name = $cache->{$wpt}{name};
    $out .= "<td><a href='http://www.geocaching.com/seek/cache_details.aspx?guid=".$cache->{$wpt}{guid}."'>".$name."</a></td>";
    $out .= "<td>".$cache->{$wpt}{date}."</td>";
    $out .= "<td>$la</td>";
    $out .= "<td>$lo</td>";
    $out .= "</tr>\n";
  }
  $out .= "</table>\n";
  $out .= "</body></html>\n";
  open(DATA,">$image_file") or die;
  print DATA $out;
  close DATA;
}

################################################################################
sub export_cachemap
################################################################################
{
  my ($cache, $image_file) = @_;
  my $wpt, $la, $lo, $out;
  my $i = 0;
 
  open(DATA,">$image_file") or die;
  $out = "<html>\n";
  $out .= "<head>\n";
  $out .= "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>\n";
  $out .= "<div id='map'>\n";

  $map_size_x = 600;
  $map_size_y = 200;

  $factor_x = $map_size_x / abs($map_x2 - $map_x1);
  $factor_y = $map_size_y / abs($map_y2 - $map_y1);
  
  foreach $wpt (keys %{$cache}) {
    $i++;
#<a href="http://www.geocaching.cz/Cachemapa.aspx?source=small&amp;x=227&amp;y=149&amp;D=c67c1df2-0fc0-4ea9-a170-2c5e0dd76004">

    $cx = $cache->{$wpt}{lon};
    $cy = $cache->{$wpt}{lat};

    $la = &round(abs($cx - $map_x1) * $factor_x);
    $lo = &round(abs($cy - $map_y1) * $factor_y);

   $out .= "<img src='t.png' style='position:absolute; left: ".$la."px; top: ".$lo."px; z-index:$i;' alt='".$cache->{$wpt}{name}."' title='".$cache->{$wpt}{name}."'>\n";
#</a>
  }
  $out .= "</div>\n";
  print DATA $out;
  close DATA;
}

################################################################################
sub export_kml2
################################################################################
{
  my ($cache, $image_file, $export_type) = @_;
  my $sx, $sy;
  my $type;
  
  my $output = new IO::File(">$image_file");
  binmode($output, ":utf8");
  my $writer = new XML::Writer(OUTPUT => $output, UNSAFE => 1);

  $writer->xmlDecl("utf-8");
  $writer->startTag("kml", "xmlns" => "http://earth.google.com/kml/2.1");
  $writer->startTag("Document");

  $writer->raw($kml_style);
         
  foreach $i (keys %{$cache}) {
    $proceed = 0;

    if ($export_type eq $type || $export_type eq "*" || $export_type eq "X" || $export_type eq "!") {
    
      if ($export_type eq "X" and $cache->{$i}{found} != 1) {next;}
      if ($export_type eq "!" and $cache->{$i}{found} == 1) {next;}
      $sx = $cache->{$i}{lon};
      $sy = $cache->{$i}{lat};
      $kml_type = $cache->{$i}{type};
      $kml_size = $cache->{$i}{size};
      &debug("cache $i $what\n");
      &debug("export_kml(): kml $i $sx,$sy\n");
      $writer->startTag("Placemark");
      $writer->dataElement("name", $cache->{$i}{wpt});

      $writer->startTag("description");
      $description = "<b>$cache->{$i}{name}</b> <i>$cache->{$i}{wpt}</i><br>";
      $description .= "$kml_type/$kml_size <br>";
      $description .= "$sx $sy<br>";
      $description .= "http://www.geocaching.com/seek/cache_details.aspx?pf=y&wp=".$cache->{$i}{wpt}."&decrypt=y&log=y&numlogs=6";
      $writer->cdata($description);
      $writer->endTag("description"); 

      $writer->dataElement("extrude", "1");  
      if ($kml_type eq "T") {$writer->dataElement("styleUrl","#t");}
      if ($kml_type eq "M") {$writer->dataElement("styleUrl","#m");}
      if ($kml_type eq "U") {$writer->dataElement("styleUrl","#u");}
      if ($kml_type eq "V") {$writer->dataElement("styleUrl","#v");}
      if ($kml_type eq "E") {$writer->dataElement("styleUrl","#e");}
      if ($kml_type eq "W") {$writer->dataElement("styleUrl","#w");}
      $writer->startTag("Point");
      $writer->dataElement("coordinates", "$sx,$sy");
      $writer->endTag("Point");
      $writer->endTag();
      $writer->raw("\n");
    }
  }  
  $writer->endTag("Document");
  $writer->endTag("kml");
  $writer->end();
  $output->close();
}

################################################################################
sub export_gpx
################################################################################
{
  my ($cache, $image_file, $export_type) = @_;
  my $sx, $sy;
  my $type;
  my $size;
  my $hint;
  my $date;
  my $name;
  my $terrain;
  my $difficulty;
  my $found;

  my $output = new IO::File(">$image_file");
  binmode($output, ":utf8");
  my $writer = new XML::Writer(OUTPUT => $output, UNSAFE => 1);

#<?xml version="1.0" encoding="iso-8859-1" standalone="yes"?>
#  $writer->xmlDecl("iso-8859-1", "yes");
  $writer->xmlDecl("UTF-8", "yes");

  $writer->startTag("gpx", "version" => "1.0",
                           "creator" => "walley", 
                           "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
                           "xmlns" => "http://www.topografix.com/GPX/1/0",
                           "xsi:schemaLocation" => "http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd",
  );                           

  $writer->raw("\n");
  if ($export_type eq "X") {
    $writer->dataElement("name", "My Finds Pocket Query"); $writer->raw("\n");
    $writer->dataElement("desc", "Geocache file generated by Groundspeak");$ writer->raw("\n");
    $writer->dataElement("author", "Groundspeak"); $writer->raw("\n");
    $writer->dataElement("email", "contact@groundspeak.com"); $writer->raw("\n");
  }
#  <name>My Finds Pocket Query</name>
#  <desc>Geocache file generated by Groundspeak</desc>
#  <author>Groundspeak</author>
#  <email>contact@groundspeak.com</email>


  ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);           
  $writer->dataElement("time", 1900+$year."-".++$mon."-".$mday."T$hour:$min:$sec.111");

  $writer->dataElement("keywords", "cache, geocache, groundspeak");
#  <keywords>cache, geocache, groundspeak</keywords>

  $writer->raw('<bounds minlat="0.00000000" maxlat="51.05505000" minlon="0.00000000" maxlon="18.85071667"/>');

  foreach $i (keys %{$cache}) {
    if ($export_type eq $type || $export_type eq "*" || $export_type eq "X" || $export_type eq "!") {

      $sx = $cache->{$i}{lat};
      $sy = $cache->{$i}{lon};
      $type = $cache->{$i}{type};
      $size = $cache->{$i}{size};
      $hint = $cache->{$i}{hint};
      $name = $cache->{$i}{name};
      $date = $cache->{$i}{date};
      $terrain = $cache->{$i}{terrain};
      $difficulty = $cache->{$i}{difficulty};
      $found = $cache->{$i}{found};

      if ($export_type eq "X" and $found != 1) {next;}
      if ($export_type eq "!" and $found == 1) {next;}

      $writer->raw("\n");
      $writer->startTag("wpt", "lat" => "$sx", "lon" => "$sy");$writer->raw("\n");
#    <time>2005-07-16T00:00:00.0000000-07:00</time>
      $writer->dataElement("time", "2005-07-16T00:00:00.0000000-07:00");$writer->raw("\n");
      $writer->startTag("name");
      $writer->cdata($type.$size."-".$cache->{$i}{name});
      $writer->endTag("name"); $writer->raw("\n");

      $writer->dataElement("desc", $cache->{$i}{wpt});

      $writer->startTag("url");
      $writer->cdata("Cache Details");
      $writer->endTag();$writer->raw("\n");
  
      $writer->startTag("urlname");
      $writer->cdata("http://www.geocaching.com/seek/cache_details.aspx?wp=$cache->{$i}{wpt}");
      $writer->endTag();$writer->raw("\n");

#      $writer->startTag("extensions", "type" => "guid", "minOccurs" => "0");
#      $writer->cdata("$cache->{$i}{guid}");
#      $writer->endTag();$writer->raw("\n");
#      $writer->startTag("extensions", "type" => "found", "minOccurs" => "0");
#      $writer->cdata($cache->{$i}{found});
#      $writer->endTag();$writer->raw("\n");

      $writer->dataElement("type", "geocache|$type");

      if ($found) {
        $writer->dataElement("sym", "Geocache Found");
      } else {
        $writer->dataElement("sym", "Geocache");
      }  
      $writer->raw("\n");
      
      $name =~ s/\&/a/g;

      $writer->raw("
<groundspeak:cache id=\"65365\" available=\"True\" archived=\"False\" xmlns:groundspeak=\"http://www.groundspeak.com/cache/1/0\">
  <groundspeak:placed_by>$author</groundspeak:placed_by>
  <groundspeak:owner id=\"35726\">$owner</groundspeak:owner>
\n");

      $writer->raw("  "); $writer->startTag("groundspeak:name");$writer->cdata($name);$writer->endTag();   $writer->raw("\n");
      $writer->raw("  "); $writer->startTag("groundspeak:type");$writer->cdata(&get_cache_type_string($type));$writer->endTag();   $writer->raw("\n");
      $writer->raw("  "); $writer->startTag("groundspeak:container");$writer->cdata(&get_cache_size_string($size));$writer->endTag();   $writer->raw("\n");

      $writer->raw("
  <groundspeak:difficulty>$difficulty</groundspeak:difficulty>
  <groundspeak:terrain>$terrain</groundspeak:terrain>
  <groundspeak:country>$country</groundspeak:country>
  <groundspeak:state>$state</groundspeak:state>
  <groundspeak:short_description html=\"False\"></groundspeak:short_description>
  <groundspeak:long_description html=\"False\"></groundspeak:long_description>\n");

      $writer->startTag("groundspeak:encoded_hints");$writer->cdata($hint);$writer->endTag();$writer->raw("\n");


if ($found) {
      $writer->startTag("groundspeak:logs");
      $writer->raw("
        <groundspeak:log id=\"14308167\">");

      $yyyy = substr($date, 0, 4);
      $mm = substr($date, 4, 2);
      $dd = substr($date, 6, 2);
      $log_date = "$yyyy-$mm-$dd"."T07:00:00\n";
      $writer->dataElement("groundspeak:date", $log_date);

      $writer->raw("          <groundspeak:type>Found it</groundspeak:type>
          <groundspeak:finder id=\"551787\">_walley_</groundspeak:finder>
          <groundspeak:text encoded=\"False\">found it</groundspeak:text>
        </groundspeak:log>\n");
     $writer->endTag("groundspeak:logs");
}
      $writer->raw("      <groundspeak:travelbugs></groundspeak:travelbugs>
</groundspeak:cache>\n");
      $writer->endTag();
      $writer->raw("\n");
    }  
  }

  $writer->endTag("gpx");
  $writer->end();
  $output->close();
}

################################################################################
sub years_cached
################################################################################
{
  my @caching_years;
  
  foreach $k (sort(keys %graph_data)) {
    my $key_day = substr($k,6,4);
    my $key_month = substr($k,4,2);
    my $key_year = substr($k,0,4);
    if (grep {/^($key_year)/} @caching_years) {
    } else {
      push @caching_years, $key_year;
    }
  }
  return @caching_years;
}


################################################################################
sub date_to_days
################################################################################
{
  my $k = @_[0];

  my $key_day = substr($k,6,4);
  my $key_month = substr($k,4,2);
  my $key_year = substr($k,0,4);

  return $key_day + $key_month * 31 + $key_year * 365;
}

################################################################################
sub day_stats
################################################################################
{
  my %monthly;
  my $days_cached;
  my $c_cached, $max_c_cached;
  my $days_cached = 0;
  my @sorted_dates = sort(keys %graph_data);
  my $min_date = min(@sorted_dates);
  my $max_date = max(@sorted_dates);

  $c_cached = $max_c_cached = 1;

  my $old_k;
  my $old_m = 0;
  my $max_month = 0;

  foreach $k (@sorted_dates) {
    $days_cached++;

    if ($k == ($old_k + 1)) {
      $c_cached++;
    } else {
      if ($c_cached > $max_c_cached) {$max_c_cached = $c_cached;}
      $c_cached = 1;
    }
    $old_k = $k;

    my $key_day = substr($k,6,4);
    my $key_month = substr($k,4,2);
    my $key_year = substr($k,0,4);

    if ($key_month != $old_m) {
      $old_m = $key_month;
      $months_max{$key_year.$key_month} = $max_month; 
      $max_month = 0;
    } else {
      $max_month += $graph_data{$k};
    }

#    $monthly{$key} += $graph_data{$k};
#    if (grep {/^($key_year)/} @caching_years) {
#    } else {
#      push @caching_years, $key_year;
#    }
#    $monthly{$key_year}{$key_month} += $graph_data{$k};
  }
  
  print Dumper(%months_max);

  print &date_to_days($max_date);
  print &date_to_days($min_date);
  print "\n"; 

  my $all_days = &date_to_days($max_date) - &date_to_days($min_date);
#fixme calculation is wrong
  if ($all_days == 0) {$all_days = 1;}
  my $cache_ratio = $all_caches_found / $all_days;
#fixme not right, asuming 365 days in year

  my $s = "
  <table>
  <tr><td>date started:</td><td> min:$min_date </td></tr>
  <tr><td>caches found:</td><td> $all_caches_found  </td></tr>
  <tr><td>found (approx.) cache per day:</td><td> $cache_ratio </td></tr>  
  <tr><td>days caching:</td><td> $days_cached  </td></tr>
  <tr><td>consecutive days caching:</td><td> $max_c_cached </td></tr>
  <tr><td>  last day:</td><td>$max_date</td></tr>
";

  $s .= "<tr><td>month max:</td><td>".max(sort(values %months_max))."</td></tr>";
  $s .="   </table>
  \n";
  return $s;
}

################################################################################
sub export_graph
################################################################################
{

&day_stats();
#die;

  my %monthly;

  my($image_file, $type) = @_;

  foreach $k (sort(keys %graph_data)) {
    my $key_month = substr($k,4,2);
    my $key_year = substr($k,0,4);
    $monthly{$key} += $graph_data{$k};
    if (grep {/^($key_year)/} @caching_years) {
    } else {
      push @caching_years, $key_year;
    }
    $monthly{$key_year}{$key_month} += $graph_data{$k};
  }

  print "caching years\n",Dumper(@caching_years);

  my $data = GD::Graph::Data->new();
  foreach $year (@caching_years) {
    print "Doing year $year\n";
    my @olddata, @d1, @d2;

    $data->reset();
    reset 'd1';
    reset 'd2';
    reset 'olddata';
    
    for $k (1..12) {
      $kk = sprintf("%02d",$k);
      push @d1, $kk;
      push @d2, $monthly{$year}{$kk};
    }
  
    push @olddata, \@d1;
    push @olddata, \@d2;
    $data->copy_from(\@olddata);

    print Dumper(@olddata);
    print Dumper($data);

    my $fname = "$image_file$year.png";
    print "Exporting to $fname\n";
    open(FILE, ">$fname") or die; 
    binmode FILE;
    my $mygraph = GD::Graph::bars->new(640, 480) or die $mygraph->error;
    $mygraph->set(
        x_label     => 'Date',
        y_label     => 'Number of finds',
        title       => 'Daily finds',
        transparent => 1,
    ) or warn $mygraph->error;

    my $myimage = $mygraph->plot($data) or die $mygraph->error;

    print FILE $myimage->png;
    close(FILE);
  }
}


################################################################################
sub show_caches
################################################################################
{
  my ($cache, $gps_pois, $image_file, $export_type) = @_;
  my $sx, $sy;
  my $type;
  my $size;
  my $hint;
  my $name;

# add a new type of column that reverses the text that's in a scalar
  Gtk2::SimpleList->add_column_type(
    'ralacs', 	# think about it for a second...
    type     => 'Glib::Scalar',      
    renderer => 'Gtk2::CellRendererText',   
    attr     => sub {
                  my ($tree_column, $cell, $model, $iter, $i) = @_;
		  my $info = $model->get ($iter, $i);
		  $info = join('',reverse(split('', $info || '' )));
		  $cell->set (text => $info);
                } 
  );

# add a new type of column that sums up an array reference
  Gtk2::SimpleList->add_column_type(
  'sum_of_array',
	type     => 'Glib::Scalar',      
	renderer => 'Gtk2::CellRendererText',   
	attr     => sub {
		my ($tree_column, $cell, $model, $iter, $i) = @_;
		my $sum = 0;
		my $info = $model->get ($iter, $i);
		foreach (@$info) {
		  $sum += $_;
		}
		$cell->set (text => $sum);
	} 
  );

  my $win = Gtk2::Window->new;
  $win->set_title ('Buzumbura');
  $win->set_border_width (6);
  $win->set_default_size (700, 600);
  $win->signal_connect (delete_event => sub { Gtk2->main_quit; });

  my $hbox = Gtk2::HBox->new (0, 6);
  $win->add ($hbox);

  my $scwin = Gtk2::ScrolledWindow->new;
  $hbox->pack_start ($scwin, 1, 1, 0);
  $scwin->set_policy (qw/automatic automatic/);

# create a simple list widget with one of each column type
  my $slist = Gtk2::SimpleList->new (
    'Found'  => 'bool',
    'WPT'    => 'text',
    'Name'   => 'text',
    'lat'    => 'double',
    'lon'    => 'double',
    'size'   => 'text',
    'Type'   => 'text',
    'GPS type'  => 'text',
  );
  $scwin->add ($slist);

  my $vbox = Gtk2::VBox->new (0, 6);
  $hbox->pack_start($vbox, 0, 1, 0);

# now lets create some buttons to push, pop, shift, and unshift, ...
  my $btn;
  my $tooltips = Gtk2::Tooltips->new;
  foreach (
		[ 'GPS', 'nt' ],
		[ 'GC', 'Fill the list with data using an array assignment' ],
		[ 'Delete', 'Delete the ~middle element from the list' ],
		[ 'Push', 'Push a row onto the list' ],
		[ 'Pop', 'Pop a row off of the list' ],
		[ 'Unshift', 'Unshift a row onto the list' ],
		[ 'Shift', 'Shift a row off of the list' ],
		[ 'Splice 1', 'splice @data, 2, 2, (5 new items)' ],
		[ 'Splice 2', 'splice @data, 2, 0, (5 new items)' ],
		[ 'Splice 3', 'splice @data, 2, 2' ],
		[ 'Splice 4', 'splice @data, 2' ],
		[ 'Change 1', 'Change all of the columns of row 1 with an array ref assignment' ],
		[ 'Change 2', 'Change all of the columns of row 1 with array element assignments' ],
		[ 'Change 3', 'Change the first column of row 1 with a scalar assignment, most useful with single column lists' ],
		[ 'Delete', 'Delete the ~middle element from the list' ],
		[ 'Empty', 'Delete all rows from the list with an empty array assignement' ],
		[ 'Fill', 'Fill the list with data using an array assignment' ],
		[ 'Many', 'Push and Unshift several data element onto the list' ],
		[ 'Dump List', 'Dump list data to stdout' ],
		[ 'Dump Sel', 'Dump index of selected item(s)' ],
	) {
    $btn = Gtk2::Button->new ($_->[0]);
    $btn->signal_connect (clicked => \&btn_clicked, $_->[0]);
    $tooltips->set_tip ($btn, $_->[1]);
    $vbox->pack_start($btn, 0, 1, 0);
  }
  $tooltips->enable;

  # here's a little optionmenu to set the list's selection mode.
  my $opt = Gtk2::OptionMenu->new;
  my $menu = Gtk2::Menu->new;
  foreach (qw/none single browse multiple/) {
    my $item = Gtk2::MenuItem->new ($_);
    $item->signal_connect (activate => sub {
      $slist->get_selection->set_mode ($_[1]);
    }, $_);
    $item->show;
    $menu->append ($item);
  }

$opt->set_menu ($menu);
$opt->set_history (1);
$vbox->pack_start ($opt, 0, 0, 0);
$tooltips->set_tip ($opt, 'set the selection mode for the list');

# toggle the editable-ness of column 0
my $chk = Gtk2::CheckButton->new ('editable');
$chk->set_active (1);
$chk->signal_connect (toggled => sub {
		$slist->set_column_editable (0, $_[0]->get_active);
		});
$vbox->pack_start ($chk, 0, 0, 0);
$tooltips->set_tip ($chk, 'set whether column zero\'s text is editable');


# toggle the reorderable-ness of the view
$chk = Gtk2::CheckButton->new ('reorderable');
$chk->set_active (0);
$chk->signal_connect (toggled => sub {
		$slist->set_reorderable ($_[0]->get_active);
		});
$vbox->pack_start ($chk, 0, 0, 0);
$tooltips->set_tip ($chk, 'set whether the list is reorderable');

# finally, a button to end it all
$btn = Gtk2::Button->new_from_stock ('gtk-quit');
$btn->signal_connect (clicked => sub  { Gtk2->main_quit; });
$vbox->pack_end($btn, 0, 1, 0);

$slist->signal_connect (row_activated => sub {
  my ($slist, $path, $column) = @_;
  my $row_ref = $slist->get_row_data_from_path ($path);
  ($p, $f) = $slist->get_cursor;
  my $row = $path->to_string();
  print "$row\n";
  print Dumper ($row_ref).Dumper ($path);

});

# just for shorthand
my $dslist = $slist->{data};
my $op_count = 0;

my @pixbufs;
foreach (qw/gtk-ok gtk-cancel gtk-quit gtk-apply gtk-clear gtk-delete gtk-execute gtk-dnd/)
{
  push @pixbufs, $win->render_icon ($_, 'menu');
}
# so some will be blank
push @pixbufs, undef;

sub btn_clicked
{
  my ($button, $op) = @_;

  if( $op eq 'Push' )	{
	push @$dslist, [ 'pushed',5, 5.5, 0, 'scalar pushed', 
		$pixbufs[rand($#pixbufs+1)], 'scalar pushed', 
		[5, 6, 7], '' ];
}
elsif( $op eq 'Pop' )	{
	pop @$dslist;
}
elsif( $op eq 'Unshift' )	{
	unshift @$dslist, [ 'unshifted', 6, 6.6, 1, 'scalar unshifted', 
		$pixbufs[rand($#pixbufs+1)], 'scalar unshifted',
		[6, 7, 8], '<span color="green">unshift</span>' ];
}
elsif( $op eq 'Shift' )	{
	shift @$dslist;
}
elsif( $op eq 'Change 1' )	{
	$dslist->[0] = [ 'changed1', 7, 7.7, 0, 'scalar changed1', 
		$pixbufs[rand($#pixbufs+1)], 'scalar changed1',
		[7, 8, 9], '<span color="green">changed1</span>' ];
}
elsif( $op eq 'Change 2' )	{
	$dslist->[0][0] = 'changed2';
	$dslist->[0][1] = 8;
	$dslist->[0][2] = 8.8;
	$dslist->[0][3] = 1;
	$dslist->[0][4] = 'scalar changed2';
	$dslist->[0][5] = $pixbufs[rand($#pixbufs+1)];
	$dslist->[0][6] = 'scalar changed2';
	$dslist->[0][7] = [8, 9, 10];
	$dslist->[0][8] = '<span color="green">changed2</span>';
}
elsif( $op eq 'Change 3' )	{
	# this is most useful if you've got a 1 column list
	$dslist->[0] = 'changed3';
}
elsif ($op eq 'Splice 1')	{
	splice @$dslist, 2, 2, (1..5),
}
elsif ($op eq 'Splice 2')	{
	splice @$dslist, 2, 0, (1..5)
}
elsif ($op eq 'Splice 3')	{
	splice @$dslist, 2, 2;
}
elsif ($op eq 'Splice 4')	{
	splice @$dslist, 2;
  } elsif( $op eq 'Delete' ) {
  # delete the ~middle element

#  my ($path, $f) = $slist->get_cursor;
#  my $row = $path->to_string();
#  print "$row\n";
#  delete $dslist->[$row]; 

  foreach $i (reverse $slist->get_selected_indices) {
    delete $dslist->[$i];
  }

#    delete $dslist->[$#$dslist]; posledni
  } elsif( $op eq 'GPS' ) {
    @{$slist->{data}} = ();
    foreach $i (keys %{$gps_pois}) {
      $sx = $gps_pois->{$i}{lat};
      $sy = $gps_pois->{$i}{lon};
      $sym = $gps_pois->{$i}{sym};
      $ele = $gps_pois->{$i}{ele};
      $name = $gps_pois->{$i}{name};

      push @{$slist->{data}}, 
            ([ 0,  $name, "x",  $sx, $sy, $type, $ele, $sym]);
    }
  } elsif( $op eq 'GC' ) {
		# can't use shorthand on this b/c we're replacing the ref
		# in the simple list's data.

    foreach $i (keys %{$cache}) {

      $sx = $cache->{$i}{lat};
      $sy = $cache->{$i}{lon};
      $type = $cache->{$i}{type};
      $size = $cache->{$i}{size};
      $hint = $cache->{$i}{hint};
      $name = $cache->{$i}{name};

      $found = $cache->{$i}{found};

      push @{$slist->{data}}, (
       [ $found, $cache->{$i}{wpt}, $name, $sx, $sy, $size, undef, 'uno', [1, 2, 3],'<span color="green">one</span>' ],
      );
    }
  } elsif( $op eq 'Many' ) {
		# can't use shorthand on this b/c we're replacing the ref
		# in the simple list's data.
		push @{$slist->{data}}, (
			[ 'one', 1, 1.1, 1, 'uno', undef, 'uno', 
				[1, 2, 3],
				'<span color="green">one</span>' ],
			[ 'two', 2, 2.2, 0, 'dos', undef, 'dos', 
				[2, 3, 4],
				'<span color="green">two</span>' ],
			[ 'three', 3, 3.3, 1, 'tres', undef, 'tres', 
				[3, 4, 5],
				'<span color="green">three</span>' ],
			[ 'four', 4, 4.4, 0, 'quatro', undef,  'quatro', 
				[4, 5, 6],
				'<span color="green">four</span>' ],
		);
		unshift @{$slist->{data}}, (
			[ 'one', 1, 1.1, 1, 'uno', undef, 'uno', 
				[1, 2, 3],
				'<span color="green">one</span>' ],
			[ 'two', 2, 2.2, 0, 'dos', undef, 'dos', 
				[2, 3, 4],
				'<span color="green">two</span>' ],
			[ 'three', 3, 3.3, 1, 'tres', undef, 'tres', 
				[3, 4, 5],
				'<span color="green">three</span>' ],
			[ 'four', 4, 4.4, 0, 'quatro', undef,  'quatro', 
				[4, 5, 6],
				'<span color="green">four</span>' ],
		);
	}
	elsif( $op eq 'Dump Sel' )
	{
		print "selected indices: "
		    . join(", ", 
		    $slist->get_selected_indices
		    )
		    . "\n";
		    
		    foreach $i (reverse $slist->get_selected_indices) {
		      delete $dslist->[$i];
		    }
		    
	}
	elsif( $op eq 'Dump List' )
	{
		print "\n\nList Data\n".Dumper($slist->{data})."\n\n";
	}

	1;
  }
  $win->show_all;
  Gtk2->main;
}
