package HTML::GMap::Files;

our $VERSION = '0.02';

# $Id: Files.pm,v 1.6 2007/05/10 14:51:52 canaran Exp $

use warnings;
use strict;

use Carp;
use Storable;

sub new {
    my ($class, %params) = @_;

    my $self = bless {}, $class;

    eval {
        exists $params{temp_dir} or croak("A temp_dir param is required!");
        $self->temp_dir($params{temp_dir});
    };

    $self->croak($@) if $@;

    $self->create_files;

    return $self;
}

sub create_files {
    my ($self) = @_;

    my $files_ref = $self->files;
    my $temp_dir  = $self->temp_dir;

    foreach my $file_name (keys %$files_ref) {
        my $file_content = $files_ref->{$file_name}->{content};
        my $file_type    = $files_ref->{$file_name}->{type};
        
        if ($file_type eq 'binary') {
            $file_content =~ s/\n//g;
            
            my $binary_content = pack('H*', $file_content);
            
            open(OUT, ">$temp_dir/$file_name")
              or croak("Cannot write file ($temp_dir/$file_name): $!");
            binmode OUT;
            
            print OUT $binary_content;

            close OUT;
        }    
        
        else {
            open(OUT, ">$temp_dir/$file_name")
              or croak("Cannot write file ($temp_dir/$file_name): $!");

            $file_content =~ s/^\s+//;
            $file_content =~ s/\s+$/\n/;

            print OUT $file_content;

            close OUT;
        }    


    }

    return 1;
}

sub temp_dir {
    my ($self, $value) = @_;

    $self->{temp_dir} = $value if @_ > 1;

    return $self->{temp_dir};
}

