﻿/**
* HDirections.
*/
function HDirections(map, container, provider, usev2) {
    this.map = map;
    this.container = container;
    this.route = null;
    this.provider = provider;
    this.usev2 = (usev2 !== undefined ? true : false);
}

HDirections.BASE_URL = "http://public.api.hitta.se/route/";
HDirections.BASE_URLv2 = "http://bf.public.api.hitta.se/route/v1/";
HDirections.PROVIDERS = { BASEFARM: { ID: '', STROKE_COLOR: "225, 0, 0" },
    ADJUSTED_HIERARCHIES: { ID: 'adjusted_hierarchies', STROKE_COLOR: "0, 225, 0" },
    STAGING: { ID: 'staging', STROKE_COLOR: "0, 0, 225" }
};


/**
* Event triggered when queried route is available.
*
* @event
*/
HDirections.EVENT_AVAILABLE = "hdirectionavailable";

/**
* Event triggered when queried route is available, polyline drawn on
* map instance and directions list added to DOM.
*
* @event
*/
HDirections.EVENT_DONE = "hdirectiondone";

/**
* Event triggered when query failed.
*
* @event
* @param {String} msg Error message.
*/
HDirections.EVENT_FAILED = "hdirectionerror";

/**
* Find directions. Use event to retrieve route when done.
* 
* @params {Array} waypoints Array of waypoints as HPointRT90.
*/
HDirections.prototype.find = function (waypoints) {
    this.initQuery(waypoints);
};

/**
* Init routing web service query for given waypoints.
*
* @params {}
* @private
*/
HDirections.prototype.initQuery = function (waypoints) {
    this.waypoints = waypoints;

    var url = this.usev2 ? this.constructQueryURLv2(waypoints) : this.constructQueryURL(waypoints);
    HMapHelpers.getJSONP(url, this.queryCallback, this);
};

/**
* Process directions data returned by web service.
* 
* @params {Object} data Data from JSONP callback.
* @private
*/
HDirections.prototype.queryCallback = function (data) {
    if (!data.route || data.route.error) {
        var msg = "Error.";
        if (data.route.error) {
            msg = data.route.error;
        }

        HEvent.trigger(this, HDirections.EVENT_FAILED, msg);
        return;
    }
    data.route.waypoints = this.waypoints;
    this.route = (this.usev2 ? new HRoute(data.route, this.provider) : new HRoute(data.route, null));

    HEvent.trigger(this, HDirections.EVENT_AVAILABLE);

    if (this.map) {
        this.drawRoute(this.route);
    }

    if (this.container) {
        this.generateDirections(this.route);
    }

    HEvent.trigger(this, HDirections.EVENT_DONE);
};

/**
* Construct URL for web service query.
*
* @params {Array} waypoints Array of waypoints as HPointRT90.
* @returns {String} URL to query web service for direcitons.
*/
HDirections.prototype.constructQueryURL = function (waypoints) {
    var stops = [];
    for (var i = 0; i < waypoints.length; i++) {
        stops.push('[' + waypoints[i].point.east + ',' + waypoints[i].point.north + ']');
    }

    var url = HDirections.BASE_URL + '?stops=[' + stops.join(',') + ']'
    return url;
};

/**
* Construct URL for web service query.
*
* @params {Array} waypoints Array of waypoints as HPointRT90.
* @returns {String} URL to query web service for direcitons.
*/
HDirections.prototype.constructQueryURLv2 = function (waypoints) {
    var url = HDirections.BASE_URLv2 + 'from/' + waypoints[0].point.east + ':' + waypoints[0].point.north + '/to/' + waypoints[waypoints.length-1].point.east + ':' + waypoints[waypoints.length-1].point.north + '/?provider=' + this.provider.ID
    return url;
};

/**
* Get current route.
*
* @returns {HRoute} Current HRoute instance.
*/
HDirections.prototype.getRoute = function () {
    return this.route;
};

