var width = 750,            // Set up size
	height = width;
    
var circles = {},           // A and B locations 
    tmp;                    // tmp circle

var creating;               // current circle
var lastScale, lastPos;     // last scale/translation
    
var crimePts;               // All crime points

var timeStart = 0,          // time range filter
    timeEnd = 1440;

var dayOfWeek = {           // days of week 
    sunday: true,
    monday: true,
    tuesday: true,
    wednesday: true, 
    thursday: true,
    friday: true,
    saturday: true
};   

var categoriesSelected = {  //categories selected filter
    violent: true, 
    property: true,
    quality: true,
    corporate: true,
    other: true
};         

//categories
var violent = ["ARSON", "ASSAULT", "KIDNAPPING", "MISSING PERSON", "ROBBERY", "SEX OFFENSES, FORCIBLE", "SEX OFFENSES, NON FORCIBLE", "WEAPON LAWS"]
var property = ["BURGLARY", "LARCENY/THEFT", "STOLEN PROPERTY", "TRESPASS", "VANDALISM", "VEHICLE THEFT"]
var qualityOfLife = ["DRUG/NARCOTIC", "DRUNKENNESS", "FAMILY OFFENSES", "GAMBLING", "LIQUOR LAWS", "LOITERING", "PROSTITUTION", "SUICIDE", "SUSPICIOUS OCC", "DISORDERLY CONDUCT", "DRIVING UNDER THE INFUENCE"]
var corporate = ["BRIBERY", "EMBEZZLEMENT", "EXTORTION", "FORGERY/COUNTERFEITING", "FRAUD"]
var other = ["NON-CRIMINAL", "OTHER OFFENSES", "RUNAWAY", "SECONDARY CODES", "WARRENTS"]

//colors of categories
var violentColor = Array(violent.length).fill("#d62728");
var propertyColor = Array(property.length).fill("#ff7f0e");
var qualityOfLifeColor = Array(qualityOfLife.length).fill("#bcbd22");
var corporateColor = Array(corporate.length).fill("#9467bd");
var otherColor = Array(other.length).fill("#17becf");

