// ==============================================================================
// Copyright (C) 2019 - Philip Paquette, Steven Bocco
//
//  This program is free software: you can redistribute it and/or modify it under
//  the terms of the GNU Affero General Public License as published by the Free
//  Software Foundation, either version 3 of the License, or (at your option) any
//  later version.
//
//  This program is distributed in the hope that it will be useful, but WITHOUT
//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
//  FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
//  details.
//
//  You should have received a copy of the GNU Affero General Public License along
//  with this program.  If not, see <https://www.gnu.org/licenses/>.
// ==============================================================================
import $ from "jquery";

const ARMY = 'Army';
const FLEET = 'Fleet';
// SVG tag names.
const PREFIX_TAG = 'jdipNS'.toLowerCase();
const TAG_ORDERDRAWING = 'jdipNS:ORDERDRAWING'.toLowerCase();
const TAG_POWERCOLORS = 'jdipNS:POWERCOLORS'.toLowerCase();
const TAG_POWERCOLOR = 'jdipNS:POWERCOLOR'.toLowerCase();
const TAG_SYMBOLSIZE = 'jdipNS:SYMBOLSIZE'.toLowerCase();
const TAG_PROVINCE_DATA = 'jdipNS:PROVINCE_DATA'.toLowerCase();
const TAG_PROVINCE = 'jdipNS:PROVINCE'.toLowerCase();
const TAG_UNIT = 'jdipNS:UNIT'.toLowerCase();
const TAG_DISLODGED_UNIT = 'jdipNS:DISLODGED_UNIT'.toLowerCase();
const TAG_SUPPLY_CENTER = 'jdipNS:SUPPLY_CENTER'.toLowerCase();
const TAG_DISPLAY = 'jdipNS:DISPLAY'.toLowerCase();

function attr(node, name) {
    return node.attributes[name].value;
}

function offset(floatString, offset) {
    return "" + (parseFloat(floatString) + offset);
}

export class Renderer {
    constructor(svgDomElement, game, mapData) {
        this.svg = svgDomElement;
        this.game = game;
        this.mapData = mapData;
        this.metadata = {
            color: {},
            symbol_size: {},
            orders: {},
            coord: {}
        };
        this.initialInfluences = {};
        this.__load_metadata();
        this.__save_initial_influences();
    }

    __hashed_id(id) {
        return `${id}___${this.svg.parentNode.id}`;
    }

    __svg_element_from_id(id) {
        const hashedID = this.__hashed_id(id);
        const element = this.svg.getElementById(hashedID);
        if (!element)
            throw new Error(`Unable to find ID ${id} (looked for hashed ID ${hashedID})`);
        return element;
    }

