/*
    Copyright 2008-2013
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
    
    You can redistribute it and/or modify it under the terms of the
    
      * GNU Lesser General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version
      OR
      * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
    
    JSXGraph 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 Lesser General Public License for more details.
    
    You should have received a copy of the GNU Lesser General Public License and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 utils/encoding
 utils/zip
 utils/type
 utils/base64
 utils/xml
  elements:
   point
   line
   conic
   parallel
   perpendicular
   intersection
   otherintersection
   glider
   bisector
   midpoint
   circumcircle
   tangent
   circle
   bisectorlines
   polygon
   arrow
 */

(function () {

    "use strict";

    JXG.IntergeoReader = function (board, str) {
        var xmlStr;

        this.board = board;
        xmlStr = this.prepareString(str);
        this.tree = JXG.XML.parse(xmlStr);
    };

    JXG.extend(JXG.IntergeoReader.prototype, /** @lends JXG.IntergeoReader.prototype */ {
        /**
         * this.objects holds all objects from the XML file.
         * Every object gets an attribute "exists"
         */
        objects: {},

        read: function () {
            this.board.origin = {};
            this.board.origin.usrCoords = [1, 0, 0];
            this.board.origin.scrCoords = [1, 400, 300];
            this.board.unitX = 30;
            this.board.unitY = 30;

            this.readElements(this.tree.getElementsByTagName("elements"));
            this.readConstraints(this.tree.getElementsByTagName("constraints"));
            this.cleanUp();
            this.board.fullUpdate();
            this.readDisplay(this.tree.getElementsByTagName("display"));
            this.board.fullUpdate();
        },

        readElement: function (tree, s) {
            var node;

            node = tree[0].childNodes[s];

            if (node.nodeType > 1) {
                // not an element node
                return;
            }

            if (node.nodeName === 'point') {
                this.storePoint(node);
            } else if (node.nodeName === 'line' || node.nodeName === 'line_segment' ||
                    node.nodeName === 'ray' || node.nodeName === 'vector') {
                this.storeLine(node);
            } else if (node.nodeName === 'circle') {
                this.storeConic(node);
            } else if (node.nodeName === 'conic') {
                this.storeConic(node);
                //} else if (node.nodeName === 'polygon') {
                // ignore, see this.addPolygonByVertices
            } else {
                JXG.debug('Not implemented: ' + node.nodeName + ' ' + node.getAttribute('id'));
            }
        },

        /**
         * Element part
         */
        readElements: function (tree) {
            var s;

            for (s = 0; s < tree[0].childNodes.length; s++) {
                this.readElement(tree, s);
            }
        },

        /**
         * Points are created instantly via create
         */
        storePoint: function (node) {
            var i = 0,
                j = 0,
                l = 0,
                p = node.childNodes[i],
                c = [],
                parents = [];

            // skip non element nodes
            while (p.nodeType > 1) {
                i += 1;
                p = node.childNodes[i];
            }

            if (p.nodeName === 'homogeneous_coordinates') {
                for (j = 0; j < p.childNodes.length; j++) {
                    if (p.childNodes[j].nodeType === 1) {
                        if (p.childNodes[j].nodeName === 'double') {
                            // content of <double>...</double>
                            c.push(p.childNodes[j].firstChild.data);
                        } else if (p.childNodes[j].nodeName === 'complex') {
                            for (l = 0; l < p.childNodes[j].childNodes.length; l++) {
                                if (p.childNodes[j].childNodes[l].nodeName === 'double') {
                                    c.push(p.childNodes[j].childNodes[l].firstChild.data);
                                }
                            }
                        } else {
                            // <complex>
                            JXG.debug('Not implemented: ' + p.childNodes[j].nodeName);
                            return;
                        }
                    }
                }
                for (j = 0; j < c.length; j++) {
                    c[j] = parseFloat(c[j]);
                }

                // Real
                if (c.length === 3) {
                    parents = [c[2], c[0], c[1]];
                // complex, but real
                } else if (c.length === 6 && Math.abs(c[1]) < 1e-10 && Math.abs(c[3]) < 1e-10 && Math.abs(c[5]) < 1e-10) {
                    parents = [c[4], c[0], c[2]];
                } else {
                    // <complex>
                    JXG.debug('type not supported, yet');
                    return;
                }
            // the latter one is a workaround for faulty i2g construction exported by DynaGeo
            } else if (p.nodeName === 'euclidean_coordinates' || p.nodeName === 'euclidian_coordinates') {
                for (j = 0; j < p.childNodes.length; j++) {
                    if (p.childNodes[j].nodeType === 1) {
                        // content of <double>...</double>
                        c.push(p.childNodes[j].firstChild.data);
                    }
                }
                for (j = 0; j < c.length; j++) {
                    c[j] = parseFloat(c[j]);
                }
                parents = [c[0], c[1]];
            } else if (p.nodeName === 'polar_coordinates') {
                for (j = 0; j < p.childNodes.length; j++) {
                    if (p.childNodes[j].nodeType === 1) {
                        // content of <double>...</double>
                        c.push(p.childNodes[j].firstChild.data);
                    }
                }
                for (j = 0; j < c.length; j++) {
                    c[j] = parseFloat(c[j]);
                }
                parents = [c[0] * Math.cos(c[1]), c[0] * Math.sin(c[1])];
            } else {
                JXG.debug('This coordinate type is not yet implemented: ' + p.nodeName);
                return;
            }

            this.objects[node.getAttribute('id')] = {'id': node.getAttribute('id'), 'coords': null};
            this.objects[node.getAttribute('id')].coords = parents;
            this.objects[node.getAttribute('id')].id = node.getAttribute('id');
            this.objects[node.getAttribute('id')].exists = false;
            this.objects[node.getAttribute('id')].i2geoType = 'point';
        },

        /**
         * Line data is stored in an array
         * for further access during the reading of constraints.
         * There, id and name are needed.
         */
        storeLine: function (node) {
            var i, p, c, j;

            this.objects[node.getAttribute('id')] = {'id': node.getAttribute('id'), 'coords': null};
            i = 0;
            p = node.childNodes[i];

            // skip non element nodes
            while (p.nodeType > 1) {
                i += 1;
                p = node.childNodes[i];
            }

            if (p.nodeName === 'homogeneous_coordinates') {
                c = [];
                for (j = 0; j < p.childNodes.length; j++) {
                    if (p.childNodes[j].nodeType === 1) {
                        if (p.childNodes[j].nodeName === 'double') {
                            // content of <double>...</double>
                            c.push(parseFloat(p.childNodes[j].firstChild.data));
                        }
                        // else {
                        //    $('debug').innerHTML += 'Not: '+ p.childNodes[j].nodeName + '<br>';  // <complex>
                        //}
                    }
                }
                this.objects[node.getAttribute('id')].coords = c;
                this.objects[node.getAttribute('id')].id = node.getAttribute('id');
                this.objects[node.getAttribute('id')].exists = false;
                this.objects[node.getAttribute('id')].i2geoType = 'line';
            }
        },

        /**
         * Circle / conic data is stored in an array
         * for further access during the reading of constraints.
         * There, id and name are needed.
         * Concretely, the circle   (x-1)^2 + (y-3)^2 = 4   has matrix
         * (  1  0 -1 )
         * (  0  1 -3 )
         * ( -1 -3  6 )
         *
         * In general
         * Ax^2+Bxy+Cy^2+Dx+Ey+F = 0
         * is stored as
         * (  A   B/2  D/2 )
         * (  B/2  C   E/2 )
         * (  D/2 E/2  F )
         *
         *  Mx = D/A
         *  My = E/C
         *  r = A*Mx^2+B*My^2-F
         */
        storeConic: function (node) {
            var i, j, p, c;

            this.objects[node.getAttribute('id')] = {'id': node.getAttribute('id'), 'coords': null};
            i = 0;
            p = node.childNodes[i];

            // skip non element nodes
            while (p.nodeType > 1) {
                i += 1;
                p = node.childNodes[i];
            }

            if (p.nodeName === 'matrix') {
                c = [];

                for (j = 0; j < p.childNodes.length; j++) {
                    if (p.childNodes[j].nodeType === 1) {
                        if (p.childNodes[j].nodeName === 'double') {
                            // content of <double>...</double>
                            c.push(parseFloat(p.childNodes[j].firstChild.data));
                        }
                        //else {
                        //    $('debug').innerHTML += 'Not: '+ p.childNodes[j].nodeName + '<br>';  // <complex>
                        //}
                    }
                }
                this.objects[node.getAttribute('id')].coords = c;
                this.objects[node.getAttribute('id')].id = node.getAttribute('id');
                this.objects[node.getAttribute('id')].exists = false;
                this.objects[node.getAttribute('id')].i2geoType = 'conic';
            }
        },

        /**
         * Constraint part
         */
        readConstraint: function (tree, s) {
            var param, node;

            node = tree[0].childNodes[s];

            // not an element node
            if (node.nodeType > 1) {
                return;
            }

            if (node.nodeName === 'line_through_two_points') {
                this.addLineThroughTwoPoints(node, false);
            } else if (node.nodeName === 'ray_from_point_through_point') {
                this.addLineThroughTwoPoints(node, true);
            } else if (node.nodeName === 'line_through_point') {
                this.addLineThroughPoint(node);
            } else if (node.nodeName === 'line_parallel_to_line_through_point') {
                this.addLineParallelToLineThroughPoint(node, false);
            } else if (node.nodeName === 'ray_from_point_and_vector') {
                this.addLineParallelToLineThroughPoint(node, true);
            } else if (node.nodeName === 'line_perpendicular_to_line_through_point') {
                this.addLinePerpendicularToLineThroughPoint(node);
            } else if (node.nodeName === 'line_segment_by_points') {
                this.addLineSegmentByTwoPoints(node);
            } else if (node.nodeName === 'vector_from_point_to_point') {
                this.addVectorFromPointToPoint(node);
            } else if (node.nodeName === 'endpoints_of_line_segment') {
                this.addEndpointsOfLineSegment(node);
            } else if (node.nodeName === 'free_line') {
                this.addFreeLine(node);
            } else if (node.nodeName === 'point_on_line') {
                this.addPointOnLine(node);
            } else if (node.nodeName === 'point_on_line_segment') {
                this.addPointOnLine(node);
            } else if (node.nodeName === 'point_on_circle') {
                this.addPointOnCircle(node);
            } else if (node.nodeName === 'angular_bisector_of_three_points') {
                this.addAngularBisectorOfThreePoints(node, false);
            } else if (node.nodeName === 'angular_bisectors_of_two_lines') {
                this.addAngularBisectorsOfTwoLines(node);
            } else if (node.nodeName === 'line_angular_bisector_of_three_points') {
                this.addAngularBisectorOfThreePoints(node, true);
            } else if (node.nodeName === 'line_angular_bisectors_of_two_lines') {
                this.addAngularBisectorsOfTwoLines(node);
            } else if (node.nodeName === 'midpoint_of_two_points') {
                this.addMidpointOfTwoPoints(node);
            } else if (node.nodeName === 'midpoint') {
                this.addMidpointOfTwoPoints(node);
            } else if (node.nodeName === 'midpoint_of_line_segment' || node.nodeName === 'midpoint_line_segment') {
                this.addMidpointOfLineSegment(node);
            } else if (node.nodeName === 'point_intersection_of_two_lines') {
                this.addPointIntersectionOfTwoLines(node);
            } else if (node.nodeName === 'locus_defined_by_point') {
                this.addLocusDefinedByPoint(node);
            } else if (node.nodeName === 'locus_defined_by_point_on_line') {
                this.addLocusDefinedByPointOnLine(node);
            } else if (node.nodeName === 'locus_defined_by_point_on_line_segment') {
                this.addLocusDefinedByPointOnLine(node);
            } else if (node.nodeName === 'locus_defined_by_line_through_point') {
                this.addLocusDefinedByLineThroughPoint(node);
            } else if (node.nodeName === 'locus_defined_by_point_on_circle') {
                this.addLocusDefinedByPointOnCircle(node);
            } else if (node.nodeName === 'circle_by_three_points') {
                this.addCircleByThreePoints(node);
            } else if (node.nodeName === 'circle_by_center_and_point') {
                this.addCircleByCenterAndPoint(node);
            } else if (node.nodeName === 'center_of_circle') {
                this.addCenterOfCircle(node);
            } else if (node.nodeName === 'intersection_points_of_two_circles') {
                this.addIntersectionPointsOfTwoCircles(node);
            } else if (node.nodeName === 'intersection_points_of_circle_and_line') {
                this.addIntersectionPointsOfCircleAndLine(node);
            } else if (node.nodeName === 'other_intersection_point_of_two_circles') {
                this.addOtherIntersectionPointOfTwoCircles(node);
            } else if (node.nodeName === 'other_intersection_point_of_circle_and_line') {
                this.addOtherIntersectionPointOfCircleAndLine(node);
            } else if (node.nodeName === 'circle_tangent_lines_by_point') {
                this.addCircleTangentLinesByPoint(node);
            } else if (node.nodeName === 'polygon_by_vertices') {
                this.addPolygonByVertices(node);
            //} else if (node.nodeName === 'free_point') {
            // do nothing
            } else {
                param = this.readParams(node);
                JXG.debug('readConstraints: not implemented: ' + node.nodeName + ': ' + param[0]);
            }
        },

        readConstraints: function (tree) {
            var s;

            // Set the default colors of dependent elements
            this.board.options.point.strokeColor = 'blue';
            this.board.options.point.fillColor = 'blue';

            for (s = 0; s < tree[0].childNodes.length; s++) {
                this.readConstraint(tree, s);
            }
        },

        setAttributes: function (o) {
            o.setAttribute({strokecolor: this.board.options.point.strokeColor, fillColor: this.board.options.point.fillColor});
        },

        readParams: function (node) {
            var j,
                param = [];

            for (j = 0; j < node.childNodes.length; j++) {
                if (node.childNodes[j].nodeType === 1) {
                    param.push(node.childNodes[j].firstChild.data);
                }
            }

            return param;
        },

        addPoint: function (p) {
            if (!p.exists) {
                p.exists = true;
                p = this.board.create('point', p.coords, {name: p.id});
                p.setAttribute({strokecolor: 'red', fillColor: 'red'});
            }

            return p;
        },

        /**
         * Direct construction of a line
         * in read elements
         */
        addLine: function (id) {
            var j,
                c = this.objects[id].coords,
                el;

            for (j = 0; j < c.length; j++) {
                c[j] = parseFloat(c[j]);
            }
            el = this.board.create('line', [c[2], c[0], c[1]], {name: id, strokeColor: 'black', withLabel: true});
            this.objects[id] = el;
        },

        addConic: function (p) {
            var c;

            if (!p.exists) {
                c = p.coords;
                // (a_00,a_11,a_22,a_01,a_12,a_22)
                p = this.board.create('conic', [c[0], c[4], c[8], c[1], c[5], c[2]], {name: p.id});
                p.exists = true;
            }

            return p;
        },

        cleanUp: function () {
            var p;

            for (p in this.objects) {
                if (this.objects.hasOwnProperty(p)) {
                    if (this.objects[p].exists === false) {
                        if (this.objects[p].i2geoType === 'point') {
                            this.addPoint(this.objects[p]);
                        } else if (this.objects[p].i2geoType === 'line') {
                            this.addLine(this.objects[p]);
                        } else if (this.objects[p].i2geoType === 'conic') {
                            this.addConic(this.objects[p]);
                        } else {
                            JXG.debug('forgotten: ' + this.objects[p].id + ' of type ' + this.objects[p].i2geoType);
                        }
                    }
                }
            }
        },

        addLineThroughTwoPoints: function (node, isRay) {
            var el1, el2, el,
                param = this.readParams(node);

            el1 = this.addPoint(this.objects[param[1]]);
            el2 = this.addPoint(this.objects[param[2]]);
            el = this.board.create('line', [el1.id, el2.id], {
                name: param[0],
                withLabel: true,
                straightFirst: !isRay,
                straightLast: true
            });
            this.objects[param[0]] = el;
            this.objects[param[0]].exists = true;
        },

        addLineThroughPoint: function (node) {
            var param = this.readParams(node),
                j,
                c = this.objects[param[0]].coords,
                p = this.addPoint(this.objects[param[1]]),
                el;

            for (j = 0; j < c.length; j++) {
                c[j] = parseFloat(c[j]);
            }

            el = this.board.create('line', [
                function () {
                    return c[2] - c[0] * p.X() - c[1] * p.Y() - c[2] * p.Z();
                },
                c[0],
                c[1]
            ], {name: param[0], strokeColor: 'black', withLabel: true});

            this.objects[param[0]] = el;
            this.objects[param[0]].exists = true;
        },

        addLineParallelToLineThroughPoint: function (node, isRay) {
            var el1, el2, el,
                param = this.readParams(node);

            el1 = this.addPoint(this.objects[param[1]]);
            el2 = this.addPoint(this.objects[param[2]]);
            el = this.board.create('parallel', [el1.id, el2.id], {
                name: param[0],
                withLabel: true,
                straightFirst: !isRay,
                straightLast: true
            });

            this.objects[param[0]] = el;
            this.objects[param[0]].exists = true;
        },

        addLinePerpendicularToLineThroughPoint: function (node) {
            var el1, el2, el,
                param = this.readParams(node);

            el1 = this.addPoint(this.objects[param[1]]);
            el2 = this.addPoint(this.objects[param[2]]);
            el = this.board.create('perpendicular', [el1.id, el2.id], {
                name: param[0],
                id: param[0],
                straightFirst: true,
                straightLast: true,
                point: {
                    name: param[0] + 'foot',
                    id: param[0] + 'foot'
                },
                withLabel: true
            });
            this.objects[param[0]] = el;
            this.objects[param[0]].exists = true;
        },

        addLineSegmentByTwoPoints: function (node) {
            var el1, el2, el,
                param = this.readParams(node);

            el1 = this.addPoint(this.objects[param[1]]);
            el2 = this.addPoint(this.objects[param[2]]);
            el = this.board.create('line', [el1.id, el2.id], {
                name: param[0],
                straightFirst: false,
                straightLast: false,
                strokeColor: 'black',
                withLabel: true
            });
            this.objects[param[0]] = el;
            this.objects[param[0]].exists = true;
        },

        addPointIntersectionOfTwoLines: function (node) {
            var param = this.readParams(node),
                l1 = this.objects[param[1]],
                l2 = this.objects[param[2]];

            this.objects[param[0]] = this.board.create('intersection', [l1, l2, 0], {
                name: param[0],
                id: param[0],
                withLabel: true
            });
            this.setAttributes(this.objects[param[0]]);
            this.objects[param[0]].exists = true;
        },

        addFreeLine: function (node) {
            var param = this.readParams(node),
                a = this.objects[param[0]].coords[0],
                b = this.objects[param[0]].coords[1],
                c = this.objects[param[0]].coords[2];

            this.objects[param[0]] = this.board.create('line', [c, a, b], {
                name: param[0],
                id: param[0],
                withLabel: true
            });
            this.objects[param[0]].exists = true;
        },

        addPointOnLine: function (node) {
            var param = this.readParams(node),
                l = this.board.select(param[1]);

            this.board.create('glider', [0, 0, l], {name: param[0], id: param[0], withLabel: true});
            this.objects[param[0]].exists = true;
        },

        addPointOnCircle: function (node) {
            var param = this.readParams(node),
                c = this.board.select(param[1]);

            c.update();
            this.board.create('glider', [this.objects[param[0]].coords[1], this.objects[param[0]].coords[2], c], {
                name: param[0],
                id: param[0],
                withLabel: true
            });

            this.objects[param[0]].exists = true;
        },

        addEndpointsOfLineSegment: function (node) {
            var param = this.readParams(node),
                line = this.objects[param[2]],
                p = this.addPoint(this.objects[param[0]]),
                q = this.addPoint(this.objects[param[1]]);

            p.addConstraint([
                function () {
                    return line.point1.Z();
                },
                function () {
                    return line.point1.X();
                },
                function () {
                    return line.point1.Y();
                }
            ]);

            q.addConstraint([
                function () {
                    return line.point2.Z();
                },
                function () {
                    return line.point2.X();
                },
                function () {
                    return line.point2.Y();
                }
            ]);

            this.setAttributes(p);
            this.setAttributes(q);
        },

        addAngularBisectorOfThreePoints: function (node, isLine) {
            var el1, el2, el3, el,
                param = this.readParams(node);

            el1 = this.addPoint(this.objects[param[1]]);
            el2 = this.addPoint(this.objects[param[2]]);
            el3 = this.addPoint(this.objects[param[3]]);

            el = this.board.create('bisector', [el1.id, el2.id, el3.id], {
                name: param[0],
                id: param[0],
                withLabel: true,
                straightFirst: isLine,
                straightLast: true,
                strokeColor: '#000000'
            });

            this.objects[param[0]] = el;
            this.objects[param[0]].exists = true;
        },

        addMidpointOfTwoPoints: function (node) {
            var el1, el2, el,
                param = this.readParams(node);

            el1 = this.addPoint(this.objects[param[1]]);
            el2 = this.addPoint(this.objects[param[2]]);

            el = this.board.create('midpoint', [el1.id, el2.id], { name: param[0]});

            this.setAttributes(el);
            this.objects[param[0]].exists = true;
        },

        addMidpointOfLineSegment: function (node) {
            var param = this.readParams(node),
                l = this.board.select(param[1]),
                el = this.board.create('midpoint', [l.point1, l.point2], {name: param[0]});

            this.setAttributes(el);
            this.objects[param[0]].exists = true;
        },

        addCircleByThreePoints: function (node) {
            var i,
                param = this.readParams(node),
                p = [];

            for (i = 0; i < 3; i++) {
                p[i] = this.addPoint(this.objects[param[i + 1]]);
            }

            this.board.create('circumcircle', p, {
                name: param[0],
                id: param[0],
                point: {
                    name: param[0] + 'c',
                    id: param[0] + 'c'
                },
                withLabel: true
            });
            this.objects[param[0]].exists = true;
        },

        addCenterOfCircle: function (node) {
            var param = this.readParams(node),
                c = this.board.select(param[1]),

                el = this.board.create('point', [
                    function () {
                        return c.center.X();
                    },
                    function () {
                        return c.center.Y();
                    }
                ], {name: param[0], id: param[0], withLabel: true});

            this.setAttributes(el);
            this.objects[param[0]].exists = true;
        },

        addCircleTangentLinesByPoint: function (node) {
            var polar, i1, i2, t1, t2,
                param = this.readParams(node),
                c = this.board.select(param[2]),
                p = this.addPoint(this.objects[param[3]]);

            polar = this.board.create('line', [
                function () {
                    return JXG.Math.matVecMult(c.quadraticform, p.coords.usrCoords)[0];
                },
                function () {
                    return JXG.Math.matVecMult(c.quadraticform, p.coords.usrCoords)[1];
                },
                function () {
                    return JXG.Math.matVecMult(c.quadraticform, p.coords.usrCoords)[2];
                }
            ], {visible: false});

            i1 = this.board.create('intersection', [c, polar, 0], {visible: false});
            i2 = this.board.create('intersection', [c, polar, 1], {visible: false});
            t1 = this.board.create('tangent', [i1, c]);
            t2 = this.board.create('tangent', [i2, c]);

            this.objects[param[0]] = t1;
            this.objects[param[1]] = t2;
            this.objects[param[0]].exists = true;
            this.objects[param[1]].exists = true;
        },

        addIntersectionPointsOfTwoCircles: function (node) {
            var p1, p2,
                param = this.readParams(node),
                c1 = this.board.select(param[2]),
                c2 = this.board.select(param[3]);

            p1 = this.board.create('intersection', [c1, c2, 0], {name: param[0], id: param[0], withLabel: true});
            p2 = this.board.create('intersection', [c1, c2, 1], {name: param[1], id: param[1], withLabel: true});
            this.setAttributes(p1);
            this.setAttributes(p2);
            this.objects[param[0]].exists = true;
            this.objects[param[1]].exists = true;
        },

        addIntersectionPointsOfCircleAndLine: function (node) {
            var p1, p2,
                param = this.readParams(node),
                c1 = this.board.select(param[2]),
                c2 = this.board.select(param[3]);

            p1 = this.board.create('intersection', [c1, c2, 0], {name: param[0], id: param[0], withLabel: true});
            p2 = this.board.create('intersection', [c1, c2, 1], {name: param[1], id: param[1], withLabel: true});
            this.setAttributes(p1);
            this.setAttributes(p2);
            this.objects[param[0]].exists = true;
            this.objects[param[1]].exists = true;
        },

        addCircleByCenterAndPoint: function (node) {
            var param = this.readParams(node),
                el1 = this.addPoint(this.objects[param[1]]),
                el2 = this.addPoint(this.objects[param[2]]);

            this.board.create('circle', [el1.id, el2.id], {
                name: param[0],
                id: param[0],
                withLabel: true
            });

            this.objects[param[0]].exists = true;
        },

        addOtherIntersectionPointOfTwoCircles: function (node) {
            var param = this.readParams(node),
                c1 = this.board.select(param[2]),
                c2 = this.board.select(param[3]),
                p1 = this.board.select(param[1]), // Should exist by now
                p2;

            p2 = this.board.create('otherintersection', [c1, c2, p1], {name: param[0], id: param[0], withLabel: true});
            this.setAttributes(p2);
            this.objects[param[0]].exists = true;
        },

        addOtherIntersectionPointOfCircleAndLine: function (node) {
            this.addOtherIntersectionPointOfTwoCircles(node);
        },

        /**
         * The angular bisectors of two line [c1,a1,b1] and [c2,a2,b2] are determined by the equation:
         * (a1*x+b1*y+c1*z)/sqrt(a1^2+b1^2) = +/- (a2*x+b2*y+c2*z)/sqrt(a2^2+b2^2)
         */
        addAngularBisectorsOfTwoLines: function (node) {
            var param = this.readParams(node),
                l1 = this.objects[param[2]],
                l2 = this.objects[param[3]];

            this.board.create('bisectorlines', [l1, l2], {
                line1: {
                    name: param[0],
                    id: param[0],
                    straightFirst: true,
                    straightLast: true
                },
                line2: {
                    name: param[1],
                    id: param[1],
                    straightFirst: true,
                    straightLast: true
                },
                withLabel: true
            });

            this.objects[param[0]].exists = true;
            this.objects[param[1]].exists = true;
        },

        addPolygonByVertices: function (node) {
            var j, n, param2 = [], p = [],
                param = this.readParams(node);

            for (j = 0; j < node.childNodes.length; j++) {
                if (node.childNodes[j].nodeType === 1) {
                    if (node.childNodes[j].nodeName === 'list_of_vertices') {
                        n = node.childNodes[j];
                        param2 = this.readParams(n);
                        break;
                    }
                }
            }
            for (j = 0; j < param2.length; j++) {
                p.push(this.addPoint(this.objects[param2[j]]));
            }

            this.board.create('polygon', p, {name: param[0], id: param[0], withLabel: true});
            this.objects[param[0]].exists = true;
        },

        addVectorFromPointToPoint: function (node) {
            var el, el1, el2,
                param = this.readParams(node);

            el1 = this.addPoint(this.objects[param[1]]);
            el2 = this.addPoint(this.objects[param[2]]);
            el = this.board.create('arrow', [el1.id, el2.id], {name: param[0]});
            this.setAttributes(el);
            this.objects[param[0]].exists = true;
        },

// ----------------------------------------------------------------------------------------------------

        addLocusDefinedByPoint: function (node) {
            var param = this.readParams(node),
                el = this.board.select(param[1]);

            el.setAttribute({trace: true});
            this.objects[param[1]] = el;
            this.setAttributes(el);
        },

        addLocusDefinedByPointOnLine: function (node) {
            var param = this.readParams(node),
                el = this.board.select(param[1]);

            el.setAttribute({trace: true});
            this.objects[param[1]] = el;
            this.setAttributes(el);
        },

        addLocusDefinedByLineThroughPoint: function (node) {
            var param = this.readParams(node),
                el = this.board.select(param[1]);

            el.setAttribute({trace: true});
            this.objects[param[1]] = el;
            this.setAttributes(el);
        },

        addLocusDefinedByPointOnCircle: function (node) {
            var param = this.readParams(node),
                el = this.board.select(param[1]);

            el.setAttribute({trace: true});
            this.objects[param[1]] = el;
            this.setAttributes(el);
        },

        /**
         * Extract the xml-code as String from the zipped Intergeo archive.
         * @param {String} fileStr
         * @returns {String} xml code
         */
        prepareString: function (fileStr) {
            var i,
                bA = [],
                isZip = false;

            if ((fileStr.slice(0, 2) === "PK" ||
                    JXG.Util.UTF8.asciiCharCodeAt(fileStr.slice(0, 1), 0) === 31)) {
                isZip = true;
            }

            // It's not a zip file but it doesn't start with '<' either -> base64!
            if (!isZip && fileStr.indexOf('<') !== 0) {
                fileStr = JXG.Util.Base64.decode(fileStr);
            }

            if (fileStr.indexOf('<') !== 0) {
                //binary = false;
                for (i = 0; i < fileStr.length; i++) {
                    bA[i] = JXG.Util.UTF8.asciiCharCodeAt(fileStr, i);
                }

                fileStr = (new JXG.Util.Unzip(bA)).unzipFile("construction/intergeo.xml");
                // Extract "construction/intergeo.xml" from
                // the zip-archive in bA.
            }

            return fileStr;
        },

        /**
         * Displpay part
         */

        readDisplayProperty: function (tree, s) {
            var j, node, el, prop = {}, key, val;

            node = tree[0].childNodes[s];

            // not an element node
            if (node.nodeType > 1) {
                return;
            }

            if (node.nodeName === 'background-color') {
                this.board.containerObj.style.backgroundColor = node.firstChild.data;
            } else if (node.nodeName === 'style') {
                // get the element
                el = this.board.select(node.getAttribute('ref'));

                for (j = 0; j < node.childNodes.length; j++) {
                    if (node.childNodes[j].nodeType === 1) {
                        key = node.childNodes[j].nodeName;
                        val = node.childNodes[j].firstChild.data;
                        if (key === 'stroke') {
                            key = 'strokeColor';
                        } else if (key === 'stroke-width' || key === 'border-width') {
                            key = 'strokeWidth';
                        } else if (key === 'fill') {
                            key = 'fillColor';
                        } else if (key === 'fill-opacity') {
                            key = 'fillOpacity';
                        } else if (key === 'border-opacity') {
                            key = 'strokeOpacity';
                        } else if (key === 'point-size') {
                            key = 'size';
                        } else if (key === 'label') {
                            key = 'name';
                        } else if (key === 'point-style') {
                            key = 'face';
                            if (val === 'circle') {
                                val = 'o';
                            } else if (val === 'cross') {
                                val = '+';
                            } else if (val === 'x-mark') {
                                val = 'x';
                            } else if (val === 'square') {
                                val = '[]';
                            } else if (val === 'triangle') {
                                val = 'triangleup';
                            } else if (val === 'point') {  // Setting size to 1 is missing
                                val = 'o';
                            } else {
                                JXG.debug('Display: not implemented' + node.nodeName);
                                // Missing:
                                // circumference, image
                            }
                        }
                        prop[key] = val;
                    }
                }
                el.setAttribute(prop);
            } else {
                JXG.debug('Display: not implemented' + node.nodeName);
            }
        },

        readDisplay: function (tree) {
            var s;

            if (!JXG.exists(tree) || !JXG.isArray(tree)) {
                return;
            }

            for (s = 0; s < tree[0].childNodes.length; s++) {
                this.readDisplayProperty(tree, s);
            }
        }

    });

    JXG.registerReader(JXG.IntergeoReader, ['i2g', 'xml', 'intergeo']);
}());