d3.json('data/scpd_incidents.json', function(error, data) {
    if (error) {
        return console.warn(error);
    }          

    // define zoom behavior
    var zoom = d3.behavior.zoom().scaleExtent([1, 8]).on("zoom", zoomed);
    
    // Set up projection that map is using
    var projection = d3.geo.mercator()
        .center([-122.433701, 37.767683]) // San Francisco, roughly
        .scale(225000)
        .translate([width / 2, height / 2]);
    // Add an svg element to the DOM
    var svg = d3.select("#map").append("svg")
        .attr("width", width)
        .attr("height", height)
        .append("g")
        .call(zoom)
        .append("g");
    
    // define drag behavior
    var drag = d3.behavior.drag()
        .on("dragstart", dragStart)
        .on("drag", dragDuration)
        .on("dragend", dragEnd);
    
    // Add svg map at correct size, assumes map is saved in a subdirectory called "data"
    svg.append("image")
              .attr("id", "map")
              .attr("width", width)
              .attr("height", height)
              .attr("xlink:href", "data/sf-map.svg")
              .call(drag);

    // set up color scale, add points
  
    var categories = violent.concat(property);
    categories = categories.concat(qualityOfLife);
    categories = categories.concat(corporate);
    categories = categories.concat(other);
    
    var categoriesColor = violentColor.concat(propertyColor);
    categoriesColor = categoriesColor.concat(qualityOfLifeColor);
    categoriesColor = categoriesColor.concat(corporateColor);
    categoriesColor = categoriesColor.concat(otherColor);

    var colorScale = d3.scale.ordinal()
      .domain(categories)
      .range(categoriesColor);
    
    var dict = {};

    crimePts = svg.selectAll("circle").data(data.data);
    crimePts.enter()
        .append("circle")
        .attr("fill", function(d) { dict[d.Category] = colorScale(d.Category); return colorScale(d.Category); })
        .attr("r", 2)
        .attr("cx", function(incident) { return projection(incident.Location)[0]; })
        .attr("cy", function(incident) { return projection(incident.Location)[1]; })
        .attr("data-legend",function(d) { return d.name});

    function centerDrag() {
        var name = this.id.split('-', 1)[0];
        var x = d3.event.x;
        var y = d3.event.y;
        d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
        
        circles[name].center.x = x;
        circles[name].center.y = y;
        d3.select("#"+name)
            .attr("transform", "translate("+x+","+y+" ) scale("+circles[name].radius/2.0+", "+circles[name].radius/2.0+")");
            
        filterPoints();
    }
    var centerDrag = d3.behavior.drag()
		.on("dragstart", disableZoom)
		.on("drag", centerDrag)
		.on("dragend", enableZoom);
      
    var dragged;
    function circleDragStart() {
        dragged = d3.event.sourceEvent.target.id;
        appendTooltip();
        disableZoom();
    }
    function circleDragDuration() {
        if(d3.event.defaultPrevented) return;
            
        var point = d3.mouse(svg[0][0]);
        var p = {x:(point[0]-circles[dragged].center.x), y:(point[1]-circles[dragged].center.y)};
        circles[dragged].radius = Math.sqrt(p.x*p.x + p.y*p.y);
        circles[dragged].geo_radius = d3.geo.distance(projection.invert(point), projection.invert([circles[dragged].center.x, circles[dragged].center.y]))* 3959.0;
        

        //re-scale circle
        d3.select("#"+dragged)
            .attr("transform", "translate("+circles[dragged].center.x+","+circles[dragged].center.y+" ) scale("+circles[dragged].radius/2.0+")");
            
        filterPoints();
        if (!tooltipDiv) appendTooltip();
        updateTooltip();
        
        d3.select("#"+dragged+"-radius").html(Math.round(circles[dragged].geo_radius * 100) / 100+' mi');
    }
    function circleDragEnd() {
        d3.select('.tooltip').remove();
        enableZoom();
    }
        
    var circleDrag = d3.behavior.drag()
        .on("dragstart", circleDragStart)
        .on("drag", circleDragDuration)
        .on("dragend", circleDragEnd);
        
    var tooltipDiv;
    var bodyNode = d3.select('body').node();
    
    // add circle when drag starts
    function dragStart() {
        if(d3.event.defaultPrevented) return;

        if (creating) {
            disableZoom();
                
            //extract click location
            var point = d3.mouse(this);
            var p = {x:point[0], y:point[1]};
            tmp = {center: p, radius: 0.0, geo_radius: 0.0};

            d3.select("#tmp").remove();
            d3.select("#tmp-center").remove();
            
            //append new circle border
            svg.append("circle")
                .attr("r", "2px")
                .attr("transform", "translate("+p.x+","+p.y+")" )
                .attr("class", "region")
                .attr("id", "tmp")
                
            //append new circle center
            svg.append("circle")
                .attr("r", "7px")
                .attr("transform", "translate("+p.x+","+p.y+")" )
                .attr("class", "center")
                .attr("id", "tmp-center")
                .call(centerDrag);
            
            appendTooltip();
        }
    }
    
    function dragEnd() {
        if (creating) {
            if(d3.event.defaultPrevented) return;
                
            var point = d3.mouse(this);
            var p = {x:(point[0]-tmp.center.x), y:(point[1]-tmp.center.y)};
            tmp.radius = Math.sqrt(p.x*p.x + p.y*p.y);
            tmp.geo_radius = d3.geo.distance(projection.invert(d3.mouse(this)), projection.invert([tmp.center.x, tmp.center.y]))* 3959.0;
            
            //re-scale circle
            d3.select("#tmp")
                .attr("transform", "translate("+tmp.center.x+","+tmp.center.y+" ) scale("+tmp.radius/2.0+", "+tmp.radius/2.0+")")
                    
            // set or cancel
            replaceCircle(creating);
            
            if (circles['A'] && circles['B']) filterPoints();
           
            d3.select('.tooltip').remove();

            enableZoom();
        }
    }
    
    function dragDuration() {
        if (creating) {
                
            if(d3.event.defaultPrevented) return;
            
            var point = d3.mouse(this);
            var p = {x:(point[0]-tmp.center.x), y:(point[1]-tmp.center.y)};
            tmp.radius = Math.sqrt(p.x*p.x + p.y*p.y);
            tmp.geo_radius = d3.geo.distance(projection.invert(d3.mouse(this)), projection.invert([tmp.center.x, tmp.center.y]))* 3959.0;
            
            //re-scale circle
            d3.select("#tmp")
                .attr("transform", "translate("+tmp.center.x+","+tmp.center.y+" ) scale("+tmp.radius/2.0+")");
                
            updateTooltip();
        }
    }
    
    // filter points
    function filterPoints() {
        crimePts.style("display", function(d) {
            var overlapShow = true;
            var timeShow = false;
            var dayShow = true;
            var legendShow = true;
            
            if (circles['A'] && circles['B']) {
                var p = {x:projection(d.Location)[0], y:projection(d.Location)[1]}
                var p_A = {x:(p.x-circles['A'].center.x), y:(p.y-circles['A'].center.y)};
                var d1 = Math.sqrt(p_A.x*p_A.x + p_A.y*p_A.y);
                var p_B = {x:(p.x-circles['B'].center.x), y:(p.y-circles['B'].center.y)};
                var d2 = Math.sqrt(p_B.x*p_B.x + p_B.y*p_B.y);
                

                overlapShow = (d1 < circles['A'].radius && d2 < circles['B'].radius);
            }

            var time = d.Time.split(":");
            var hour = parseInt(time[0]);
            var minute = parseInt(time[1]);
            var timeMinutes = hour * 60 + minute;

            if (timeMinutes > timeStart && timeMinutes < timeEnd) {
                timeShow = true;
            }
            
            if (!dayOfWeek[d.DayOfWeek.toLowerCase()]) {
                dayShow = false;
            }
                
            var generalCategory = "";
            if(violent.indexOf(d.Category) > -1) {
                generalCategory = "violent";
            } else if(property.indexOf(d.Category) > -1) {
                generalCategory = "property";
            } else if(qualityOfLife.indexOf(d.Category) > -1) {
                generalCategory = "quality";
            } else if(corporate.indexOf(d.Category) > -1) {
                generalCategory = "corporate";
            } else if(other.indexOf(d.Category) > -1) {
                generalCategory = "other";
            }

            if (!categoriesSelected[generalCategory]) {
                legendShow = false;
            }

            return overlapShow && timeShow && dayShow && legendShow ? "" : "none";
        });
    }
    // replace a circle
    function replaceCircle(name) {
        if (d3.select("#"+name)) d3.select("#"+name).remove();
        if (d3.select("#"+name+"-center")) d3.select("#"+name+"-center").remove();
        
        circles[name] = tmp;
        d3.select("#tmp")
            .attr("id", name)            
            .call(circleDrag)
            .call(d3.helper.tooltip()
                .text(function(d, i){ 
                    var r  = circles[name].geo_radius;
                    return 'Distance: '+Math.round(r * 100) / 100+' mi'; 
                })
            );
        d3.select("#tmp-center")
            .attr("id", name+"-center");    
        
        filterPoints();
        d3.select("#"+name+"-radius").html(Math.round(circles[name].geo_radius * 100) / 100+' mi');
        
        $("#map").removeClass("pointer");
        $("#map").addClass("move");
        
        creating = '';
        
        $("#setA").removeClass("creating");
        $("#setB").removeClass("creating");
    }
    
    // tooltip functions 
    function appendTooltip() {
        var name, value;
        // Clean up lost tooltips
        d3.select('body').selectAll('.tooltip').remove();
        // Append tooltip
        tooltipDiv = d3.select('body').append('div').attr('class', 'tooltip');
        var absoluteMousePos = d3.mouse(bodyNode);
        tooltipDiv.style({
            left: (absoluteMousePos[0] + 10)+'px',
            top: (absoluteMousePos[1] - 15)+'px',
            position: 'absolute',
            'z-index': 1001
        });
        // Add text using the accessor function, Crop text arbitrarily
        tooltipDiv.html(function(d, i){
                var r  = tmp.geo_radius;
                return 'Distance: '+Math.round(r * 100) / 100+' mi'; 
            });
    }
    
    function updateTooltip() {
        if (tooltipDiv) {
            // Move tooltip
            var absoluteMousePos = d3.mouse(bodyNode);
            tooltipDiv.style({
                left: (absoluteMousePos[0] + 10)+'px',
                top: (absoluteMousePos[1] - 15)+'px',
                background: 'white'
            });
            // Keep updating the text, it could change according to position
            tooltipDiv.html(function(d, i){ 
                var r  = tmp.geo_radius;
                return 'Distance: '+Math.round(r * 100) / 100+' mi';             
            });
            
        }
    }
    
    // event listeners for circle deletion
    $("#deleteA").click(function(){
        circles['A'] = undefined;
        d3.select("#A").remove();
        d3.select("#A-center").remove();
        filterPoints();
    })
    $("#deleteB").click(function(){
        circles['B'] = undefined;
        d3.select("#B").remove();
        d3.select("#B-center").remove();
        filterPoints();
    })
    // event listeners for circle creation
    $("#setA").click(function(){
        if ($("#setA").hasClass("creating")) {
            $("#setA").removeClass("creating");
            creating = '';
            $("#map").removeClass("pointer");
            $("#map").addClass("move");
        } else {
            creating = 'A';
            $("#setB").removeClass("creating");
            $("#setA").addClass("creating");
            $("#map").addClass("pointer");
            $("#map").removeClass("move");
        }
    })
    $("#setB").click(function(){
        if ($("#setB").hasClass("creating")) {
            $("#setB").removeClass("creating");
            creating = '';
            $("#map").removeClass("pointer");
            $("#map").addClass("move");
        } else {
            creating = 'B';
            $("#setA").removeClass("creating");
            $("#setB").addClass("creating");
            $("#map").addClass("pointer");
            $("#map").removeClass("move");
        }
    })
    
    function disableZoom() {
        zoom.on("zoom", null);
        lastScale = zoom.scale();
        lastPos = zoom.translate();
    }
    function enableZoom() {
        zoom.scale(lastScale)
            .translate(lastPos)
            .on("zoom", zoomed);
    }
    
    // zoom code http://bl.ocks.org/linssen/7352810
    
    function zoomed() {
        svg.attr("transform",
            "translate(" + zoom.translate() + ")" +
            "scale(" + zoom.scale() + ")"
        );
    }

    function interpolateZoom (translate, scale) {
        var self = this;
        return d3.transition().duration(350).tween("zoom", function () {
            var iTranslate = d3.interpolate(zoom.translate(), translate),
                iScale = d3.interpolate(zoom.scale(), scale);
            return function (t) {
                if (!creating) {
                    zoom.scale(iScale(t))
                        .translate(iTranslate(t));
                } else {
                    zoom.scale(iScale(t));
                }
                zoomed();
            };
        });
    }
    function zoomClick() {
        var clicked = d3.event.target,
            direction = 1,
            factor = 0.2,
            target_zoom = 1,
            center = [width / 2, height / 2],
            extent = zoom.scaleExtent(),
            translate = zoom.translate(),
            translate0 = [],
            l = [],
            view = {x: translate[0], y: translate[1], k: zoom.scale()};

        d3.event.preventDefault();
        direction = (this.id === 'zoom_in') ? 1 : -1;
        target_zoom = zoom.scale() * (1 + factor * direction);

        if (target_zoom < extent[0] || target_zoom > extent[1]) { return false; }

        translate0 = [(center[0] - view.x) / view.k, (center[1] - view.y) / view.k];
        view.k = target_zoom;
        l = [translate0[0] * view.k + view.x, translate0[1] * view.k + view.y];
        
        if (!creating) {
            view.x += center[0] - l[0];
            view.y += center[1] - l[1];
        }

        interpolateZoom([view.x, view.y], view.k);
    }

    d3.selectAll('.zoom').on('click', zoomClick);

    /* time slider */
    $("#slider-range").slider({
        range: true,
        min: 0,
        max: 1440,
        step: 15,
        values: [0, 1440],
        slide: function (e, ui) {
            var hours1 = Math.floor(ui.values[0] / 60);
            var minutes1 = ui.values[0] - (hours1 * 60);

            timeStart = hours1 * 60 + minutes1;

            if (hours1.length == 1) hours1 = '0' + hours1;
            if (minutes1.length == 1) minutes1 = '0' + minutes1;
            if (minutes1 == 0) minutes1 = '00';
            if (hours1 >= 12) {
                if (hours1 == 12) {
                    hours1 = hours1;
                    minutes1 = minutes1 + " PM";
                } else {
                    hours1 = hours1 - 12;
                    minutes1 = minutes1 + " PM";
                }
            } else {
                hours1 = hours1;
                minutes1 = minutes1 + " AM";
            }
            if (hours1 == 0) {
                hours1 = 12;
                minutes1 = minutes1;
            }

            $('.slider-time').html(hours1 + ':' + minutes1);

            var hours2 = Math.floor(ui.values[1] / 60);
            var minutes2 = ui.values[1] - (hours2 * 60);

            timeEnd = hours2 * 60 + minutes2;

            if (hours2.length == 1) hours2 = '0' + hours2;
            if (minutes2.length == 1) minutes2 = '0' + minutes2;
            if (minutes2 == 0) minutes2 = '00';
            if (hours2 >= 12) {
                if (hours2 == 12) {
                    hours2 = hours2;
                    minutes2 = minutes2 + " PM";
                } else if (hours2 == 24) {
                    hours2 = 11;
                    minutes2 = "59 PM";
                } else {
                    hours2 = hours2 - 12;
                    minutes2 = minutes2 + " PM";
                }
            } else {
                hours2 = hours2;
                minutes2 = minutes2 + " AM";
            }

            $('.slider-time2').html(hours2 + ':' + minutes2);

            filterPoints();
            /*
            if a point's Time is within range of time1 and time2 then keep 
            otherwise filter out
            */
        }
    });
    
    
    var daysSeen = {}
    function toggleDay(event) {
        var day = $(event.target);

        if (day.hasClass("day")) {
            if (!daysSeen[event.target.id]) {
                if (day.hasClass("selected")) {
                    day.removeClass("selected");
                    dayOfWeek[event.target.id] = false;
                } else {
                    day.addClass("selected");
                    dayOfWeek[event.target.id] = true;
                }
                daysSeen[d3.event.sourceEvent.target.id] = true;
            }
            filterPoints();
        }
    }
    var dayDrag = d3.behavior.drag()
        .on("dragstart", function() {
            daysSeen = {};
            toggleDay(d3.event.sourceEvent)
        })
        .on("drag", function() {
            toggleDay(d3.event.sourceEvent);
        });
        
    d3.select("#day-range").call(dayDrag);

    var categoriesSeen = {}
    function toggleCategories(event) {
        var category = $(event.target);
        if (category.hasClass("legend")) {
            if (!categoriesSeen[event.target.id]) {
                if (category.hasClass("selected")) {
                    category.removeClass("selected");
                    categoriesSelected[event.target.id] = false;
                } else {
                    category.addClass("selected");
                    categoriesSelected[event.target.id] = true;
                }
                categoriesSeen[d3.event.sourceEvent.target.id] = true;
            }
            filterPoints();
        }
    }
    var categoryDrag = d3.behavior.drag()
        .on("dragstart", function() {
            categoriesSeen = {};
            toggleCategories(d3.event.sourceEvent);
        })
        .on("drag", function() {
            toggleCategories(d3.event.sourceEvent);
        });
        
    d3.select(".legend-scale").call(categoryDrag);
        

});