/**
* Draw route as polyline to current HMap instance.
*
* @private {HRoute} route
* @private
*/
HDirections.prototype.drawRoute = function (route) {
    var poly = this.map.getPolyLayer();
    poly.addPoly(route.getPolyline());

    var bounds = route.getBounds();
    var zoomLevel = this.map.getBoundsZoomLevel(bounds);

    this.map.setCenter(bounds.getCenter(), Math.min(zoomLevel, 8));
};

/**
* Generate directions as html and set to container.
*
* @params {HRoute} route
* @private
*/
HDirections.prototype.generateDirections = function (route) {
    var stepCount = route.getStepCount();

    var html = '<table class="directionTable">';
    for (var i = 0; i < stepCount; i++) {
        html += route.getStep(i).getDescriptionRow();
    }
    html += '</table>';

    this.container.innerHTML = html;
};

/**
* Clear current route from map instance and container.
*/
HDirections.prototype.clear = function () {
    // clear polyline
    if (this.map) {
        var poly = this.map.getPolyLayer();
        poly.removePoly(this.route.getPolyline());
    }

    // remove direction descriptions
    if (this.container) {
        this.container.innerHTML = "";
    }
};

/**
* HRoute.
*/
function HRoute(data, provider) {
    this.path = data.path;
    this.waypoints = data.waypoints;
    this.directions = data.directions;
    this.bounds = null;
    this.distanceTotal = 0;
    this.provider = provider;

    this.polyline = this.constructPolyline(this.path);
    this.steps = this.constructSteps(this.directions);
}

/**
*
* @returns {HPolylineExtended}
*/
HRoute.prototype.getPolyline = function () {
    return this.polyline;
};

HRoute.prototype.getStepCount = function () {
    return this.steps.length;
};

HRoute.prototype.getStep = function (index) {
    return this.steps[index];
};

HRoute.prototype.getBounds = function () {
    if (this.bounds === null) {
        this.bounds = this.polyline.getBounds();

        // extend bounds to include all waypoints (since start/stop may not be part of route)
        for (var i = 0; i < this.waypoints.length; i++) {
            this.bounds.extend(this.waypoints[i].point);
        }
    }

    return this.bounds;
};

HRoute.prototype.getWaypoints = function () {
    return this.waypoints;
};

HRoute.prototype.getDistance = function () {
    return this.distanceTotal;
};


/**
*
* @private
*/
HRoute.prototype.constructPolyline = function (path) {
    var line = new HPolylineExtended();

    var polyStyle = new HPolyStyle();
    polyStyle.strokeColor = (this.provider !== null ? this.provider.STROKE_COLOR : "211, 8, 229");
    console.log(this.provider);
    polyStyle.width = "10px";
    polyStyle.weight = 5;
    polyStyle.strokeOpacity = 0.6;

    line.setStyle(polyStyle);

    var point = new HPointRT90(parseInt(path.startPoint.north), parseInt(path.startPoint.east));

    line.addVertex(point);

    var pathLength = path.offsetList.east.length;
    for (var i = 0; i < pathLength; i++) {
        path.offsetList.north[i] = parseInt(path.offsetList.north[i]);
        path.offsetList.east[i] = parseInt(path.offsetList.east[i]);

        point = new HPointRT90(point.north + path.offsetList.north[i], point.east + path.offsetList.east[i]);
        line.addVertex(point);
    }

    return line;
};

/**
*
* @private
*/
HRoute.prototype.constructSteps = function (directions) {
    var steps = [];

    var goalIndex = 1;

    var step;
    for (var i = 0; i < directions.length; i++) {
        step = new HStep(directions[i], i, this);

        // set waypoint position if step is a goal (since goal
        // and waypoint position may be different)
        if (step.type == HStep.TYPE_GOAL) {
            step.setGoalWaypoint(this.waypoints[goalIndex]);
            goalIndex++;
        }

        steps.push(step);
        this.distanceTotal += parseInt(directions[i].length);
    }

    return steps;
};