sub files {
    my ($self) = @_;

    my %files;

    
    $files{'gmap-main.css'}{'type'}    = 'ascii';
    $files{'gmap-main.css'}{'content'} = <<'CONTENT';
/*
Author: Payan Canaran (canaran@cshl.edu)
Copyright 2006-2007 Cold Spring Harbor Laboratory
$Id: Files.pm,v 1.6 2007/05/10 14:51:52 canaran Exp $
*/

body {
    text-align: center;
    font-family: arial;
}    

div {
  border: 0px solid;
  font-size: 13px;
}

th {
  font-size: 12px;
}

td {
  font-size: 12px;
}

div.container {
  border: 1px dashed #333;
  background-color: #F5F5DC;
  margin-left: auto;
  margin-right: auto;
}

div.spacer {
  clear: both;
  height: 2px;
  display: inline;
}

div.left_column {
  float: left;
  display: inline;
  text-align: left;
}

div.center_column {
  float: left;
  display: inline;
}

div.right_column {
  float: left;
  width: 250px;
  display: inline;
  text-align: left;
}

div.sub_header {
  clear: both;
  padding: 4px;
  font-size: 14px;
  font-weight: bold;
  display: inline;
}

div.float_right {
  clear: right;
  display: inline;
}


div.map {
  float: left;
  display: inline;
}


div.status {
  float: left;
  padding: 4px;
  width: 250px;
  display: inline;
}

div.filter {
  float: left;
  padding: 4px;
  width: 240px;
  display: inline;
}

div.messages {
 float: left;
 padding: 4px;
 width: 250px;
 overflow: auto;
 display: inline;
}

div.details_info {
  float: left;
  padding: 4px;
  height: 110px;
  width: 240px;
  overflow: auto;
  display: inline;
}

div.legend_info {
  float: left;
  padding: 4px;
  height: 490px;
  width: 180px;
  overflow: auto;
  display: inline;
}


div.hidden {
  visibility: hidden;
  display: inline;
}

div.visible {
  visibility: visible;
  display: inline;
}

font.bold {
  font-weight: bold;
}

CONTENT
    
    $files{'gmap-main.html'}{'type'}    = 'ascii';
    $files{'gmap-main.html'}{'content'} = <<'CONTENT';
<!--
Author: Payan Canaran (canaran@cshl.edu)
Copyright 2006-2007 Cold Spring Harbor Laboratory
$Id: Files.pm,v 1.6 2007/05/10 14:51:52 canaran Exp $
-->

[% cgi_header %]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">

<head>
    <link rel="stylesheet" type="text/css" href="[% gmap_main_css_file_eq %]" />
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>[% page_title %]</title>

    <style type="text/css">
        v\:* {
          behavior:url(#default#VML);
        }
    </style>
</head>

<body>
    [% header %]

    <h1>[% page_title %]</h1>

    <div class="container" style="height: [% container_height_pix %]px; width: [% container_width_pix %]px">
        <div class="spacer">&nbsp;</div>

        <div class="left_column">
            <div class="sub_header">
                <div class="sub_header">Legend:&nbsp;</div>
                <div id="legend" class="legend_info"></div>
                <p/>
                <div id="legend_message"></div>
            </div>
        </div>

        <div class="center_column" style="width: [% center_width_pix %]px">
            <div id="map" class="map" style="height: [% image_height_pix %]px; width: [% image_width_pix %]px">
            Loading map ...
            </div>
        </div>

        <div class="right_column">
            <div>
                <div class="sub_header">Status:&nbsp;</div>
                <div id="status" class="status">Initializing ...</div>
            </div>

            <div>
                <div class="spacer">&nbsp;</div>
                <form id="filter">
                <div class="sub_header">Filter:&nbsp;</div>

                <div class="spacer">&nbsp;</div>
                <div class="spacer">&nbsp;</div>

                <div class="filter">
                    <table width="100%">
                    [% FOREACH field = param_fields_with_values %]
                        <tr>
                        <!-- Param [% field.name %] -->
                        <td align="left">
                            <font class="bold">[% field.display %]:</font>
                        </td>
                        <td align="right">
                            <select id="[% field.name %]" name="[% field.name %]">
                                [% FOREACH value = field.values %]
                                    <option value="[% value.param %]">[% value.display %]</option>
                                [% END %]
                            </select>
                        </td>
                    [% END %]
                    </tr>
                    </table>
                    <div id="cluster_slices_group" class="visible">
                        [% IF display_cluster_slices %]
                        <table width="100%">
                            <tr>
                                <td align="left">
                                    <input id="cluster_slices" type="checkbox" name="cluster_slices"></input>
                                    Cluster rare values:
                                </td>
                            </tr>
                            <tr>
                                <td align="right">
                                    (less than
                                    <input id="cluster_slices_value" type="text" name="cluster_slices_value" size="2" value="2">
                                    <select id="cluster_slices_by" name="cluster_slices_by">
                                        <option value="percent" selected="1">%</option>
                                        <option value="count">pt</option>
                                    </select>
                                    per tile)
                                </td>
                            </tr>
                        </table>
                        [% END %]
                    </div>

                    <div>
                    <table width="100%">
                        <tr>
                            <td align="right">
                                <input type="button" name="Filter" value="Filter" onClick="doRefresh()"/>
                            </td>
                        </tr>       
                    </table>    
                    </div>
                </div>

                </form>    

            </div>

            <div>
                <div class="sub_header">Details:&nbsp;</div>
                <div id="details" class="details_info"></div>
            </div>

            <div>
                <div class="sub_header">Messages:&nbsp;</div>
                <div id="messages" class="messages">[% messages %]</div>            
            </div>
        </div>

        <div class="spacer">&nbsp;</div>
    </div>

    <div id="debug">
    </div>

    <!-- Execute scripts here (1/2) -->
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=[% gmap_key %]"
            type="text/javascript">
    </script>

    <script src="[% prototype_js_file_eq %]"
            type="text/javascript">
    </script>

    <script type="text/javascript">
        var varStore = {
            centerLat      :'[% center_latitude %]',
            centerLng      :'[% center_longitude %]',
            imageHeightPix :'[% image_height_pix %]',
            tileHeightPix  :'[% tile_height_pix %]',
            imageWidthPix  :'[% image_width_pix %]',
            tileWidthPix   :'[% tile_width_pix %]',
            queryParams    :[[% param_fields %]], // This must be an array
            urlTemplate    :'[% url_template %]',
            clusterField   :'[% cluster_field %]'
        };
    </script>

    <!-- Execute scripts here (2/2) -->
    <script src="[% gmap_main_js_file_eq %]"
            type="text/javascript">
    </script>

    [% footer %]
</body>
</html>

CONTENT
    
    $files{'gmap-main.js'}{'type'}    = 'ascii';
    $files{'gmap-main.js'}{'content'} = <<'CONTENT';
// Author: Payan Canaran (canaran@cshl.edu)
// Copyright 2006-2007 Cold Spring Harbor Laboratory
// $Id: Files.pm,v 1.6 2007/05/10 14:51:52 canaran Exp $

// Create and initialize map
var map = new GMap2(document.getElementById('map'));

var centerLat = varStore.centerLat;
var centerLng = varStore.centerLng;

map.addControl(new GSmallZoomControl());
map.addControl(new GMapTypeControl());
map.addControl(new GScaleControl());
map.setCenter(new GLatLng(centerLat, centerLng), 4);

// Register map events
GEvent.addListener(map, "movestart", startRefresh);
GEvent.addListener(map, "zoomend",   limitZoom);
GEvent.addListener(map, "moveend",   limitVerticalMove);
GEvent.addListener(map, "moveend",   doRefresh);

// Register non-map events
var clusterField = varStore.clusterField;
if (clusterField && clusterField != '_default') {
    Event.observe(document.getElementById(clusterField), 'change', toggleClusterSlicesGroup);
}
Event.observe(window, 'load', doRefresh);

// [End of main script]

// Create a URL from form variables
function constructUrl() {
    var queryParams = varStore.queryParams;
    var urlTemplate = varStore.urlTemplate;

    // Determine geographic boundaries of the map in view
    var mapBounds = map.getBounds();

    var  boundSouthWest = mapBounds.getSouthWest();
    var  boundNorthEast = mapBounds.getNorthEast();

    var latNorth = boundNorthEast.lat();
    var lngEast  = boundNorthEast.lng();
    var latSouth = boundSouthWest.lat();
    var lngWest  = boundSouthWest.lng();

    // Make a copy of urlTemplate, will be overwritten
    var url = urlTemplate;

   // Retrieve and substitute query param values
   for (var i = 0; i < queryParams.length; i++) {
       var paramName = queryParams[i];
       var paramValue = document.getElementById(paramName).value;
       url += ';' + escape(paramName) + "=" + escape(paramValue);
   }

    // Add coordinates
    url += "&latitude_north=" + escape(latNorth);
    url += "&longitude_east=" + escape(lngEast);
    url += "&latitude_south=" + escape(latSouth);
    url += "&longitude_west=" + escape(lngWest);

    // Add clustering (slices) params
    if (document.getElementById('cluster_slices_group') 
        && Element.hasClassName(document.getElementById('cluster_slices_group'), 'visible')
        && document.getElementById('cluster_slices') 
        ) {
        url += "&cluster_slices="       + escape(document.getElementById('cluster_slices').checked);
        url += "&cluster_slices_value=" + escape(document.getElementById('cluster_slices_value').value);
        url += "&cluster_slices_by="    + escape(document.getElementById('cluster_slices_by').value);
    };

    // document.getElementById("debug").innerHTML = url;

    return url;
}

// Add latitude-longitude grid to map
function addLatLngLines() {
    var imageHeightPix = varStore.imageHeightPix;
    var tileHeightPix = varStore.tileHeightPix;

    var imageWidthPix  = varStore.imageWidthPix;
    var tileWidthPix  = varStore.tileWidthPix;

    var numberOfVerticalTiles   = imageHeightPix / tileHeightPix;
    var numberOfHorizontalTiles = imageWidthPix / tileWidthPix;

    // Determine geographic boundaries of the map in view
    var mapBounds = map.getBounds();

    var  boundSouthWest = mapBounds.getSouthWest();
    var  boundNorthEast = mapBounds.getNorthEast();

    var latNorth = boundNorthEast.lat();
    var lngEast  = boundNorthEast.lng();
    var latSouth = boundSouthWest.lat();
    var lngWest  = boundSouthWest.lng();

    var lngMid   = (lngWest < lngEast) ? lngWest + (lngEast - lngWest) / 2
                                       : lngWest + ((lngEast - (-180)) + (180 - lngWest)) / 2;
    if (lngMid > 180) { // Longitude +180 continues with -180
        lngMid = -180 + (lngMid - 180); // (lngMid - 180) is overflow over 180'
    }

    // Longitude +180 continues with -180
    tileSideLng = (lngWest < lngEast) ? (lngEast - lngWest) / numberOfHorizontalTiles              
                                      : ((lngEast - (-180)) + (180 - lngWest)) / numberOfHorizontalTiles;

    tileSideLat = (latNorth - latSouth) / numberOfVerticalTiles;

//  document.getElementById("debug").innerHTML =   '> latNorth:' + latNorth
//                                               + ' latSouth:' + latSouth
//                                               + '<br>'
//                                               + ' lngWest:'  + lngWest
//                                               + ' lngEast:'  + lngEast
//                                               + ' tileSideLng:'  + tileSideLng
//                                               + '<br/>';

    for (var n = 1; n < numberOfHorizontalTiles; n++) {
        // Draw longitudes
        var lngNth = lngWest + n * tileSideLng;

        if (lngNth > 180) { // Longitude +180 continues with -180
            lngNth = -180 + (lngNth - 180); // (lngNth - 180) is overflow over 180'
        }

        var polyline = new GPolyline([
            new GLatLng(latNorth, lngNth),
            new GLatLng(latSouth, lngNth)
            ], "#ff8b04", 2);
        map.addOverlay(polyline);
    }   

    for (var n = 1; n < numberOfVerticalTiles; n++) {
        // Draw latitudes
        var latNth = latSouth + n * tileSideLat;
        var polyline = new GPolyline([
            new GLatLng(latNth, lngWest),
            new GLatLng(latNth, lngMid),   // A mid point is needed to ensure direction of the line is correct
            new GLatLng(latNth, lngEast)
            ], "#ff8b04", 2);
        map.addOverlay(polyline);
    }

// document.getElementById("debug").innerHTML = 'zoom:' + map.getZoom();

    return 1;
}

// Start refresh (clear overlays and messages)
function startRefresh() {
    // Clear existing overlays
    map.clearOverlays();

    document.getElementById("status").innerHTML         = 'Moving ...';
    document.getElementById("legend").innerHTML         = '';
    document.getElementById("legend_message").innerHTML = '';
    document.getElementById("details").innerHTML        = '';
}

// Refresh display (clear overlays, send an AJAX request, process request)
function doRefresh(event) {
    // Clear existing overlays
    map.clearOverlays();

    document.getElementById("status").innerHTML = "Refreshing ...";

    // Construct URL based on coordinates and form values
    var requestUrl = constructUrl();

    // Query database, pass results on for parsing
    GDownloadUrl(requestUrl, processRequest); // No parentheses

    addLatLngLines();
}

// Place markers on the map
function processRequest(data, responseCode) {
    var xml = GXml.parse(data);

    var markers = xml.documentElement.getElementsByTagName("marker");

    for (var i = 0; i < markers.length; i++) {
        var lat = parseFloat(markers[i].getAttribute("latitude"));
        var lng = parseFloat(markers[i].getAttribute("longitude"));

        var iconUrl         = markers[i].getAttribute("icon_url");
        var iconSize        = markers[i].getAttribute("icon_size");
        var messagesOnClick = markers[i].getAttribute("messages_on_click");
        var detailsOnClick  = markers[i].getAttribute("details_on_click");
        var legendOnClick   = markers[i].getAttribute("legend_on_click");

        var point = new GLatLng(lat, lng);

        addMarker(point, iconUrl, iconSize, legendOnClick, detailsOnClick, messagesOnClick);

        document.getElementById("details").innerHTML = '[Click on an icon on the map for details ...]';
    }

    var metaData = xml.documentElement.getElementsByTagName("meta_data"); // Only one present

    var legendByDefault   = metaData[0].getAttribute("legend_by_default");
    var detailsByDefault  = metaData[0].getAttribute("details_by_default")
    var messagesByDefault = metaData[0].getAttribute("messages_by_default")

    if (legendByDefault) {
        document.getElementById("legend").innerHTML = legendByDefault;
    }

    if (detailsByDefault) {
        document.getElementById("details").innerHTML = detailsByDefault;
    }

    if (messagesByDefault) {
        document.getElementById("messages").innerHTML = messagesByDefault;
    }

document.getElementById("status").innerHTML = "Ready";
}

// Utility function to add a marker
function addMarker(point, iconUrl, iconSize, legendOnClick, detailsOnClick, messagesOnClick) {
    var icon   = createIcon(iconUrl, iconSize);
    var marker = new GMarker(point, icon);

    if (legendOnClick) {
        GEvent.addListener(marker, "click", function() {
            document.getElementById("legend").innerHTML = legendOnClick;
        })
    };

    if (detailsOnClick) {
        GEvent.addListener(marker, "click", function() {
            document.getElementById("details").innerHTML = detailsOnClick;
        })
    };

    if (messagesOnClick) {
        GEvent.addListener(marker, "click", function() {
            document.getElementById("message").innerHTML = messagesOnClick;
        })
    };

    map.addOverlay(marker);
}

// Utility function to create an icon object to be used as a marker
function createIcon(imageUrl, size) {
    var icon                = new GIcon();
    icon.image              = imageUrl;

    icon.shadow             = "";
    icon.iconSize           = new GSize(size, size);
    icon.shadowSize         = "";
    icon.iconAnchor         = new GPoint(Math.floor(size/2)+1, Math.floor(size/2)+1);
    icon.infoWindowAnchor   = new GPoint(Math.floor(size/2)+1, Math.floor(size/2)+1);;
    return icon;
}

// Utility function to toggle cluster slices group
function toggleClusterSlicesGroup(event) {
    var element      = Event.element(event);
    var elementValue = element.value;

    var clusterSlicesGroup = document.getElementById("cluster_slices_group");

    if (elementValue == 'all') {
        clusterSlicesGroup.className = 'visible';
    }

    else {
        clusterSlicesGroup.className = 'hidden';
    }
}

// Utility function to limit zoom
function limitZoom() {
    var zoom = map.getZoom();

    if (zoom < 3) {
        map.setZoom(3);
    }
}

// Utility function to limit vertical move
function limitVerticalMove() {
    var mapBounds = map.getBounds();

    var  boundSouthWest = mapBounds.getSouthWest();
    var  boundNorthEast = mapBounds.getNorthEast();

    var latNorth = boundNorthEast.lat();
    var lngEast  = boundNorthEast.lng();
    var latSouth = boundSouthWest.lat();
    var lngWest  = boundSouthWest.lng();

    var latDelta = latNorth - latSouth;

    var mapCenter    = map.getCenter();
    var mapCenterLng = mapCenter.lng();

    if (latNorth > 84.5) {
        var newMapCenterLat = 84.5 - latDelta / 2;
        map.panTo(new GLatLng(newMapCenterLat, mapCenterLng));
    }    

    if (latSouth < -84.5) {
        var newMapCenterLat = -84.5 + latDelta / 2;
        map.panTo(new GLatLng(newMapCenterLat, mapCenterLng));
    }    
}

// End of Functions

CONTENT
    

    return \%files;
}

1;

__END__

=head1 NAME

HTML::GMap::Files - File storage for HTML::GMap

=head1 SYNOPSIS

  HTML::GMap::Files->new(temp_dir => '/tmp');

=head1 DESCRIPTION

This file is used by HTML::GMap as a storage for 
HTML template, CSS and Javascript files.

=head1 USAGE

This file is not intended to be used directly. Please refer to
HTML::GMap::Tutorial for detailed usage information.

=head1 AUTHOR

Payan Canaran <canaran@cshl.edu>

=head1 BUGS

=head1 VERSION

Version 0.02

=head1 ACKNOWLEDGEMENTS

=head1 COPYRIGHT & LICENSE

Copyright (c) 2006-2007 Cold Spring Harbor Laboratory

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself. See DISCLAIMER.txt for
disclaimers of warranty.

=cut
