#! /usr/bin/perl -s

use FindBin;
use lib $FindBin::Bin;
use POSIX qw(strftime);
use IO::Handle;
use FileHandle;
use Time::Local;

BEGIN {
  STDERR->autoflush(1);

  ($me = $0) =~ s%^.*/|-(old|new)$%%g if !defined $me;
  $conf = $ENV{HOME} . '/.' . $me . '.pl' if !defined $conf;

  local *C;
  my $expr;

  if (open(C, "< $conf")) {
    {local $/; $expr = <C>;}
    close(C);
  }

  eval $expr if $expr ne '';
}

use Garmin::FIT;

$debug = 0 if !defined $debug;
$verbose = 0 if !defined $verbose;
$indent_step = 2 if !defined $indent_step;
$pw_fix = 1 if !defined $pw_fix;
$pw_fix_b = 0 if !defined $pw_fix_b;
$tplimit = 0 if !defined $tplimit;
$tplimit_smart = 1 if !defined $tplimit_smart;
$must = 'Time' if !defined $must;
$double_precision = 7 if !defined $double_precision;
$include_creator = 1 if !defined $include_creator;
$tcdns = 'http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2' if !defined $tcdns;
$tcdxsd = 'http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd' if !defined $tcdxsd;
$fcns = 'http://www.garmin.com/xmlschemas/FatCalories/v1' if !defined $fcns;
$fcxsd = 'http://www.garmin.com/xmlschemas/fatcalorieextensionv1.xsd' if !defined $fcxsd;
$tpxns = 'http://www.garmin.com/xmlschemas/ActivityExtension/v2' if !defined $tpxns;
$tpxxsd = 'http://www.garmin.com/xmlschemas/ActivityExtensionv2.xsd' if !defined $tpxxsd;
$lxns = $tpxns if !defined $lxns;
$lxxsd = $tpxxsd if !defined $lxxsd;
$lap = '' if !defined $lap;
$lap_start = 0 if !defined $lap_start;
$lap_max = ~(~0 << 16) - 1 if !defined $lap_max;
$tpmask = '' if !defined $tpmask;
$tpexclude = '' if !defined $tpexclude;
$show_version = 0 if !defined $show_version;

my $version = "0.09";

if ($show_version) {
  print $version, "\n";
  exit;
}

my @must = split /,/, $must;
my @tpexclude = split /,/, $tpexclude;
my ($from, $to) = qw(- -);

if (@ARGV) {
  $from = shift @ARGV;
  @ARGV and $to = shift @ARGV;
}

my (%xmllocation, @xmllocation);

if (!defined $xmlns{$tpxns}) {
  $xmlns{$tpxns} = $tpxxsd;
  push @xmllocation, $tpxns, $tpxxsd;
}

if (!defined $xmlns{$fcns}) {
  $xmlns{$fcns} = $fcxsd;
  push @xmllocation, $fcns, $fcxsd;
}

if (!defined $xmlns{$lxns}) {
  $xmlns{$lxns} = $lxxsd;
  push @xmllocation, $lxns, $lxxsd;
}

if (!defined $xmlns{$tcdns}) {
  $xmlns{$tcdns} = $tcdxsd;
  push @xmllocation, $tcdns, $tcdxsd;
}

my $indent = ' ' x $indent_step;

my $start = <<EOF;
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<TrainingCenterDatabase xmlns="$tcdns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="@xmllocation">
$indent<Activities>
EOF

my $end = <<EOF;
$indent</Activities>
</TrainingCenterDatabase>
EOF

$indent .= ' ' x $indent_step;

my $pf = $double_precision eq '' ? 'g' : '.' . $double_precision . 'f';
my @with_ushort_value_def = ('sub' => [+{'name' => 'Value', 'format' => 'u'}]);