/**
* HStep.
*/
function HStep(data, count, route) {
    this.route = route;
    this.count = count;
    this.offset = parseInt(data.offsetIndex) + 1;
    this.location = new HPointRT90(parseInt(data.location.north), parseInt(data.location.east));
    this.text = data.text;
    this.type = HMapHelpers.makeArray(data.type);
    this.distanceStep = parseInt(data.length);
    this.distance = route.distanceTotal;
    this.goalWaypoint = null;
}

HStep.IMAGE_BASE_URL = "/images/route/";

HStep.TYPE_GOAL = 1;

HStep.WALK_TO_WAYPOINT_THRESHOLD = 50; // in meters

HStep.IMAGES = {
    0: {}, //unkown
    1: { file: "type.goal.gif", title: "Mål" }, //arrive at stop
    2: { file: "dirchange.Straight.gif", title: "Rakt fram" }, //go straight
    //3: { file: "dirchange.Left.gif", title: "Vänster" }, //bear left
    3: { file: "dirchange.Straight.gif", title: "Vänster" }, //bear left
    //4: { file: "dirchange.Right.gif", title: "Höger" }, // bear right
    4: { file: "dirchange.Straight.gif", title: "Höger" }, // bear right
    5: { file: "dirchange.Left.gif", title: "Vänster" }, // turn left
    6: { file: "dirchange.Right.gif", title: "Höger" }, //turn right
    7: { file: "dirchange.Left.gif", title: "Vänster" }, //make sharp left
    8: { file: "dirchange.Right.gif", title: "Höger" }, //make sharp right
    9: {}, //make u-turn
    10: { file: "type.ferryboarding.gif", title: "Färja" }, //take ferry
    11: { file: "type.roundabout.gif", title: "Rondell" }, //roundabout
    12: { file: "type.sliproad.gif", title: "Avfart" }, //merge to highway
    13: {}, //exit highway
    14: {}, //change highway
    15: [21, 2], //at fork keep center
    16: [21, 5], //at fork keep left
    17: [21, 6], //at fork keep right
    18: {}, //depart start
    19: {}, //trip planning item
    20: {}, //end of ferry
    21: { file: "type.branch.gif", title: "Avfart"} //branch (never used by ESRI internal use only)
};

HStep.prototype.getDescriptionRow = function () {
    var desc = this.getDescriptionImages();

    return '<tr class="directionRow" step="' + this.count + '">' +
           '<td class="directionCount">' + this.getStepIndex() + '.</td>' +
           '<td class="directionImages">' + (desc == "" ? "&nbsp;" : desc) + '</td>' +
           '<td class="directionText">' + this.getDescriptionText() + '</td>' +
           '<td class="directionDistance">' + HDirectionsUtils.distanceToString(this.getDistance()) + '</td>' +
           '</tr>';
};

HStep.prototype.getDescriptionText = function () {
    var text = this.text;

    // if step is a goal, append extra text if distance between goal and waypoint
    // is greater than threshold.
    if (this.type == HStep.TYPE_GOAL && this.goalWaypoint !== null) {
        var distance = this.location.distanceTo(this.goalWaypoint.point);
        if (distance > HStep.WALK_TO_WAYPOINT_THRESHOLD) {
            text += ". G&aring; " + HDirectionsUtils.distanceToString(distance) + " till " + this.goalWaypoint.desc + ".";
        }
    }

    return text;
};

HStep.prototype.getDescriptionImages = function () {
    // flatten array
    var i, j;
    var types = [];
    var img = null;
    for (i = 0; i < this.type.length; i++) {
        img = HStep.IMAGES[this.type[i]];
        if (HMapHelpers.isArray(img)) {
            for (j = 0; j < img.length; j++) {
                types.push(HStep.IMAGES[img[j]]);
            }
        } else {
            types.push(img);
        }
    }

    // construct string of html images
    var str = "";
    for (i = 0; i < types.length; i++) {
        if (types[i] && types[i].file) {
            str += "<img class=\"directionImage\" src=\"" + HStep.IMAGE_BASE_URL + types[i].file + "\" alt=\"" + types[i].title + "\" title=\"" + types[i].title + "\">"
        }
    }

    return str;
};

