'use strict';

var util = require('util');
var assert = require('assert');

module.exports = function () {
    this.ShouldGetAResponse = () => {
        assert.equal(this.response.statusCode, 200);
        assert.ok(this.response.body);
        assert.ok(this.response.body.length);
    };

    this.ShouldBeValidJSON = (callback) => {
        try {
            this.json = JSON.parse(this.response.body);
            callback();
        } catch (e) {
            callback(e);
        }
    };

    this.ShouldBeWellFormed = () => {
        assert.equal(typeof this.json.status, 'number');
    };

    this.WhenIRouteIShouldGet = (table, callback) => {
        this.reprocessAndLoadData((e) => {
            if (e) return callback(e);
            var headers = new Set(table.raw()[0]);

            var requestRow = (row, ri, cb) => {
                var got;

                var afterRequest = (err, res, body) => {
                    if (err) return cb(err);
                    if (body && body.length) {
                        let destinations, exits, pronunciations, instructions, refs, bearings, turns, modes, times, classes,
                            distances, summary, intersections, lanes, locations, annotation, weight_name, weights, approaches,
                            driving_sides;

                        let json = JSON.parse(body);

                        got.code = json.code;

                        let hasRoute = json.code === 'Ok';

                        if (hasRoute) {
                            instructions = this.wayList(json.routes[0]);
                            pronunciations = this.pronunciationList(json.routes[0]);
                            refs = this.refList(json.routes[0]);
                            destinations = this.destinationsList(json.routes[0]);
                            exits = this.exitsList(json.routes[0]);
                            bearings = this.bearingList(json.routes[0]);
                            turns = this.turnList(json.routes[0]);
                            intersections = this.intersectionList(json.routes[0]);
                            modes = this.modeList(json.routes[0]);
                            driving_sides = this.drivingSideList(json.routes[0]);
                            classes = this.classesList(json.routes[0]);
                            times = this.timeList(json.routes[0]);
                            distances = this.distanceList(json.routes[0]);
                            lanes = this.lanesList(json.routes[0]);
                            summary = this.summary(json.routes[0]);
                            locations = this.locations(json.routes[0]);
                            annotation = this.annotationList(json.routes[0]);
                            weight_name = this.weightName(json.routes[0]);
                            weights = this.weightList(json.routes[0]);
                            approaches = this.approachList(json.routes[0]);
                        }

                        if (headers.has('status')) {
                            got.status = res.statusCode.toString();
                        }

                        if (headers.has('message')) {
                            got.message = json.message || '';
                        }

                        if (headers.has('data_version')) {
                            got.data_version = json.data_version || '';
                        }

                        if (headers.has('#')) {
                            // comment column
                            got['#'] = row['#'];
                        }

                        if (headers.has('geometry')) {
                            got.geometry = json.routes[0].geometry;
                        }

                        if (headers.has('route')) {
                            got.route = (instructions || '').trim();
                        }

                        if (headers.has('summary')) {
                            got.summary = (summary || '').trim();
                        }

                        if (headers.has('alternative')) {
                            // TODO examine more than first alternative?
                            got.alternative ='';
                            if (json.routes && json.routes.length > 1)
                                got.alternative = this.wayList(json.routes[1]);
                        }

                        var distance = hasRoute && json.routes[0].distance,
                            time = hasRoute && json.routes[0].duration,
                            weight = hasRoute && json.routes[0].weight;

                        if (headers.has('distance')) {
                            if (row.distance.length) {
                                if (!row.distance.match(/\d+m/))
                                    return cb(new Error('*** Distance must be specified in meters. (ex: 250m)'));
                                got.distance = instructions ? util.format('%dm', distance) : '';
                            } else {
                                got.distance = '';
                            }
                        }

                        if (headers.has('weight')) {
                            if (row.weight.length) {
                                if (!row.weight.match(/[\d.]+/))
                                    return cb(new Error('*** Weight must be specified as a numeric value. (ex: 8)'));
                                got.weight = instructions ? util.format('%d', weight) : '';
                            } else {
                                got.weight = '';
                            }
                        }

                        if (headers.has('time')) {
                            if (!row.time.match(/\d+s/))
                                return cb(new Error('*** Time must be specied in seconds. (ex: 60s)'));
                            got.time = instructions ? util.format('%ds', time) : '';
                        }

                        if (headers.has('lanes')) {
                            got.lanes = (lanes || '').trim();
                        }

                        if (headers.has('speed')) {
                            if (row.speed !== '' && instructions) {
                                if (!row.speed.match(/\d+ km\/h/))
                                    cb(new Error('*** Speed must be specied in km/h. (ex: 50 km/h)'));
                                var speed = time > 0 ? Math.round(3.6*distance/time) : null;
                                got.speed = util.format('%d km/h', speed);
                            } else {
                                got.speed = '';
                            }
                        }

                        if (headers.has('intersections')) {
                            got.intersections = (intersections || '').trim();
                        }

                        if (headers.has('locations')){
                            got.locations = (locations || '').trim();
                        }
                        if (headers.has('waypoints_count')) {
                            if ('waypoints' in json) {
                                got.waypoints_count = json.waypoints.length;
                            } else{
                                got.waypoints_count = 0;
                            }
                        }
                        /*
                        if (headers.has('approaches')){
                            got.approaches = (approaches || '').trim();
                        }*/
                        // if header matches 'a:*', parse out the values for *
                        // and return in that header
                        headers.forEach((k) => {
                            let whitelist = ['duration', 'distance', 'datasources', 'nodes', 'weight', 'speed' ];
                            let metadata_whitelist = [ 'datasource_names' ];
                            if (k.match(/^a:/)) {
                                let a_type = k.slice(2);
                                if (whitelist.indexOf(a_type) == -1)
                                    return cb(new Error('Unrecognized annotation field', a_type));
                                if (annotation && !annotation[a_type])
                                    return cb(new Error('Annotation not found in response', a_type));
                                got[k] = annotation && annotation[a_type] || '';
                            } else if (k.match(/^am:/)) {
                                let a_type = k.slice(3);
                                if (metadata_whitelist.indexOf(a_type) == -1)
                                    return cb(new Error('Unrecognized annotation field', a_type));
                                if (annotation && (!annotation.metadata || !annotation.metadata[a_type]))
                                    return cb(new Error('Annotation not found in response', a_type));
                                got[k] = (annotation && annotation.metadata && annotation.metadata[a_type]) || '';
                            }
                        });

                        var putValue = (key, value) => {
                            if (headers.has(key)) got[key] = instructions ? value : '';
                        };

                        putValue('ref', refs);
                        putValue('bearing', bearings);
                        putValue('turns', turns);
                        putValue('modes', modes);
                        putValue('classes', classes);
                        putValue('times', times);
                        putValue('distances', distances);
                        putValue('pronunciations', pronunciations);
                        putValue('destinations', destinations);
                        putValue('exits', exits);
                        putValue('weight_name', weight_name);
                        putValue('weights', weights);
                        putValue('weight', weight);
                        putValue('approach', approaches);

                        if (driving_sides) {
                            putValue('driving_side', driving_sides);
                        }

                        for (var key in row) {
                            if (this.FuzzyMatch.match(got[key], row[key])) {
                                got[key] = row[key];
                            }
                        }

                        cb(null, got);
                    } else {
                        cb(new Error('request failed to return valid body'));
                    }
                };

                if (headers.has('request')) {
                    got = { request: row.request };
                    this.requestUrl(row.request, afterRequest);
                } else {
                    var defaultParams = this.queryParams;
                    var userParams = [];
                    got = {};
                    for (var k in row) {
                        var match = k.match(/param:(.*)/);
                        if (match) {
                            if (row[k] === '(nil)') {
                                userParams.push([match[1], null]);
                            } else if (row[k]) {
                                userParams.push([match[1], row[k]]);
                            }
                            got[k] = row[k];
                        }
                    }

                    var params = this.overwriteParams(defaultParams, userParams),
                        waypoints = [],
                        bearings = [],
                        approaches = [];

                    if (row.bearings) {
                        got.bearings = row.bearings;
                        bearings = row.bearings.split(' ').filter(b => !!b);
                    }

                    if (row.approaches) {
                        got.approaches = row.approaches;
                        approaches = row.approaches.split(' ').filter(b => !!b);
                    }

                    if (row.from && row.to) {
                        var fromNode = this.findNodeByName(row.from);
                        if (!fromNode) return cb(new Error(util.format('*** unknown from-node "%s"', row.from)));
                        waypoints.push(fromNode);

                        var toNode = this.findNodeByName(row.to);
                        if (!toNode) return cb(new Error(util.format('*** unknown to-node "%s"', row.to)));
                        waypoints.push(toNode);

                        got.from = row.from;
                        got.to = row.to;
                        this.requestRoute(waypoints, bearings, approaches, params, afterRequest);
                    } else if (row.waypoints) {
                        row.waypoints.split(',').forEach((n) => {
                            var node = this.findNodeByName(n.trim());
                            if (!node) return cb(new Error(util.format('*** unknown waypoint node "%s"', n.trim())));
                            waypoints.push(node);
                        });
                        got.waypoints = row.waypoints;
                        this.requestRoute(waypoints, bearings, approaches, params, afterRequest);
                    } else {
                        return cb(new Error('*** no waypoints'));
                    }
                }
            };

            this.processRowsAndDiff(table, requestRow, callback);
        });
    };
};