my %activity_def =
  (
   'name' => 'Activity',
   'array' => 1,
   'attr' => [+{'name' => 'Sport', 'format' => 's'}],

   'sub' => [
     +{'name' => 'Id', 'format' => 's'},

     +{
       'name' => 'Lap',
       'array' => 1,
       'attr' => [+{'name' => 'StartTime', 'format' => 's'}],

       'sub' => [
	 +{'name' => 'TotalTimeSeconds', 'format' => $pf},
	 +{'name' => 'DistanceMeters', 'format' => $pf},
	 +{'name' => 'MaximumSpeed', 'format' => $pf},
	 +{'name' => 'Calories', 'format' => 'u'},
	 +{'name' => 'AverageHeartRateBpm', @with_ushort_value_def},
	 +{'name' => 'MaximumHeartRateBpm', @with_ushort_value_def},
	 +{'name' => 'Intensity', 'format' => 's'},
	 +{'name' => 'Cadence', 'format' => 'u'},
	 +{'name' => 'TriggerMethod', 'format' => 's'},

	 +{
	   'name' => 'Track',
	   'array' => 1,

	   'sub' => [
	     +{
	       'name' => 'Trackpoint',
	       'array' => 1,

	       'sub' => [
		 +{'name' => 'Time', 'format' => 's'},

		 +{
		   'name' => 'Position',

		   'sub' => [
		     +{'name' => 'LatitudeDegrees', 'format' => $pf},
		     +{'name' => 'LongitudeDegrees', 'format' => $pf},
		   ],
		 },

		 +{'name' => 'AltitudeMeters', 'format' => $pf},
		 +{'name' => 'DistanceMeters', 'format' => $pf},
		 +{'name' => 'HeartRateBpm', @with_ushort_value_def},
		 +{'name' => 'Cadence', 'format' => 'u'},

		 +{
		   'name' => 'Extensions',

		   'sub' => [
		     +{
		       'name' => 'TPX',
		       'attr' => [+{'name' => 'xmlns', 'fixed' => $tpxns}],

		       'sub' => [
			 +{'name' => 'Speed', 'format' => $pf},
			 +{'name' => 'Watts', 'format' => 'u'},
		       ],
		     },
		   ],
		 },
	       ],
	     },
	   ],
	 },

	 +{
	   'name' => 'Extensions',

	   'sub' => [
	     +{
	       'name' => 'FatCalories',
	       'attr' => [+{'name' => 'xmlns', 'fixed' => $fcns}],
	       @with_ushort_value_def,
	     },

	     +{
	       'name' => 'LX',
	       'attr' => [+{'name' => 'xmlns', 'fixed' => $lxns}],

	       'sub' => [
		 +{'name' => 'AvgSpeed', 'format' => $pf},
		 +{'name' => 'MaxBikeCadence', 'format' => 'u'},
		 +{'name' => 'AvgWatts', 'format' => 'u'},
		 +{'name' => 'MaxWatts', 'format' => 'u'},
	       ],
	     },
	   ],
	 },
       ],
     },

     +{
       'name' => 'Creator',
       'attr' => [+{'name' => 'xsi:type', 'fixed' => 'Device_t'}],

       'sub' => [
	 +{'name' => 'Name', 'format' => 's'},
	 +{'name' => 'UnitId', 'format' => 'u'},
	 +{'name' => 'ProductID', 'format' => 'u'},

	 +{'name' => 'Version',
	   'sub' => [
	     +{'name' => 'VersionMajor', 'format' => 's'},
	     +{'name' => 'VersionMinor', 'format' => 's'},
	   ],
	 },
       ],
     },
   ],
   );

my (@lap, $beg_end);