    __load_metadata() {
        // Order drawings.
        const order_drawings = this.svg.getElementsByTagName(TAG_ORDERDRAWING);
        if (!order_drawings.length)
            throw new Error('Unable to find order drawings (tag ' + TAG_ORDERDRAWING + ') in SVG map.');
        for (let order_drawing of order_drawings) {
            for (let child_node of order_drawing.childNodes) {
                if (child_node.nodeName === TAG_POWERCOLORS) {
                    // Power colors.
                    for (let power_color of child_node.childNodes) {
                        if (power_color.nodeName === TAG_POWERCOLOR) {
                            this.metadata.color[attr(power_color, 'power').toUpperCase()] = attr(power_color, 'color');
                        }
                    }
                } else if (child_node.nodeName === TAG_SYMBOLSIZE) {
                    // Symbol size.
                    this.metadata.symbol_size[attr(child_node, 'name')] = [attr(child_node, 'height'), attr(child_node, 'width')];
                } else if (child_node.nodeName.startsWith(PREFIX_TAG)) {
                    // Order type.
                    const order_type = child_node.nodeName.replace(PREFIX_TAG + ':', '');
                    this.metadata.orders[order_type] = {};
                    for (let attribute of child_node.attributes) {
                        if (!attribute.name.includes(':')) {
                            this.metadata.orders[order_type][attribute.name] = attribute.value;
                        }
                    }
                }
            }
        }
        // Object coordinates.
        const all_province_data = this.svg.getElementsByTagName(TAG_PROVINCE_DATA);
        if (!all_province_data.length)
            throw new Error('Unable to find province data in SVG map (tag ' + TAG_PROVINCE_DATA + ').');
        for (let province_data of all_province_data) {
            for (let child_node of province_data.childNodes) {
                // Province.
                if (child_node.nodeName === TAG_PROVINCE) {
                    const province = attr(child_node, 'name').toUpperCase().replace('-', '/');
                    this.metadata.coord[province] = {};
                    for (let coord_node of child_node.childNodes) {
                        if (coord_node.nodeName === TAG_UNIT) {
                            this.metadata.coord[province].unit = [attr(coord_node, 'x'), attr(coord_node, 'y')];
                        } else if (coord_node.nodeName === TAG_DISLODGED_UNIT) {
                            this.metadata.coord[province].disl = [attr(coord_node, 'x'), attr(coord_node, 'y')];
                        } else if (coord_node.nodeName === TAG_SUPPLY_CENTER) {
                            this.metadata.coord[province].sc = [attr(coord_node, 'x'), attr(coord_node, 'y')];
                        }
                    }
                }
            }
        }
        // Deleting.
        this.svg.removeChild(this.svg.getElementsByTagName(TAG_DISPLAY)[0]);
        this.svg.removeChild(this.svg.getElementsByTagName(TAG_ORDERDRAWING)[0]);
        this.svg.removeChild(this.svg.getElementsByTagName(TAG_PROVINCE_DATA)[0]);

        // (this code was previously in render())
        // Removing mouse layer.
        this.svg.removeChild(this.__svg_element_from_id('MouseLayer'));
    }

    __save_initial_influences() {
        const mapLayer = this.__svg_element_from_id('MapLayer');
        if (!mapLayer)
            throw new Error('Unable to find map layer.');
        for (let element of mapLayer.childNodes) {
            if (element.tagName === 'path') {
                this.initialInfluences[element.id] = element.getAttribute('class');
            }
        }
    }

    __restore_initial_influences() {
        for (let id of Object.keys(this.initialInfluences)) {
            const className = this.initialInfluences[id];
            this.svg.getElementById(id).setAttribute('class', className);
        }
    }

    __set_current_phase() {
        const current_phase = (this.game.phase[0] === '?' || this.game.phase === 'COMPLETED') ? 'FINAL' : this.game.phase;
        const phase_display = this.__svg_element_from_id('CurrentPhase');
        if (phase_display) {
            phase_display.childNodes[0].nodeValue = current_phase;
        }
    }

    __set_note(note1, note2) {
        note1 = note1 || '';
        note2 = note2 || '';
        const display_note1 = this.__svg_element_from_id('CurrentNote');
        const display_note2 = this.__svg_element_from_id('CurrentNote2');
        if (display_note1)
            display_note1.childNodes[0].nodeValue = note1;
        if (display_note2)
            display_note2.childNodes[0].nodeValue = note2;
    }