/**
* Returns the distance for this direction step.
*
* @returns {Number} distance in meter
*/
HStep.prototype.getDistanceStep = function () {
    return this.distanceStep;
};

/**
* Returns the distance from start up till this step.
*
* @returns {Number} distance in meter
*/
HStep.prototype.getDistance = function () {
    return this.distance;
};

HStep.prototype.getStepIndex = function () {
    return this.count + 1;
};

HStep.prototype.setGoalWaypoint = function (waypoint) {
    this.goalWaypoint = waypoint;
};

/**
* Get URL to image of current step.
* 
* @param {Number} width
* @param {Number} height
* @param {Number} zoomLevel
* @param {Number} mapType
*/
HStep.prototype.getImageUrl = function (width, height, zoomLevel, mapType) {
    zoomLevel = arguments[2] || 8;
    mapType = arguments[3] || HStandardMapProvider.MAP_TYPE_NORMAL;

    var resolution = HMap.ZOOM_LEVELS[zoomLevel];

    // calculate image size based on resolution
    var distWidth = resolution * width / 2;
    var distHeight = resolution * height / 2;

    // calculate image bounds
    var ne = new HPointRT90(this.location.north + distHeight, this.location.east + distWidth);
    var sw = new HPointRT90(this.location.north - distHeight, this.location.east - distWidth);
    var bounds = new HBoundsRT90(ne, sw);

    // note: get vertices from regular polyline, not extended. Since otherwise
    // offsets may be missaligned due to optimizations
    var polyline = this.route.getPolyline();

    var vertex;

    // get start offset within bounds
    var offset = this.offset - 1;
    while (offset >= 0) {
        vertex = polyline.getVertex(offset);
        if (!bounds.containsRT90(vertex)) {
            break;
        }
        offset--;
    }
    var startOffset = Math.max(offset, 0); // note: add extra point to make line extend outside image border

    // get end offset within bounds
    offset = this.offset;
    var count = polyline.getVertexCount();
    while (offset < count) {
        vertex = polyline.getVertex(offset);
        if (!bounds.containsRT90(vertex)) {
            break;
        }
        offset++;
    }
    var endOffset = Math.min(offset, count - 1); // note: add extra point to make line extend outside image border

    // generate url
    var startPoint = polyline.getVertex(startOffset);
    var offsetY = this.route.path.offsetList.east.slice(startOffset, endOffset);
    var offsetX = this.route.path.offsetList.north.slice(startOffset, endOffset);

    //console.log("X: " + offsetX.join(','), "Y: " + offsetY.join(','), " bounds:", bounds, " offset:" + this.offset + " startOffset:" + startOffset, " endOffset:" + endOffset, "offsetVertex: " + polyline.getVertex(this.offset).toString());

    var url = 'http://bf.public.api.hitta.se/image/v1/' +
        mapType + '/' +
        HMap.ZOOM_LEVELS[zoomLevel] + '/' +
        bounds.getCenter().north + '/' +
        bounds.getCenter().east + '/' +
        width + '/' +
        height +
        '?filter={name:route,props:{e:' + startPoint.east + ',n:' + startPoint.north + ',px:[' + offsetY.join(',') + '],py:[' + offsetX.join(',') + ']}}';

    return url;
};

HStep.prototype.getLocation = function () {
    return this.location;
};

var HDirectionsUtils = {

    /**
    * Returns distance as m or km with unit suffixed.
    *
    * @param {Number} dist
    * @returns {String}
    */
    distanceToString: function (dist) {
        if (dist < 1000) {
            return Math.round(dist) + " m";
        } else {
            return (dist / 1000).toFixed(1).replace('.', ',') + " km";
        }
    }

}