foreach $beg_end (split /,/, $lap, -1) {
  if ($beg_end =~ /-/) {
    push @lap, $`, $';
  }
  else {
    push @lap, $beg_end, $beg_end;
  }
}

if (@lap && $lap_start > 0) {
  for ($i = 0 ; $i < @lap ; ++$i) {
    if ($lap[$i] =~ /^(\*|all)?$/i) {
      $lap[$i] = $i % 2 ? $lap_max : 0;
    }
    else {
      $lap[$i] -= $lap_start;
    }
  }
}

my (@tpmask, $mask);

sub cmp_lon {
  my ($a, $b) = @_;

  if ($a < $b) {
    if ($a - $b <= -180) {
      1;
    }
    else {
      -1;
    }
  }
  elsif ($a > $b) {
    if ($a - $b >= 180) {
      -1;
    }
    else {
      1;
    }
  }
  else {
    0;
  }
}

sub tpmask_rect {
  my ($lat, $lon, $lat_sw, $lon_sw, $lat_ne, $lon_ne) = @_;

  $lat >= $lat_sw && $lat <= $lat_ne && &cmp_lon($lon, $lon_sw) >= 0 && &cmp_lon($lon, $lon_ne) <= 0;
}

foreach $mask (split /:/, $tpmask) {
  my @v = split /,/, $mask;

  if (@v % 2) {
    die "$mask: not a sequence of latitude and longitude pairs";
  }
  elsif (@v < 4) {
    die "$mask: vertices < 2";
  }
  elsif (@v > 4) {
    die "$mask: sorry but arbitrary polygons are not implemented yet";
  }
  else {
    grep {
      s/^\s+|\s+$//g;
    } @v;

    $v[0] > $v[2] and @v[0, 2] = @v[2, 0];
    &cmp_lon($v[1], $v[3]) > 0 and @v[1, 3] = @v[3, 1];
    push @tpmask, [\&tpmask_rect, @v];
  }
}

my %memo = ('tpv' => [], 'trackv' => [], 'lapv' => [], 'av' => []);
my $fit = new Garmin::FIT;

$fit->use_gmtime(1);
$fit->numeric_date_time(0);
$fit->semicircles_to_degree(1);
$fit->without_unit(1);
$fit->mps_to_kph(0);

sub cb_file_id {
  my ($obj, $desc, $v, $memo) = @_;
  my $file_type = $obj->value_cooked(@{$desc}{qw(t_type a_type I_type)}, $v->[$desc->{i_type}]);

  if ($file_type eq 'activity') {
    1;
  }
  else {
    $obj->error("$file_type: not an activity");
    undef;
  }
}

sub cb_device_info {
  my ($obj, $desc, $v, $memo) = @_;

  if ($include_creator &&
      $obj->value_cooked(@{$desc}{qw(t_device_index a_device_index I_device_index)}, $v->[$desc->{i_device_index}]) eq 'creator') {
    my ($tname, $attr, $inval, $id) = (@{$desc}{qw(t_product a_product I_product)}, $v->[$desc->{i_product}]);
    my $t_attr = $obj->switched($desc, $v, $attr->{switch});

    if (ref $t_attr eq 'HASH') {
      $attr = $t_attr;
      $tname = $attr->{type_name};
    }

    my $ver = $obj->value_cooked(@{$desc}{qw(t_software_version a_software_version I_software_version)}, $v->[$desc->{i_software_version}]);
    my ($major, $minor) = split /\./, $ver, 2;

    $memo->{Creator} = +{
      'Name' => $obj->value_cooked($tname, $attr, $inval, $id),
      'UnitId' => $v->[$desc->{i_serial_number}],
      'ProductID' => $id,

      'Version' => +{
	'VersionMajor' => $major,
	'VersionMinor' => $minor,
      }
    };
  }

  1;
}

sub cb_record {
  my ($obj, $desc, $v, $memo) = @_;
  my (%tp, $lat, $lon, $speed, $watts);

  $tp{Time} = $obj->named_type_value($desc->{t_timestamp}, $v->[$desc->{i_timestamp}]);
  $memo->{id} = $tp{Time} if !defined $memo->{id};

  $lat = $obj->value_processed($v->[$desc->{i_position_lat}], $desc->{a_position_lat})
    if defined $desc->{i_position_lat} && $v->[$desc->{i_position_lat}] != $desc->{I_position_lat};

  $lon = $obj->value_processed($v->[$desc->{i_position_long}], $desc->{a_position_long})
    if defined $desc->{i_position_long} && $v->[$desc->{i_position_long}] != $desc->{I_position_long};

  defined $lat and defined $lon and $tp{Position} = +{'LatitudeDegrees' => $lat, 'LongitudeDegrees' => $lon};

  $tp{AltitudeMeters} = $obj->value_processed($v->[$desc->{i_altitude}], $desc->{a_altitude})
    if defined $desc->{i_altitude} && $v->[$desc->{i_altitude}] != $desc->{I_altitude};

  $tp{DistanceMeters} = $obj->value_processed($v->[$desc->{i_distance}], $desc->{a_distance})
    if defined $desc->{i_distance} && $v->[$desc->{i_distance}] != $desc->{I_distance};

  $speed = $obj->value_processed($v->[$desc->{i_speed}], $desc->{a_speed})
    if defined $desc->{i_speed} && $v->[$desc->{i_speed}] != $desc->{I_speed};

  $tp{HeartRateBpm} = +{'Value' => $v->[$desc->{i_heart_rate}]} if defined $desc->{i_heart_rate} && $v->[$desc->{i_heart_rate}] != $desc->{I_heart_rate};
  $tp{Cadence} = $v->[$desc->{i_cadence}] if defined $desc->{i_cadence} && $v->[$desc->{i_cadence}] != $desc->{I_cadence};
  $watts = $v->[$desc->{i_power}] * $pw_fix + $pw_fix_b if $v->[$desc->{i_power}] != $desc->{I_power};

  if (defined $speed || defined $watts) {
    my %tpx;

    $tpx{Speed} = $speed if defined $speed;
    $tpx{Watts} = $watts if defined $watts;
    $tp{Extensions} = +{'TPX' => \%tpx};
  }

  my ($miss, $k);

  foreach $k (@tpexclude) {
    delete $tp{$k};
  }

  foreach $k (@must) {
    defined $tp{$k} or ++$miss;
  }

  push @{$memo->{tpv}}, \%tp if !$miss;
  1;
}

sub track_end {
  my $memo = shift;
  my $ntps = @{$memo->{tpv}};

  if ($ntps) {
    my %track = ('Trackpoint' => [@{$memo->{tpv}}]);

    @{$memo->{tpv}} = ();
    $memo->{ntps} += $ntps;
    push @{$memo->{trackv}}, \%track;
  }
}

sub cb_event {
  my ($obj, $desc, $v, $memo) = @_;
  my $event = $obj->named_type_value($desc->{t_event}, $v->[$desc->{i_event}]);
  my $event_type = $obj->named_type_value($desc->{t_event_type}, $v->[$desc->{i_event_type}]);

  if ($event_type eq 'stop_all') {
    &track_end($memo);
  }

  1;
}

my %intensity =
  (
   'active' => 'Active',
   'rest' => 'Resting',
   );

my %lap_trigger =
  (
   'manual' => 'Manual',
   'distance' => 'Distance',
   'time' => 'Time',
   );

sub cb_lap {
  my ($obj, $desc, $v, $memo) = @_;

  &track_end($memo);

  if (@{$memo->{trackv}}) {
    my %lap = ('Track' => [@{$memo->{trackv}}]);

    @{$memo->{trackv}} = ();
    $lap{'<a>StartTime'} = $obj->named_type_value($desc->{t_start_time}, $v->[$desc->{i_start_time}]);
    $lap{TotalTimeSeconds} = $obj->value_processed($v->[$desc->{i_total_timer_time}], $desc->{a_total_timer_time});

    $lap{DistanceMeters} = $obj->value_processed($v->[$desc->{i_total_distance}], $desc->{a_total_distance})
      if defined $desc->{i_total_distance} && $v->[$desc->{i_total_distance}] != $desc->{I_total_distance};

    $lap{MaximumSpeed} = $obj->value_processed($v->[$desc->{i_max_speed}], $desc->{a_max_speed})
      if defined $desc->{i_max_speed} && $v->[$desc->{i_max_speed}] != $desc->{I_max_speed};

    $lap{Calories} = $v->[$desc->{i_total_calories}]
      if defined $desc->{i_total_calories} && $v->[$desc->{i_total_calories}] != $desc->{I_total_calories};

    $lap{Cadence} = $v->[$desc->{i_avg_cadence}] if defined $desc->{i_avg_cadence} && $v->[$desc->{i_avg_cadence}] != $desc->{I_avg_cadence};

    my $intensity = $obj->value_cooked(@{$desc}{qw(t_intensity a_intensity I_intensity)}, $v->[$desc->{i_intensity}]);

    defined ($lap{Intensity} = $intensity{$intensity}) or $lap{Intensity} = 'Active';

    my $lap_trigger = $obj->value_cooked(@{$desc}{qw(t_lap_trigger a_lap_trigger I_lap_trigger)}, $v->[$desc->{i_lap_trigger}]);

    defined ($lap{TriggerMethod} = $lap_triger{$lap_triger}) or $lap{TriggerMethod} = 'Manual';

    $lap{AverageHeartRateBpm} = +{'Value' => $v->[$desc->{i_avg_heart_rate}]}
      if defined $desc->{i_avg_heart_rate} && $v->[$desc->{i_avg_heart_rate}] != $desc->{I_avg_heart_rate};

    $lap{MaximumHeartRateBpm} = +{'Value' => $v->[$desc->{i_max_heart_rate}]}
      if defined $desc->{i_max_heart_rate} && $v->[$desc->{i_max_heart_rate}] != $desc->{I_max_heart_rate};

    my (%x, %lx);

    $x{FatCalories} = +{'Value' => $v->[$desc->{i_total_fat_calories}]}
      if defined $desc->{i_total_fat_calories} && $v->[$desc->{i_total_fat_calories}] != $desc->{I_total_calories};

    $lx{AvgSpeed} = $obj->value_processed($v->[$desc->{i_avg_speed}], $desc->{a_avg_speed})
      if defined $desc->{i_avg_speed} && $v->[$desc->{i_avg_speed}] != $desc->{I_avg_speed};

    $lx{MaxBikeCadence} = $v->[$desc->{i_max_cadence}] if defined $desc->{i_max_cadence} && $v->[$desc->{i_max_cadence}] != $desc->{I_max_cadence};
    $lx{AvgWatts} = $v->[$desc->{i_avg_power}] * $pw_fix + $pw_fix_b if defined $desc->{i_avg_power} && $v->[$desc->{i_avg_power}] != $desc->{I_avg_power};
    $lx{MaxWatts} = $v->[$desc->{i_max_power}] * $pw_fix + $pw_fix_b if defined $desc->{i_max_power} && $v->[$desc->{i_max_power}] != $desc->{I_max_power};
    %lx and $x{LX} = \%lx;
    %x and $lap{Extensions} = \%x;
    push @{$memo->{lapv}}, \%lap;
  }

  1;
}

my %sport =
  (
   'running' => 'Running',
   'cycling' => 'Biking',
   );

sub cb_session {
  my ($obj, $desc, $v, $memo) = @_;

  unless (@{$memo->{lapv}}) {
    &cb_lap($obj, $desc, $v, $memo) || return undef;
  }

  if (@{$memo->{lapv}}) {
    my %activity;

    defined($activity{'<a>Sport'} = $sport{$obj->named_type_value($desc->{t_sport}, $v->[$desc->{i_sport}])}) or $activity{'<a>Sport'} = 'Other';
    $activity{Id} = $obj->named_type_value($desc->{t_start_time}, $v->[$desc->{i_start_time}]);
    $activity{Lap} = [@{$memo->{lapv}}];
    @{$memo->{lapv}} = ();
    $activity{Creator} = $memo->{Creator} if defined $memo->{Creator};
    push @{$memo->{av}}, \%activity;
  }

  delete $memo->{Creator};
  1;
}

sub output {
  my ($datum, $def, $indent, $T) = @_;

  if (ref $datum eq 'ARRAY') {
    my $datum1;

    foreach $datum1 (@$datum) {
      &output($datum1, $def, $indent, $T);
    }
  }
  else {
    $T->print("$indent<$def->{name}");

    my $attrv = $def->{attr};

    if (ref $attrv eq 'ARRAY') {
      my $attr;

      foreach $attr (@$attrv) {
	my ($aname, $aformat, $afixed) = @{$attr}{qw(name format fixed)};

	$T->print(" $aname=\"");

	if (defined $afixed) {
	  $T->print($afixed);
	}
	elsif (defined $aformat) {
	  $T->printf("%$aformat", $datum->{'<a>' . $aname});
	}

	$T->print("\"");
      }
    }

    $T->print(">");

    my ($sub, $format) = @{$def}{qw(sub format)};

    if ($format ne '') {
      $T->printf("%$format", $datum);
    }
    elsif (ref $sub eq 'ARRAY') {
      $T->print("\n");

      my $subindent = $indent . ' ' x $indent_step;
      my $i;

      for ($i = 0 ; $i < @$sub ;) {
	my $subdef = $sub->[$i++];
	my $subdatum = $datum->{$subdef->{name}};

	defined $subdatum and &output($subdatum, $subdef, $subindent, $T);
      }

      $T->print($indent);
    }

    $T->print("</$def->{name}>\n");
  }
}

$fit->data_message_callback_by_name('file_id', \&cb_file_id, \%memo) || die $fit->error;
$fit->data_message_callback_by_name('device_info', \&cb_device_info, \%memo) || die $fit->error;
$fit->data_message_callback_by_name('record', \&cb_record, \%memo) || die $fit->error;
$fit->data_message_callback_by_name('event', \&cb_event, \%memo) || die $fit->error;
$fit->data_message_callback_by_name('lap', \&cb_lap, \%memo) || die $fit->error;
$fit->data_message_callback_by_name('session', \&cb_session, \%memo) || die $fit->error;
$fit->file($from);
$fit->open || die $fit->error;

sub dead {
  my ($obj, $err) = @_;
  my ($p, $fn, $l, $subr, $fit);

  $err = $obj->{error} if !defined $err;
  (undef, $fn, $l) = caller(0);
  ($p, undef, undef, $subr) = caller(1);
  $obj->close;
  die "$p::$subr\#$l\@$fn: $err\n";
}

my ($fsize, $proto_ver, $prof_ver, $h_extra, $h_crc_expected, $h_crc_calculated) = $fit->fetch_header;

defined $fsize || &dead($fit);

my ($proto_major, $proto_minor) = $fit->protocol_version_major($proto_ver);
my ($prof_major, $prof_minor) = $fit->profile_version_major($prof_ver);

if ($verbose) {
  printf "File size: %lu, protocol version: %u.%02u, profile_verion: %u.%02u\n", $fsize, $proto_major, $proto_minor, $prof_major, $prof_minor;

  if ($h_extra ne '') {
    print "Hex dump of extra octets in the file header";

    my ($i, $n);

    for ($i = 0, $n = length($h_extra) ; $i < $n ; ++$i) {
      print "\n  " if !($i % 16);
      print ' ' if !($i % 4);
      printf " %02x", ord(substr($h_extra, $i, 1));
    }

    print "\n";
  }

  if (defined $h_crc_calculated) {
    printf "File header CRC: expected=0x%04X, calculated=0x%04X\n", $h_crc_expected, $h_crc_calculated;
  }
}

1 while $fit->fetch;
$fit->EOF || &dead($fit);

if ($verbose) {
  printf "CRC: expected=0x%04X, calculated=0x%04X\n", $fit->crc_expected, $fit->crc;

  my $garbage_size = $fit->trailing_garbages;

  print "Trailing $garbage_size octets garbages skipped\n" if $garbage_size > 0;
}

$fit->close;

my $av = $memo{av};

if (@$av) {
  my ($i, $j);

  for ($i = $j = 0 ; $i < @$av ; ++$i) {
    my $lv = $av->[$i]->{Lap};

    if (@lap) {
      my ($p, $q, $r);

      for ($p = $q = 0 ; $p < @$lv ; ++$p) {
	for ($r = 1 ; $r < @lap ; $r += 2) {
	  if ($p >= $lap[$r - 1] && $p <= $lap[$r]) {
	    $lv->[$q++] = $lv->[$p];
	    last;
	  }
	}
      }

      splice @$lv, $q;
    }

    @$lv and $av->[$j++] = $av->[$i];
  }

  splice @$av, $j;
}

if (@$av && @tpmask) {
  my ($i, $j);

  for ($i = $j = 0 ; $i < @$av ; ++$i) {
    my $lv = $av->[$i]->{Lap};
    my ($p, $q);

    for ($p = $q = 0 ; $p < @$lv ; ++$p) {
      my $trkv = $lv->[$p]->{Track};
      my ($u, $v);

      for ($u = $v = 0 ; $u < @$trkv ; ++$u) {
	my $tpv =$trkv->[$u]->{Trackpoint};
	my ($r, $s);

	for ($r = $s = 0 ; $r < @$tpv ; ++$r) {
	  my $mask;

	  foreach $mask (@tpmask) {
	    if ($mask->[0]->(@{$tpv->[$r]->{Position}}{qw(LatitudeDegrees LongitudeDegrees)}, @$mask[1 .. $#$mask])) {
	      $memo{ntps} -= 1;
	    }
	    else {
	      $tpv->[$s++] = $tpv->[$r];
	    }
	  }
	}

	splice @$tpv, $s;
	@$tpv and $trkv->[$v++] = $trkv->[$u];
      }

      splice @$trkv, $v;
      @$trkv and $lv->[$q++] = $lv->[$p];
    }

    splice @$lv, $q;
    @$lv and $av->[$j++] = $av->[$i];
  }

  splice @$av, $j;
}

if (@$av) {
  my $T = new FileHandle "> $to";

  defined $T || &dead($fit, "new FileHandle \"> $to\": $!");
  $T->print($start);

  my ($skip, $a);

  if ($tplimit > 0 && ($skip = $memo{ntps} / $tplimit) > 1) {
    foreach $a (@$av) {
      my $l;

      foreach $l (@{$a->{Lap}}) {
	my $t;

	foreach $t (@{$l->{Track}}) {
	  my $tpv = $t->{Trackpoint};
	  my ($j, @mv);

	  if ($tplimit_smart && defined $tpv->[0]->{AltitudeMeters}) {
	    for ($i = 1 ; $i < $#$tpv ;) {
	      my $updown;

	      for ($j = $i + 1 ; $j < @$tpv ; ++$j) {
		if (defined $tpv->[$j]->{AltitudeMeters}) {
		  if (($updown = $tpv->[$j]->{AltitudeMeters} - $tpv->[$i]->{AltitudeMeters})) {
		    last;
		  }
		}
	      }

	      if ($updown) {
		my $k;

		for ($k = $j + 1 ; $k < @$tpv ; ++$k) {
		  if (defined $tpv->[$k]->{AltitudeMeters}) {
		    if (($tpv->[$k]->{AltitudeMeters} - $tpv->[$j]->{AltitudeMeters}) / $updown < 0) {
		      last;
		    }
		  }
		}

		if ($k < @$tpv && $k - $i > $skip) {
		  push @mv, $k;
		}

		$i = $j;
	      }
	      else {
		last;
	      }
	    }
	  }

	  push @mv, $#$tpv + 1;

	  for ($i = $j = 1 ; @mv ;) {
	    my $m = shift @mv;
	    my $start = $i;
	    my $count;

	    for ($count = 0 ; $i < $m ; ++$j, ++$count) {
	      my $next = $start + int($count * $skip);
	      my ($k, $wsum, $wn, $csum, $cn);

	      for ($k = $i ; $k < $next && $k < $m ; ++$k) {
		my ($x, $tpx, $w);

		if (defined ($x = $tpv->[$k]->{Extensions}) &&
		    defined ($tpx = $x->{TPX}) &&
		    defined ($w = $tpx->{Watts})) {
		  $wsum += $w;
		  ++$wn;
		}

		if (defined $tpv->[$k]->{Cadence}) {
		  $csum += $tpv->[$k]->{Cadence};
		  ++$cn;
		}
	      }

	      $tpv->[$j] = $tpv->[$k - 1];
	      $tpv->[$j]->{Extensions}->{TPX}->{Watts} = $wsum / $wn if $wn > 0;
	      $tpv->[$j]->{Cadence} = $csum / $cn if $cn > 0;
	      $i = $k;
	    }
	  }

	  $j < @$tpv and splice @$tpv, $j;
	}
      }
    }
  }

  foreach $a (@$av) {
    &output($a, \%activity_def, $indent, $T);
  }

  $T->print($end);
  $T->close;
}

1;
__END__