    __add_unit(unit, power_name, is_dislogged) {
        const split_unit = unit.split(/ +/);
        const unit_type = split_unit[0];
        const loc = split_unit[1];
        const dislogged_type = is_dislogged ? 'disl' : 'unit';
        const symbol = unit_type === 'F' ? FLEET : ARMY;
        const loc_x = offset(this.metadata.coord[loc][dislogged_type][0], -11.5);
        const loc_y = offset(this.metadata.coord[loc][dislogged_type][1], -10.0);
        // Helpful link about creating SVG elements: https://stackoverflow.com/a/25949237
        const node = document.createElementNS("http://www.w3.org/2000/svg", 'use');
        node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id((is_dislogged ? 'Dislodged' : '') + symbol));
        node.setAttribute('x', loc_x);
        node.setAttribute('y', loc_y);
        node.setAttribute('height', this.metadata.symbol_size[symbol][0]);
        node.setAttribute('width', this.metadata.symbol_size[symbol][1]);
        node.setAttribute('class', 'unit' + power_name.toLowerCase());
        node.setAttribute('diplomacyUnit', loc);
        const parent_node = this.__svg_element_from_id(is_dislogged ? 'DislodgedUnitLayer' : 'UnitLayer');
        if (parent_node)
            parent_node.appendChild(node);
    }

    __add_supply_center(loc, power_name) {
        const symbol = 'SupplyCenter';
        const loc_x = offset(this.metadata.coord[loc]['sc'][0], -8.5);
        const loc_y = offset(this.metadata.coord[loc]['sc'][1], -11.0);
        const node = document.createElementNS("http://www.w3.org/2000/svg", 'use');
        node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id(symbol));
        node.setAttribute('x', loc_x);
        node.setAttribute('y', loc_y);
        node.setAttribute('height', this.metadata.symbol_size[symbol][0]);
        node.setAttribute('width', this.metadata.symbol_size[symbol][1]);
        node.setAttribute('class', power_name ? ('sc' + power_name.toLowerCase()) : 'scnopower');
        const parent_node = this.__svg_element_from_id('SupplyCenterLayer');
        if (parent_node)
            parent_node.appendChild(node);
    }

    __set_influence(loc, power_name) {
        loc = loc.toUpperCase().substr(0, 3);
        if (!['LAND', 'COAST'].includes(this.mapData.getProvince(loc).type))
            return;
        const path = this.__svg_element_from_id('_' + loc.toLowerCase());
        if (!path || path.nodeName !== 'path') {
            throw new Error(`Unable to find SVG path for loc ${loc}, got ${path ? path.nodeName : '(nothing)'}`);
        }
        path.setAttribute('class', power_name ? power_name.toLowerCase() : 'nopower');
    }

    issueHoldOrder(loc, power_name) {
        const polygon_coord = [];
        const loc_x = offset(this.metadata['coord'][loc]['unit'][0], 8.5);
        const loc_y = offset(this.metadata['coord'][loc]['unit'][1], 9.5);
        for (let ofs of [
            [13.8, -33.3], [33.3, -13.8], [33.3, 13.8], [13.8, 33.3], [-13.8, 33.3],
            [-33.3, 13.8], [-33.3, -13.8], [-13.8, -33.3]]
            ) {
            polygon_coord.push(offset(loc_x, ofs[0]) + ',' + offset(loc_y, ofs[1]));
        }
        const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g');
        const poly_1 = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
        const poly_2 = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
        poly_1.setAttribute('stroke-width', '10');
        poly_1.setAttribute('class', 'varwidthshadow');
        poly_1.setAttribute('points', polygon_coord.join(' '));
        poly_2.setAttribute('stroke-width', '6');
        poly_2.setAttribute('class', 'varwidthorder');
        poly_2.setAttribute('points', polygon_coord.join(' '));
        poly_2.setAttribute('stroke', this.metadata['color'][power_name]);
        g_node.appendChild(poly_1);
        g_node.appendChild(poly_2);
        const orderLayer = this.__svg_element_from_id('Layer1');
        if (!orderLayer)
            throw new Error(`Unable to find svg order layer.`);
        orderLayer.appendChild(g_node);
    }

    issueMoveOrder(src_loc, dest_loc, power_name) {
        let src_loc_x = 0;
        let src_loc_y = 0;
        const phaseType = this.game.getPhaseType();
        if (phaseType === 'R') {
            src_loc_x = offset(this.metadata.coord[src_loc]['unit'][0], -2.5);
            src_loc_y = offset(this.metadata.coord[src_loc]['unit'][1], -2.5);
        } else {
            src_loc_x = offset(this.metadata.coord[src_loc]['unit'][0], 10);
            src_loc_y = offset(this.metadata.coord[src_loc]['unit'][1], 10);
        }
        let dest_loc_x = offset(this.metadata.coord[dest_loc]['unit'][0], 10);
        let dest_loc_y = offset(this.metadata.coord[dest_loc]['unit'][1], 10);

        // Adjusting destination
        const delta_x = parseFloat(dest_loc_x) - parseFloat(src_loc_x);
        const delta_y = parseFloat(dest_loc_y) - parseFloat(src_loc_y);
        const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
        dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - 30.) / vector_length * delta_x) * 100.) / 100.;
        dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - 30.) / vector_length * delta_y) * 100.) / 100.;

        // Creating nodes.
        const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g');
        const line_with_shadow = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        const line_with_arrow = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        line_with_shadow.setAttribute('x1', src_loc_x);
        line_with_shadow.setAttribute('y1', src_loc_y);
        line_with_shadow.setAttribute('x2', dest_loc_x);
        line_with_shadow.setAttribute('y2', dest_loc_y);
        line_with_shadow.setAttribute('class', 'varwidthshadow');
        line_with_shadow.setAttribute('stroke-width', '10');
        line_with_arrow.setAttribute('x1', src_loc_x);
        line_with_arrow.setAttribute('y1', src_loc_y);
        line_with_arrow.setAttribute('x2', dest_loc_x);
        line_with_arrow.setAttribute('y2', dest_loc_y);
        line_with_arrow.setAttribute('class', 'varwidthorder');
        line_with_arrow.setAttribute('marker-end', 'url(#' + this.__hashed_id('arrow') + ')');
        line_with_arrow.setAttribute('stroke', this.metadata.color[power_name]);
        line_with_arrow.setAttribute('stroke-width', '6');
        g_node.appendChild(line_with_shadow);
        g_node.appendChild(line_with_arrow);
        const orderLayer = this.__svg_element_from_id('Layer1');
        if (!orderLayer)
            throw new Error(`Unable to find svg order layer.`);
        orderLayer.appendChild(g_node);
    }

    issueSupportMoveOrder(loc, src_loc, dest_loc, power_name) {
        const loc_x = offset(this.metadata['coord'][loc]['unit'][0], 10);
        const loc_y = offset(this.metadata['coord'][loc]['unit'][1], 10);
        const src_loc_x = offset(this.metadata['coord'][src_loc]['unit'][0], 10);
        const src_loc_y = offset(this.metadata['coord'][src_loc]['unit'][1], 10);
        let dest_loc_x = offset(this.metadata['coord'][dest_loc]['unit'][0], 10);
        let dest_loc_y = offset(this.metadata['coord'][dest_loc]['unit'][1], 10);

        // Adjusting destination
        const delta_x = parseFloat(dest_loc_x) - parseFloat(src_loc_x);
        const delta_y = parseFloat(dest_loc_y) - parseFloat(src_loc_y);
        const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
        dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (vector_length - 30.) / vector_length * delta_x) * 100.) / 100.;
        dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (vector_length - 30.) / vector_length * delta_y) * 100.) / 100.;

        const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g');
        const path_with_shadow = document.createElementNS("http://www.w3.org/2000/svg", 'path');
        const path_with_arrow = document.createElementNS("http://www.w3.org/2000/svg", 'path');
        path_with_shadow.setAttribute('class', 'shadowdash');
        path_with_shadow.setAttribute('d', `M ${loc_x},${loc_y} C ${src_loc_x},${src_loc_y} ${src_loc_x},${src_loc_y} ${dest_loc_x},${dest_loc_y}`);
        path_with_arrow.setAttribute('class', 'supportorder');
        path_with_arrow.setAttribute('marker-end', 'url(#' + this.__hashed_id('arrow') + ')');
        path_with_arrow.setAttribute('stroke', this.metadata['color'][power_name]);
        path_with_arrow.setAttribute('d', `M ${loc_x},${loc_y} C ${src_loc_x},${src_loc_y} ${src_loc_x},${src_loc_y} ${dest_loc_x},${dest_loc_y}`);
        g_node.appendChild(path_with_shadow);
        g_node.appendChild(path_with_arrow);
        const orderLayer = this.__svg_element_from_id('Layer2');
        if (!orderLayer)
            throw new Error(`Unable to find svg order layer.`);
        orderLayer.appendChild(g_node);
    }

    issueSupportHoldOrder(loc, dest_loc, power_name) {
        const loc_x = offset(this.metadata['coord'][loc]['unit'][0], 10);
        const loc_y = offset(this.metadata['coord'][loc]['unit'][1], 10);
        let dest_loc_x = offset(this.metadata['coord'][dest_loc]['unit'][0], 10);
        let dest_loc_y = offset(this.metadata['coord'][dest_loc]['unit'][1], 10);

        const delta_x = parseFloat(dest_loc_x) - parseFloat(loc_x);
        const delta_y = parseFloat(dest_loc_y) - parseFloat(loc_y);
        const vector_length = Math.sqrt(delta_x * delta_x + delta_y * delta_y);
        dest_loc_x = '' + Math.round((parseFloat(loc_x) + (vector_length - 35.) / vector_length * delta_x) * 100.) / 100.;
        dest_loc_y = '' + Math.round((parseFloat(loc_y) + (vector_length - 35.) / vector_length * delta_y) * 100.) / 100.;

        const polygon_coord = [];
        const poly_loc_x = offset(this.metadata['coord'][dest_loc]['unit'][0], 8.5);
        const poly_loc_y = offset(this.metadata['coord'][dest_loc]['unit'][1], 9.5);
        for (let ofs of [
            [15.9, -38.3], [38.3, -15.9], [38.3, 15.9], [15.9, 38.3], [-15.9, 38.3], [-38.3, 15.9],
            [-38.3, -15.9], [-15.9, -38.3]
        ]) {
            polygon_coord.push(offset(poly_loc_x, ofs[0]) + ',' + offset(poly_loc_y, ofs[1]));
        }
        const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g');
        const shadow_line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        const support_line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        const shadow_poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
        const support_poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
        shadow_line.setAttribute('x1', loc_x);
        shadow_line.setAttribute('y1', loc_y);
        shadow_line.setAttribute('x2', dest_loc_x);
        shadow_line.setAttribute('y2', dest_loc_y);
        shadow_line.setAttribute('class', 'shadowdash');
        support_line.setAttribute('x1', loc_x);
        support_line.setAttribute('y1', loc_y);
        support_line.setAttribute('x2', dest_loc_x);
        support_line.setAttribute('y2', dest_loc_y);
        support_line.setAttribute('class', 'supportorder');
        support_line.setAttribute('stroke', this.metadata['color'][power_name]);
        shadow_poly.setAttribute('class', 'shadowdash');
        shadow_poly.setAttribute('points', polygon_coord.join(' '));
        support_poly.setAttribute('class', 'supportorder');
        support_poly.setAttribute('points', polygon_coord.join(' '));
        support_poly.setAttribute('stroke', this.metadata['color'][power_name]);
        g_node.appendChild(shadow_line);
        g_node.appendChild(support_line);
        g_node.appendChild(shadow_poly);
        g_node.appendChild(support_poly);
        const orderLayer = this.__svg_element_from_id('Layer2');
        if (!orderLayer)
            throw new Error(`Unable to find svg order layer.`);
        orderLayer.appendChild(g_node);
    }

    issueConvoyOrder(loc, src_loc, dest_loc, power_name) {
        const loc_x = offset(this.metadata['coord'][loc]['unit'][0], 10);
        const loc_y = offset(this.metadata['coord'][loc]['unit'][1], 10);
        const src_loc_x = offset(this.metadata['coord'][src_loc]['unit'][0], 10);
        const src_loc_y = offset(this.metadata['coord'][src_loc]['unit'][1], 10);
        let dest_loc_x = offset(this.metadata['coord'][dest_loc]['unit'][0], 10);
        let dest_loc_y = offset(this.metadata['coord'][dest_loc]['unit'][1], 10);

        const src_delta_x = parseFloat(src_loc_x) - parseFloat(loc_x);
        const src_delta_y = parseFloat(src_loc_y) - parseFloat(loc_y);
        const src_vector_length = Math.sqrt(src_delta_x * src_delta_x + src_delta_y * src_delta_y);
        const src_loc_x_1 = '' + Math.round((parseFloat(loc_x) + (src_vector_length - 30.) / src_vector_length * src_delta_x) * 100.) / 100.;
        const src_loc_y_1 = '' + Math.round((parseFloat(loc_y) + (src_vector_length - 30.) / src_vector_length * src_delta_y) * 100.) / 100.;

        let dest_delta_x = parseFloat(src_loc_x) - parseFloat(dest_loc_x);
        let dest_delta_y = parseFloat(src_loc_y) - parseFloat(dest_loc_y);
        let dest_vector_length = Math.sqrt(dest_delta_x * dest_delta_x + dest_delta_y * dest_delta_y);
        const src_loc_x_2 = '' + Math.round((parseFloat(dest_loc_x) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_x) * 100.) / 100.;
        const src_loc_y_2 = '' + Math.round((parseFloat(dest_loc_y) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_y) * 100.) / 100.;

        dest_delta_x = parseFloat(dest_loc_x) - parseFloat(src_loc_x);
        dest_delta_y = parseFloat(dest_loc_y) - parseFloat(src_loc_y);
        dest_vector_length = Math.sqrt(dest_delta_x * dest_delta_x + dest_delta_y * dest_delta_y);
        dest_loc_x = '' + Math.round((parseFloat(src_loc_x) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_x) * 100.) / 100.;
        dest_loc_y = '' + Math.round((parseFloat(src_loc_y) + (dest_vector_length - 30.) / dest_vector_length * dest_delta_y) * 100.) / 100.;

        const triangle_coord = [];
        const triangle_loc_x = offset(this.metadata['coord'][src_loc]['unit'][0], 10);
        const triangle_loc_y = offset(this.metadata['coord'][src_loc]['unit'][1], 10);
        for (let ofs of [[0, -38.3], [33.2, 19.1], [-33.2, 19.1]]) {
            triangle_coord.push(offset(triangle_loc_x, ofs[0]) + ',' + offset(triangle_loc_y, ofs[1]));
        }

        const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g');
        const src_shadow_line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        const dest_shadow_line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        const src_convoy_line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        const dest_convoy_line = document.createElementNS("http://www.w3.org/2000/svg", 'line');
        const shadow_poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
        const convoy_poly = document.createElementNS("http://www.w3.org/2000/svg", 'polygon');
        src_shadow_line.setAttribute('x1', loc_x);
        src_shadow_line.setAttribute('y1', loc_y);
        src_shadow_line.setAttribute('x2', src_loc_x_1);
        src_shadow_line.setAttribute('y2', src_loc_y_1);
        src_shadow_line.setAttribute('class', 'shadowdash');

        dest_shadow_line.setAttribute('x1', src_loc_x_2);
        dest_shadow_line.setAttribute('y1', src_loc_y_2);
        dest_shadow_line.setAttribute('x2', dest_loc_x);
        dest_shadow_line.setAttribute('y2', dest_loc_y);
        dest_shadow_line.setAttribute('class', 'shadowdash');

        src_convoy_line.setAttribute('x1', loc_x);
        src_convoy_line.setAttribute('y1', loc_y);
        src_convoy_line.setAttribute('x2', src_loc_x_1);
        src_convoy_line.setAttribute('y2', src_loc_y_1);
        src_convoy_line.setAttribute('class', 'convoyorder');
        src_convoy_line.setAttribute('stroke', this.metadata['color'][power_name]);

        dest_convoy_line.setAttribute('x1', src_loc_x_2);
        dest_convoy_line.setAttribute('y1', src_loc_y_2);
        dest_convoy_line.setAttribute('x2', dest_loc_x);
        dest_convoy_line.setAttribute('y2', dest_loc_y);
        dest_convoy_line.setAttribute('class', 'convoyorder');
        dest_convoy_line.setAttribute('marker-end', 'url(#' + this.__hashed_id('arrow') + ')');

        dest_convoy_line.setAttribute('stroke', this.metadata['color'][power_name]);

        shadow_poly.setAttribute('class', 'shadowdash');
        shadow_poly.setAttribute('points', triangle_coord.join(' '));

        convoy_poly.setAttribute('class', 'convoyorder');
        convoy_poly.setAttribute('points', triangle_coord.join(' '));
        convoy_poly.setAttribute('stroke', this.metadata['color'][power_name]);

        g_node.appendChild(src_shadow_line);
        g_node.appendChild(dest_shadow_line);
        g_node.appendChild(src_convoy_line);
        g_node.appendChild(dest_convoy_line);
        g_node.appendChild(shadow_poly);
        g_node.appendChild(convoy_poly);

        const orderLayer = this.__svg_element_from_id('Layer2');
        if (!orderLayer)
            throw new Error(`Unable to find svg order layer.`);
        orderLayer.appendChild(g_node);
    }

    issueBuildOrder(unit_type, loc, power_name) {
        const loc_x = offset(this.metadata['coord'][loc]['unit'][0], -11.5);
        const loc_y = offset(this.metadata['coord'][loc]['unit'][1], -10.);
        const build_loc_x = offset(this.metadata['coord'][loc]['unit'][0], -20.5);
        const build_loc_y = offset(this.metadata['coord'][loc]['unit'][1], -20.5);
        const symbol = unit_type === 'A' ? ARMY : FLEET;
        const build_symbol = 'BuildUnit';
        const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g');
        const symbol_node = document.createElementNS("http://www.w3.org/2000/svg", 'use');
        const build_node = document.createElementNS("http://www.w3.org/2000/svg", 'use');
        symbol_node.setAttribute('x', loc_x);
        symbol_node.setAttribute('y', loc_y);
        symbol_node.setAttribute('height', this.metadata['symbol_size'][symbol][0]);
        symbol_node.setAttribute('width', this.metadata['symbol_size'][symbol][1]);
        symbol_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id(symbol));
        symbol_node.setAttribute('class', `unit${power_name.toLowerCase()}`);
        build_node.setAttribute('x', build_loc_x);
        build_node.setAttribute('y', build_loc_y);
        build_node.setAttribute('height', this.metadata['symbol_size'][build_symbol][0]);
        build_node.setAttribute('width', this.metadata['symbol_size'][build_symbol][1]);
        build_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id(build_symbol));
        g_node.appendChild(build_node);
        g_node.appendChild(symbol_node);
        const orderLayer = this.__svg_element_from_id('HighestOrderLayer');
        if (!orderLayer)
            throw new Error(`Unable to find svg order layer.`);
        orderLayer.appendChild(g_node);
    }

    issueDisbandOrder(loc) {
        const phaseType = this.game.getPhaseType();
        let loc_x = 0;
        let loc_y = 0;
        if (phaseType === 'R') {
            loc_x = offset(this.metadata['coord'][loc]['unit'][0], -29.);
            loc_y = offset(this.metadata['coord'][loc]['unit'][1], -27.5);
        } else {
            loc_x = offset(this.metadata['coord'][loc]['unit'][0], -16.5);
            loc_y = offset(this.metadata['coord'][loc]['unit'][1], -15.);
        }
        const symbol = 'RemoveUnit';
        const g_node = document.createElementNS("http://www.w3.org/2000/svg", 'g');
        const symbol_node = document.createElementNS("http://www.w3.org/2000/svg", 'use');
        symbol_node.setAttribute('x', loc_x);
        symbol_node.setAttribute('y', loc_y);
        symbol_node.setAttribute('height', this.metadata['symbol_size'][symbol][0]);
        symbol_node.setAttribute('width', this.metadata['symbol_size'][symbol][1]);
        symbol_node.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#' + this.__hashed_id(symbol));
        g_node.appendChild(symbol_node);
        const orderLayer = this.__svg_element_from_id('HighestOrderLayer');
        if (!orderLayer)
            throw new Error(`Unable to find svg order layer.`);
        orderLayer.appendChild(g_node);
    }

    clear() {
        this.__set_note('', '');
        $(`#${this.__hashed_id('DislodgedUnitLayer')} use`).remove();
        $(`#${this.__hashed_id('UnitLayer')} use`).remove();
        $(`#${this.__hashed_id('SupplyCenterLayer')} use`).remove();
        $(`#${this.__hashed_id('Layer1')} g`).remove();
        $(`#${this.__hashed_id('Layer2')} g`).remove();
        $(`#${this.__hashed_id('HighestOrderLayer')} g`).remove();
        this.__restore_initial_influences();
    }

    render(includeOrders, orders) {
        // Setting phase and note.
        const nb_centers = [];
        for (let power of Object.values(this.game.powers)) {
            if (!power.isEliminated())
                nb_centers.push([power.name.substr(0, 3), power.centers.length]);
        }
        // Sort nb_centers by descending number of centers.
        nb_centers.sort((a, b) => {
            return -(a[1] - b[1]) || a[0].localeCompare(b[0]);
        });
        const nb_centers_per_power = nb_centers.map((couple) => (couple[0] + ': ' + couple[1])).join(' ');
        this.__set_current_phase();
        this.__set_note(nb_centers_per_power, this.game.note);

        // Adding units, supply centers, influence and orders.
        const scs = new Set(this.mapData.supplyCenters);
        for (let power of Object.values(this.game.powers)) {
            for (let unit of power.units)
                this.__add_unit(unit, power.name, false);
            for (let unit of Object.keys(power.retreats))
                this.__add_unit(unit, power.name, true);
            for (let center of power.centers) {
                this.__add_supply_center(center, power.name);
                this.__set_influence(center, power.name);
                scs.delete(center);
            }
            if (!power.isEliminated()) {
                for (let loc of power.influence) {
                    if (!this.mapData.supplyCenters.has(loc))
                        this.__set_influence(loc, power.name);
                }
            }

            if (includeOrders) {
                const powerOrders = (orders && orders.hasOwnProperty(power.name) && orders[power.name]) || [];
                for (let order of powerOrders) {
                    const tokens = order.split(/ +/);
                    if (!tokens || tokens.length < 3)
                        continue;
                    const unit_loc = tokens[1];
                    if (tokens[2] === 'H')
                        this.issueHoldOrder(unit_loc, power.name);
                    else if (tokens[2] === '-') {
                        const destLoc = tokens[tokens.length - (tokens[tokens.length - 1] === 'VIA' ? 2 : 1)];
                        this.issueMoveOrder(unit_loc, destLoc, power.name);
                    } else if (tokens[2] === 'S') {
                        const destLoc = tokens[tokens.length - 1];
                        if (tokens.includes('-')) {
                            const srcLoc = tokens[4];
                            this.issueSupportMoveOrder(unit_loc, srcLoc, destLoc, power.name);
                        } else {
                            this.issueSupportHoldOrder(unit_loc, destLoc, power.name);
                        }
                    } else if (tokens[2] === 'C') {
                        const srcLoc = tokens[4];
                        const destLoc = tokens[tokens.length - 1];
                        if ((srcLoc !== destLoc) && (tokens.includes('-'))) {
                            this.issueConvoyOrder(unit_loc, srcLoc, destLoc, power.name);
                        }
                    } else if (tokens[2] === 'B') {
                        this.issueBuildOrder(tokens[0], unit_loc, power.name);
                    } else if (tokens[2] === 'D') {
                        this.issueDisbandOrder(unit_loc);
                    } else if (tokens[2] === 'R') {
                        const srcLoc = tokens[1];
                        const destLoc = tokens[3];
                        this.issueMoveOrder(srcLoc, destLoc, power.name);
                    } else {
                        throw new Error(`Unknown error to render (${order}).`);
                    }
                }
            }
        }
        // Adding remaining supply centers.
        for (let remainingCenter of scs)
            this.__add_supply_center(remainingCenter, null);
    }

    update(game, mapData, showOrders, orders) {
        this.game = game;
        this.mapData = mapData;
        this.clear();
        this.render(showOrders, orders);
    }
}