Initial commit
authorJonas Sauer <jonas.sauer2@kit.edu>
Wed, 7 Dec 2022 14:24:57 +0000 (15:24 +0100)
committerJonas Sauer <jonas.sauer2@kit.edu>
Wed, 7 Dec 2022 14:24:57 +0000 (15:24 +0100)
55 files changed:
.gitignore [new file with mode: 0644]
Algorithms/MapServer/Algorithm.h [new file with mode: 0644]
Algorithms/MapServer/HelloWorld.h [new file with mode: 0644]
DataStructures/Geometry/CoordinateTree.h [new file with mode: 0644]
DataStructures/Geometry/Metric.h [new file with mode: 0644]
DataStructures/Geometry/Point.h [new file with mode: 0644]
DataStructures/Geometry/Rectangle.h [new file with mode: 0644]
DataStructures/Parameter.h [new file with mode: 0644]
DataStructures/Result.h [new file with mode: 0644]
Helpers/Assert.h [new file with mode: 0644]
Helpers/Console/CommandLineParser.h [new file with mode: 0644]
Helpers/ConstructorTags.h [new file with mode: 0644]
Helpers/Debug.h [new file with mode: 0644]
Helpers/FileSystem/FileSystem.h [new file with mode: 0644]
Helpers/Function.h [new file with mode: 0644]
Helpers/Helpers.h [new file with mode: 0644]
Helpers/HighlightText.h [new file with mode: 0644]
Helpers/IO/File.h [new file with mode: 0644]
Helpers/IO/IO.h [new file with mode: 0644]
Helpers/IO/Serialization.h [new file with mode: 0644]
Helpers/JSONString.h [new file with mode: 0644]
Helpers/Meta.h [new file with mode: 0644]
Helpers/Ranges/ReverseRange.h [new file with mode: 0644]
Helpers/String/Enumeration.h [new file with mode: 0644]
Helpers/String/String.h [new file with mode: 0644]
Helpers/TaggedInteger.h [new file with mode: 0644]
Helpers/Timer.h [new file with mode: 0644]
Helpers/Types.h [new file with mode: 0644]
Helpers/Vector/Permutation.h [new file with mode: 0644]
Helpers/Vector/Vector.h [new file with mode: 0644]
MapServer/HTTPServer.h [new file with mode: 0644]
MapServer/HalloWorldMapServer.cpp [new file with mode: 0644]
MapServer/Makefile [new file with mode: 0644]
MapServer/QueryStringParser.h [new file with mode: 0644]
MapServer/Responses/Response.h [new file with mode: 0644]
MapServer/Responses/ResponseJSON.h [new file with mode: 0644]
MapServer/Responses/ResponseText.h [new file with mode: 0644]
MapServer/Servlets/AlgorithmsAndParameterInformationServlet.h [new file with mode: 0644]
MapServer/Servlets/AlgorithmsInformationServlet.h [new file with mode: 0644]
MapServer/Servlets/BoundingBoxServlet.h [new file with mode: 0644]
MapServer/Servlets/FileServlet.h [new file with mode: 0644]
MapServer/Servlets/JSONServlet.h [new file with mode: 0644]
MapServer/Servlets/QueryServlet.h [new file with mode: 0644]
MapServer/Servlets/QueryServletWithVertexInput.h [new file with mode: 0644]
MapServer/Servlets/Servlet.h [new file with mode: 0644]
MapServer/mongoose/mongoose.c [new file with mode: 0644]
MapServer/mongoose/mongoose.h [new file with mode: 0644]
Sites/MapServer/earth.ico [new file with mode: 0644]
Sites/MapServer/favicon.ico [new file with mode: 0644]
Sites/MapServer/index.html [new file with mode: 0644]
Sites/MapServer/js/googleMapsApi.js [new file with mode: 0644]
Sites/MapServer/js/maps.js [new file with mode: 0644]
Sites/MapServer/js/prototype.js [new file with mode: 0644]
Sites/MapServer/main.css [new file with mode: 0644]
Sites/MapServer/mymap.ico [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..a509209
--- /dev/null
@@ -0,0 +1 @@
+MapServer/HalloWorldMapServer
diff --git a/Algorithms/MapServer/Algorithm.h b/Algorithms/MapServer/Algorithm.h
new file mode 100644 (file)
index 0000000..4158e96
--- /dev/null
@@ -0,0 +1,85 @@
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include "../../Helpers/Types.h"
+
+#include "../../DataStructures/Result.h"
+#include "../../DataStructures/Parameter.h"
+
+class Algorithm {
+
+public:
+    Algorithm() {}
+
+    virtual ~Algorithm() {}
+
+    virtual std::vector<Result> run(const Vertex sourceVertexId, const Vertex targetNodeId) = 0;
+
+    virtual std::vector<Result> runSourceOnly(const Vertex sourceVertexId) noexcept {
+        std::vector<Result> resultSet;
+        resultSet.push_back(Result("Source Node", KIT::green));
+        resultSet.back().nodes.push_back(Result::Node(sourceVertexId, "S"));
+        return resultSet;
+    }
+
+    virtual std::vector<Result> runTargetOnly(const Vertex targetNodeId) noexcept {
+        std::vector<Result> resultSet;
+        resultSet.push_back(Result("Target Node", KIT::green));
+        resultSet.back().nodes.push_back(Result::Node(targetNodeId, "T"));
+        return resultSet;
+    }
+
+    inline const std::map<std::string, Parameter>& getParameters() const noexcept {
+        return parameters;
+    }
+
+    inline std::map<std::string, Parameter>& getParameters() noexcept {
+        return parameters;
+    }
+
+protected:
+    inline void addParameter(Parameter p) noexcept {
+        parameters[p.name] = p;
+    }
+
+    inline void constParameter(const std::string& name, const std::string& value) noexcept {
+        std::map<std::string, Parameter>::iterator position = parameters.find(name);
+        if (position != parameters.end()) {
+            constParameters[position->first] = position->second;
+            constParameters[position->first].defaultValue = value;
+            parameters.erase(position);
+        }
+    }
+
+    template <typename T>
+    inline T getParameter(const std::string& name) noexcept {
+        std::map<std::string, Parameter>::iterator position = parameters.find(name);
+        if (position != parameters.end()) {
+            return position->second.getValue<T>();
+        } else {
+            position = constParameters.find(name);
+            if (position != constParameters.end()) {
+                return position->second.getValue<T>();
+            } else {
+                std::cout << "WARNING! Parameter " << name << " not found, returning: " << T() << std::endl;
+                return T();
+            }
+        }
+    }
+
+    template <typename T>
+    inline void setParameter(const std::string& name, const T& value) noexcept {
+        std::map<std::string, Parameter>::iterator position = parameters.find(name);
+        if (position != parameters.end()) {
+            position->second.value = std::to_string(value);
+        }
+    }
+
+    std::map<std::string, Parameter> parameters;
+
+private:
+    std::map<std::string, Parameter> constParameters;
+
+};
diff --git a/Algorithms/MapServer/HelloWorld.h b/Algorithms/MapServer/HelloWorld.h
new file mode 100644 (file)
index 0000000..f7d706e
--- /dev/null
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <iostream>
+#include <vector>
+#include <string>
+
+#include "Algorithm.h"
+
+#include "../../Helpers/Types.h"
+
+#include "../../DataStructures/Parameter.h"
+#include "../../DataStructures/Result.h"
+
+// Example implementation of an "Algorithm".
+// The example shows how to read parameter values from the browser and how to construct a Result object.
+class HelloWorld : public Algorithm {
+
+public:
+    HelloWorld() : Algorithm() {
+        // Adding four exemplary parameters for this algorithm.
+        // Each parameter is specified using at least five values:
+        // 1. First the type (written as string) which is used to select a suitable input form in the browser.
+        // 2. The parameter name (also as string) which is used by HTTP GET to transmit the parameter.
+        // 3. A String displayed to the user.
+        // 4. The default value of the parameter.
+        // 5. A bool that specifies if the algorithm should be reevaluated upon parameter changes.
+        addParameter(Parameter("int", "anInt", "some integer value", "42", true));
+        addParameter(Parameter("bool", "aBool", "a checkbox", "true", true));
+        addParameter(Parameter("double", "aDouble", "some double value", "3.1415926", true));
+        addParameter(Parameter("time", "aTime", "some time", "3723", true));
+    }
+
+    // This method is executed every time this algorithm is requested by a user from the browser.
+    // You can also overwrite versions of this requiring only a source Node or a target Node.
+    // sourceVertexId (respectively targetVertexId) is the index from your coordinates vector that is closest to the point clicked by the user.
+    virtual std::vector<Result> run(const Vertex sourceVertexId, const Vertex targetVertexId) noexcept {
+        // Use getParameter<>() to get the value of a parameter.
+        int anInt = getParameter<int>("anInt");
+        bool aBool = getParameter<bool>("aBool");
+        double aDouble = getParameter<double>("aDouble");
+        int aTime = getParameter<int>("aTime");
+        std::cout << "You are running the Hello World Algorithm!" << std::endl
+                  << "Your input parameters are:" << std::endl
+                  << "   anInt   = " << anInt << std::endl
+                  << "   aBool   = " << aBool << std::endl
+                  << "   aDouble = " << aDouble << std::endl
+                  << "   aTime   = " << aTime << " (" << String::secToString(aTime) << ")" << std::endl;
+        // A path (displayed as polyline) is a vector of coordinate indices.
+        std::vector<Vertex> path({sourceVertexId, targetVertexId});
+        // Construction of one result object. Your algorithm may return multiple of these results in a vector.
+        // The string used as constructor parameter is displayed to the user. You may use HTML within this string.
+        Result result("Hello World");
+        // The color of the result
+        result.color = KIT::green;
+        // Single nodes/vertices that should be displayed on the map.
+        result.nodes.push_back(Result::Node(sourceVertexId, "Source", KIT::blue));
+        result.nodes.push_back(Result::Node(targetVertexId, "Target", KIT::red));
+        // A display a path on the map
+        result.pathes.push_back(Result::Path(path, "A Path", KIT::orange));
+        // You may want to change (or correct) a parameter value:
+        setParameter("aTime", 7302);
+        return std::vector<Result>({result});
+    }
+
+};
diff --git a/DataStructures/Geometry/CoordinateTree.h b/DataStructures/Geometry/CoordinateTree.h
new file mode 100644 (file)
index 0000000..93655a3
--- /dev/null
@@ -0,0 +1,319 @@
+#pragma once
+
+#include <cmath>
+#include <vector>
+#include <sstream>
+
+#include "Point.h"
+#include "Rectangle.h"
+#include "Metric.h"
+
+#include "../../Helpers/Types.h"
+#include "../../Helpers/Assert.h"
+#include "../../Helpers/Vector/Vector.h"
+#include "../../Helpers/Vector/Permutation.h"
+#include "../../Helpers/Debug.h"
+
+template<typename METRIC = Geometry::EuclideanMetric>
+class CoordinateTree {
+
+private:
+    // Data for leaf nodes: Interval of points represented by the leaf
+    struct LeafNodeData {
+        int begin;
+        int end;
+    };
+
+    // Data for inner nodes: Index of split dimension and max (respectively min) value for the splitDimension of the child nodes
+    struct InnerNodeDate {
+        int splitDimension;
+        double minChildMax;
+        double maxChildMin;
+    };
+
+    // Node label: Contains child Node indices and inner/leaf node data
+    struct Node {
+        inline bool isLeaf() const {return (minChild < 0) && (maxChild < 0);}
+        inline void makeLeaf() {minChild = -1; maxChild = -1;}
+        int minChild;
+        int maxChild;
+        union {
+            LeafNodeData leaf;
+            InnerNodeDate inner;
+        };
+    };
+
+public:
+    CoordinateTree(const METRIC& metric, const std::vector<Geometry::Point>& coordinates, const int maxLeafSize = 10) :
+        metric(metric),
+        coordinates(coordinates),
+        vertexCount(coordinates.size()),
+        maxLeafSize(maxLeafSize),
+        order(Vector::id<Vertex>(numVertices())),
+        nearestVertex(noVertex),
+        nearestDistance(-1),
+        minimumDistance(-1) {
+        Assert(!coordinates.empty());
+        nodes.reserve((coordinates.size() / maxLeafSize) * 4);
+        makeBoundingBox(0, numVertices(), boundingBox);
+        divideTree(newNode(), 0, numVertices(), boundingBox);
+    }
+    CoordinateTree(const std::vector<Geometry::Point>& coordinates, const int maxLeafSize = 10) :
+        coordinates(coordinates),
+        vertexCount(coordinates.size()),
+        maxLeafSize(maxLeafSize),
+        order(Vector::id<Vertex>(numVertices())),
+        nearestVertex(noVertex),
+        nearestDistance(-1),
+        minimumDistance(-1) {
+        Assert(!coordinates.empty());
+        nodes.reserve((coordinates.size() / maxLeafSize) * 4);
+        makeBoundingBox(0, numVertices(), boundingBox);
+        divideTree(newNode(), 0, numVertices(), boundingBox);
+    }
+
+    inline Vertex getNearestNeighbor(const Geometry::Point& p) const noexcept {
+        nearestVertex = Vertex(0);
+        nearestDistance = metric.distanceSquare(p, coordinates[nearestVertex]);
+        this->p = p;
+        searchLevel<false>(0, boundingBox.closestPoint(p));
+        return nearestVertex;
+    }
+
+    inline Vertex getNearestNeighbor(const Geometry::Point& p, const double minDistance) const noexcept {
+        nearestVertex = noVertex;
+        nearestDistance = metric.distanceSquare(boundingBox.min, boundingBox.max) + 1;
+        this->p = p;
+        this->minimumDistance = minDistance * minDistance;
+        searchLevel<true>(0, boundingBox.closestPoint(p));
+        return nearestVertex;
+    }
+
+    inline std::vector<Vertex> getNeighbors(const Geometry::Point& p, const double maxDistance) const noexcept {
+        neighbors.clear();
+        nearestDistance = maxDistance * maxDistance;
+        this->p = p;
+        searchNeighbors(0, boundingBox.closestPoint(p));
+        return neighbors;
+    }
+
+    inline size_t numVertices() const noexcept {
+        return vertexCount;
+    }
+
+    inline const Geometry::Point& getCoordinates(const Vertex vertex) const noexcept {
+        return coordinates[vertex];
+    }
+
+    inline void clear() noexcept {
+        order.clear();
+        nodes.clear();
+    }
+
+    inline const std::vector<Vertex>& getOrder() const noexcept {
+        return order;
+    }
+
+    inline size_t numNodes() const noexcept {
+        return nodes.size();
+    }
+
+    inline long long byteSize() const noexcept {
+        return sizeof(*this) + Vector::byteSize(order) + Vector::byteSize(nodes);
+    }
+
+    inline void check() const noexcept {
+        std::vector<bool> seen(numVertices());
+        check(0, boundingBox, seen);
+        for (int i = 0; i < seen.size(); i++) {
+            Assert(seen[i]);
+        }
+    }
+
+private:
+    void divideTree(const int nodeIndex, const int beginIndex, const int endIndex, const Geometry::Rectangle& boundingBox) noexcept {
+        AssertMsg(isBoundingBox(beginIndex, endIndex, boundingBox), boundingBoxError(beginIndex, endIndex, boundingBox));
+        AssertMsg(endIndex > beginIndex, "endIndex = " << endIndex << " <= beginIndex = " << beginIndex << "!");
+        AssertMsg(isNode(nodeIndex), "Index = " << nodeIndex << " is not a node!");
+
+        Node& node = nodes[nodeIndex];
+        if ((endIndex - beginIndex) <= maxLeafSize || boundingBox.isPoint()) {
+            node.makeLeaf();
+            node.leaf.begin = beginIndex;
+            node.leaf.end = endIndex;
+        } else {
+            if (metric.spread(boundingBox, 0) > metric.spread(boundingBox, 1)) {
+                node.inner.splitDimension = 0;
+            } else {
+                node.inner.splitDimension = 1;
+            }
+            double splitValue = boundingBox.center()[node.inner.splitDimension];
+            Geometry::Rectangle minBoundingBox(Geometry::Rectangle::Negative());
+            Geometry::Rectangle maxBoundingBox(Geometry::Rectangle::Negative());
+            int i = beginIndex;
+            int j = endIndex - 1;
+            while (i <= j) {
+                while (i <= j && coordinates[order[i]][node.inner.splitDimension] < splitValue) {
+                    minBoundingBox.extend(coordinates[order[i++]]);
+                }
+                while (i <= j && coordinates[order[j]][node.inner.splitDimension] >= splitValue) {
+                    maxBoundingBox.extend(coordinates[order[j--]]);
+                }
+                if (i >= j) break;
+                std::swap(order[i], order[j]);
+                minBoundingBox.extend(coordinates[order[i++]]);
+                maxBoundingBox.extend(coordinates[order[j--]]);
+            }
+            Assert(i - 1 == j);
+            Assert(i > beginIndex);
+            Assert(i < endIndex);
+            node.inner.minChildMax = minBoundingBox.max[node.inner.splitDimension];
+            node.inner.maxChildMin = maxBoundingBox.min[node.inner.splitDimension];
+            const int minChild = newNode();
+            divideTree(minChild, beginIndex, i, minBoundingBox);
+            const int maxChild = newNode();
+            divideTree(maxChild, i, endIndex, maxBoundingBox);
+            nodes[nodeIndex].minChild = minChild;
+            nodes[nodeIndex].maxChild = maxChild;
+        }
+    }
+
+    template<bool MIN_DISTANCE = false>
+    void searchLevel(const int nodeIndex, const Geometry::Point& closest) const noexcept {
+        AssertMsg(isNode(nodeIndex), "Index = " << nodeIndex << " is not a node!");
+
+        const Node& node = nodes[nodeIndex];
+        if (node.isLeaf()) {
+            for (int i = node.leaf.begin; i < node.leaf.end; i++) {
+                const double distance = metric.distanceSquare(p, coordinates[order[i]]);
+                if (nearestDistance <= distance) continue;
+                if constexpr (MIN_DISTANCE) if (distance <= minimumDistance) continue;
+                nearestDistance = distance;
+                nearestVertex = order[i];
+            }
+        } else {
+            Geometry::Point minClosest = closest;
+            minClosest[node.inner.splitDimension] = std::min(closest[node.inner.splitDimension], node.inner.minChildMax);
+            const double minChildDistance = metric.distanceSquare(p, minClosest);
+            Geometry::Point maxClosest = closest;
+            maxClosest[node.inner.splitDimension] = std::max(closest[node.inner.splitDimension], node.inner.maxChildMin);
+            const double maxChildDistance = metric.distanceSquare(p, maxClosest);
+            if (minChildDistance < maxChildDistance) {
+                if (nearestDistance <= minChildDistance) return;
+                searchLevel<MIN_DISTANCE>(node.minChild, minClosest);
+                if (nearestDistance <= maxChildDistance) return;
+                searchLevel<MIN_DISTANCE>(node.maxChild, maxClosest);
+            } else {
+                if (nearestDistance <= maxChildDistance) return;
+                searchLevel<MIN_DISTANCE>(node.maxChild, maxClosest);
+                if (nearestDistance <= minChildDistance) return;
+                searchLevel<MIN_DISTANCE>(node.minChild, minClosest);
+            }
+        }
+    }
+
+    void searchNeighbors(const int nodeIndex, const Geometry::Point& closest) const noexcept {
+        AssertMsg(isNode(nodeIndex), "Index = " << nodeIndex << " is not a node!");
+
+        const Node& node = nodes[nodeIndex];
+        if (node.isLeaf()) {
+            for (int i = node.leaf.begin; i < node.leaf.end; i++) {
+                const double distance = metric.distanceSquare(p, coordinates[order[i]]);
+                if (nearestDistance >= distance) neighbors.emplace_back(order[i]);
+            }
+        } else {
+            Geometry::Point minClosest = closest;
+            minClosest[node.inner.splitDimension] = std::min(closest[node.inner.splitDimension], node.inner.minChildMax);
+            const double minChildDistance = metric.distanceSquare(p, minClosest);
+            if (nearestDistance >= minChildDistance) searchNeighbors(node.minChild, minClosest);
+            Geometry::Point maxClosest = closest;
+            maxClosest[node.inner.splitDimension] = std::max(closest[node.inner.splitDimension], node.inner.maxChildMin);
+            const double maxChildDistance = metric.distanceSquare(p, maxClosest);
+            if (nearestDistance >= maxChildDistance) searchNeighbors(node.maxChild, maxClosest);
+        }
+    }
+
+    inline int newNode() noexcept {
+        nodes.push_back(Node());
+        return nodes.size() - 1;
+    }
+
+    inline bool isNode(const size_t index) const noexcept {
+        return index < nodes.size();
+    }
+
+    inline void makeBoundingBox(const int beginIndex, const int endIndex, Geometry::Rectangle& boundingBox) noexcept {
+        Assert(endIndex > beginIndex);
+        boundingBox.clear(coordinates[order[beginIndex]]);
+        for (int i = beginIndex + 1; i < endIndex; i++) {
+            boundingBox.extend(coordinates[order[i]]);
+        }
+    }
+
+    inline bool isBoundingBox(const int beginIndex, const int endIndex, const Geometry::Rectangle& boundingBox) noexcept {
+        Geometry::Rectangle realBoundingBox;
+        makeBoundingBox(beginIndex, endIndex, realBoundingBox);
+        return (realBoundingBox == boundingBox);
+    }
+
+    inline std::string boundingBoxError(const int beginIndex, const int endIndex, const Geometry::Rectangle& boundingBox) const noexcept {
+        std::stringstream ss;
+        ss << "Bounding box " << boundingBox << " does not match the coordinates from index " << beginIndex << " to index " << endIndex << "!";
+        printStackTrace();
+        return ss.str();
+    }
+
+    void check(const int nodeIndex, const Geometry::Rectangle& bb, std::vector<bool>& seen) const noexcept {
+        AssertMsg(isNode(nodeIndex), "Index = " << nodeIndex << " is not a node!");
+        const Node& node = nodes[nodeIndex];
+        if (node.isLeaf()) {
+            for (int i = node.leaf.begin; i < node.leaf.end; i++) {
+                if (!bb.contains(coordinates[order[i]])) {
+                    std::cout << "nodeIndex: " << nodeIndex << std::endl << std::flush;
+                    std::cout << "bb: " << bb << std::endl << std::flush;
+                    std::cout << "i: " << i << std::endl << std::flush;
+                    std::cout << "order[i]: " << order[i] << std::endl << std::flush;
+                    std::cout << "coordinates[order[i]]: " << coordinates[order[i]] << std::endl << std::flush;
+                }
+                Assert(bb.contains(coordinates[order[i]]));
+                Assert(!seen[order[i]]);
+                seen[order[i]] = true;
+            }
+        } else {
+            Geometry::Rectangle minBB = bb;
+            minBB.max[node.inner.splitDimension] = node.inner.minChildMax;
+            if (!bb.contains(minBB)) {
+                std::cout << "nodeIndex: " << nodeIndex << std::endl << std::flush;
+                std::cout << "bb: " << bb << std::endl << std::flush;
+                std::cout << "minBB: " << minBB << std::endl << std::flush;
+                std::cout << "node.splitDimension: " << node.inner.splitDimension << std::endl << std::flush;
+                std::cout << "node.minChildMax: " << node.inner.minChildMax << std::endl << std::flush;
+                std::cout << "node.maxChildMin: " << node.inner.maxChildMin << std::endl << std::flush;
+            }
+            Assert(bb.contains(minBB));
+            check(node.minChild, minBB, seen);
+            Geometry::Rectangle maxBB = bb;
+            maxBB.min[node.inner.splitDimension] = node.inner.maxChildMin;
+            Assert(bb.contains(maxBB));
+            check(node.maxChild, maxBB, seen);
+        }
+    }
+
+private:
+    METRIC metric;
+
+    const std::vector<Geometry::Point>& coordinates;
+    const int vertexCount;
+    const int maxLeafSize;
+
+    Geometry::Rectangle boundingBox;
+    std::vector<Vertex> order;
+    std::vector<Node> nodes; // nodes[0] = root of tree
+
+    mutable Vertex nearestVertex;
+    mutable double nearestDistance;
+    mutable double minimumDistance;
+    mutable Geometry::Point p;
+    mutable std::vector<Vertex> neighbors;
+
+};
diff --git a/DataStructures/Geometry/Metric.h b/DataStructures/Geometry/Metric.h
new file mode 100644 (file)
index 0000000..e94ba06
--- /dev/null
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <iostream>
+#include <vector>
+#include <string>
+#include <cmath>
+
+#include "Point.h"
+#include "Rectangle.h"
+
+namespace Geometry {
+
+struct EuclideanMetric {
+
+    EuclideanMetric() {}
+
+    inline double distanceSquare(const Point& a, const Point& b) const {return Geometry::euclideanDistanceSquared(a, b);}
+    inline double distance(const Point& a, const Point& b) const {return Geometry::euclideanDistance(a, b);}
+    inline double spread(const Rectangle& r, const int dimension) const {return r.d(dimension);}
+
+};
+
+struct GeoMetric {
+
+    GeoMetric() {}
+
+    inline double distanceSquare(const Point& a, const Point& b) const {
+        const double d = geoDistanceInCM(a, b);
+        return d * d;
+    }
+
+    inline double distance(const Point& a, const Point& b) const {
+        return geoDistanceInCM(a, b);
+    }
+
+    inline double spread(const Rectangle& r, const int dimension) const {
+        Point a = r.center();
+        a[dimension] = r.min[dimension];
+        Point b = r.center();
+        b[dimension] = r.max[dimension];
+        return geoDistanceInCM(a, b);
+    }
+
+};
+
+struct GeoMetricAproximation {
+
+    GeoMetricAproximation() : correction(Point(Construct::XY, 1, 1)) {}
+    inline static GeoMetricAproximation SetCorrection(const Point& c) {
+        GeoMetricAproximation metric;
+        metric.correction = c;
+        return metric;
+    }
+    inline static GeoMetricAproximation ComputeCorrection(const Point& p) {
+        GeoMetricAproximation metric;
+        const Point x = Point(Construct::XY, p.x + 0.1, p.y);
+        const Point y = Point(Construct::XY, p.x, p.y + 0.1);
+        const double dx = geoDistanceInCM(p, x);
+        const double dy = geoDistanceInCM(p, y);
+        //metric.correction = Point(Construct::XY, 1, dy / dx);
+        metric.correction = Point(Construct::XY, dx, dy * dy / dx);
+        return metric;
+    }
+
+    inline double distanceSquare(const Point& a, const Point& b) const {
+        const double dx = (a.x - b.x) * correction.x;
+        const double dy = (a.y - b.y) * correction.y;
+        return (dx * dx) + (dy * dy);
+    }
+
+    inline double distance(const Point& a, const Point& b) const {
+        return std::sqrt(distanceSquare(a, b));
+    }
+
+    inline double spread(const Rectangle& r, const int dimension) const {
+        return r.d(dimension) * correction[dimension];
+    }
+
+    Point correction;
+
+};
+
+}
diff --git a/DataStructures/Geometry/Point.h b/DataStructures/Geometry/Point.h
new file mode 100644 (file)
index 0000000..3fcf8e1
--- /dev/null
@@ -0,0 +1,184 @@
+#pragma once
+
+#include <cmath>
+#include <ostream>
+#include <sstream>
+
+#include "../../Helpers/Types.h"
+#include "../../Helpers/ConstructorTags.h"
+
+namespace Geometry {
+
+class Point {
+
+public:
+    // Constructors
+    Point() : latitude(0.0), longitude(0.0) {}
+    Point(const Construct::XYTag, const double& x, const double& y) : y(y), x(x) {}
+    Point(const Construct::LatLongTag, const double& latitude, const double& longitude) : latitude(latitude), longitude(longitude) {}
+
+    // Modifiers & Operators
+    inline void min(const Point& p) {
+        x = std::min(x, p.x);
+        y = std::min(y, p.y);
+    }
+
+    inline void max(const Point& p) {
+        x = std::max(x, p.x);
+        y = std::max(y, p.y);
+    }
+
+    inline Point& operator+=(Point& p) {
+        x += p.x;
+        y += p.y;
+        return *this;
+    }
+
+    inline Point& operator-=(Point& p) {
+        x -= p.x;
+        y -= p.y;
+        return *this;
+    }
+
+    inline Point& operator*=(const double scalar) {
+        x *= scalar;
+        y *= scalar;
+        return *this;
+    }
+
+    inline Point& operator/=(const double divisor) {
+        x /= divisor;
+        y /= divisor;
+        return *this;
+    }
+
+    inline bool operator==(const Point& p) const {
+        return (x == p.x) && (y == p.y);
+    }
+
+    inline bool operator!=(const Point& p) const {
+        return !operator==(p);
+    }
+
+    inline double abs() const {
+        return std::sqrt((x * x) + (y * y));
+    }
+
+    inline double absSquared() const {
+        return (x * x) + (y * y);
+    }
+
+    inline friend Point operator*(const double scalar, const Point& point) {
+        return Point(Construct::XY, point.x * scalar, point.y * scalar);
+    }
+
+    inline friend Point operator*(const Point& point, const double scalar) {
+        return Point(Construct::XY, scalar * point.x, scalar * point.y);
+    }
+
+    inline friend double operator*(const Point& a, const Point& b) {
+        return (a.x * b.x) + (a.y * b.y);
+    }
+
+    inline friend Point operator/(const Point& point, const double divisor) {
+        return Point(Construct::XY, point.x / divisor, point.y / divisor);
+    }
+
+    inline friend Point operator+(const Point& a, const Point& b) {
+        return Point(Construct::XY, a.x + b.x, a.y + b.y);
+    }
+
+    inline friend Point operator-(const Point& a, const Point& b) {
+        return Point(Construct::XY, a.x - b.x, a.y - b.y);
+    }
+
+    inline friend double dotProduct(const Point& a, const Point& b) {
+        return (a.x * b.x) + (a.y * b.y);
+    }
+
+    inline double distanceToPoint(const Point& p) const {
+        const double dx = x - p.x;
+        const double dy = y - p.y;
+        return sqrt((dx * dx) + (dy * dy));
+    }
+
+    inline double distanceToLine(const Point& p, const Point& q) const {
+        const Point direction = q - p;
+        const Point closestPointOnLine = p + (dotProduct(*this - p, direction) * (direction / direction.absSquared()));
+        return distanceToPoint(closestPointOnLine);
+    }
+
+    // String conversion
+    inline std::string toXY() const {
+        std::stringstream ss;
+        ss << "\"X\":" << x << ", \"Y\":" << y;
+        return ss.str();
+    }
+
+    inline std::string toLatLong() const {
+        std::stringstream ss;
+        ss << "\"Lat\":" << latitude << ", \"Lon\":" << longitude;
+        return ss.str();
+    }
+
+    inline std::ostream& writeXY(std::ostream& stream) const {
+        return stream << toXY();
+    }
+
+    inline std::ostream& writeLatLong(std::ostream& stream) const {
+        return stream << toLatLong();
+    }
+
+    inline friend std::ostream& operator<<(std::ostream& os, const Point& p) {
+        std::stringstream ss;
+        ss << "(" << p.x << ", " << p.y << ")";
+        return os << ss.str();
+    }
+
+    // Access
+    inline double& operator[](const int dimension) {return (&latitude)[dimension];}
+    inline const double& operator[](const int dimension) const {return (&latitude)[dimension];}
+
+public:
+    union {
+        double latitude;
+        double y;
+    };
+    union {
+        double longitude;
+        double x;
+    };
+
+};
+
+// Free functions
+inline Point min(const Point& a, const Point& b) {
+    return Point(Construct::XY, std::min(a.x, b.x), std::min(a.y, b.y));
+}
+
+inline Point max(const Point& a, const Point& b) {
+    return Point(Construct::XY, std::max(a.x, b.x), std::max(a.y, b.y));
+}
+
+inline double geoDistanceInCM(const Point& from, const Point& to) {
+    if (from == to) return 0;
+    double heightFrom(degreesToRadians(from.longitude));
+    double heightTo(degreesToRadians(to.longitude));
+    double widthFrom(degreesToRadians(from.latitude));
+    double widthTo(degreesToRadians(to.latitude));
+    return acos(std::min(1.0, sin(widthFrom) * sin(widthTo) + cos(widthFrom) * cos(widthTo) * cos(heightTo - heightFrom))) * EARTH_RADIUS_IN_CENTIMETRE;
+}
+
+inline double euclideanDistanceSquared(const Point& a, const Point& b) {
+    const double dx = a.x - b.x;
+    const double dy = a.y - b.y;
+    return (dx * dx) + (dy * dy);
+}
+
+inline double euclideanDistance(const Point& a, const Point& b) {
+    return std::sqrt(euclideanDistanceSquared(a, b));
+}
+
+static_assert(sizeof(Point) == 2 * sizeof(double), "Point layout is broken");
+
+}
diff --git a/DataStructures/Geometry/Rectangle.h b/DataStructures/Geometry/Rectangle.h
new file mode 100644 (file)
index 0000000..246daad
--- /dev/null
@@ -0,0 +1,196 @@
+#pragma once
+
+#include <cmath>
+#include <vector>
+#include <ostream>
+#include <sstream>
+#include <limits>
+
+#include "Point.h"
+
+#include "../../Helpers/Assert.h"
+#include "../../Helpers/Helpers.h"
+
+namespace Geometry {
+
+class Rectangle {
+
+public:
+    // Constructors
+    Rectangle() : min(), max() {}
+    Rectangle(const Point& p) : min(p), max(p) {}
+    inline static Rectangle BoundingBox(const Point& a, const Point& b) {
+        Rectangle r;
+        r.min = Geometry::min(a, b);
+        r.max = Geometry::max(a, b);
+        Assert(r.isPositive());
+        return r;
+    }
+    template<typename T>
+    inline static Rectangle BoundingBox(const std::vector<T>& points) {
+        Rectangle r = Empty();
+        r.extend(points);
+        return r;
+    }
+    inline static Rectangle MinMax(const Point& min, const Point& max) {
+        Rectangle r;
+        r.min = min;
+        r.max = Geometry::max(min, max);
+        Assert(r.isPositive());
+        return r;
+    }
+    inline static Rectangle Union(const Rectangle& a, const Rectangle& b) {
+        Rectangle r;
+        r.min = Geometry::min(a.min, b.min);
+        r.max = Geometry::max(a.max, b.max);
+        Assert(r.isPositive());
+        return r;
+    }
+    template<typename T>
+    inline static Rectangle Union(const std::vector<T>& rectangles) {
+        Rectangle r = Empty();
+        r.extend(rectangles);
+        return r;
+    }
+    inline static Rectangle Intersection(const Rectangle& a, const Rectangle& b) {
+        Rectangle r;
+        r.min = Geometry::max(a.min, b.min);
+        r.max = Geometry::min(a.max, b.max);
+        r.max.max(r.min);
+        Assert(r.isPositive());
+        return r;
+    }
+    inline static Rectangle Negative(const double d = 1000000) {
+        Rectangle r;
+        r.min = Point(Construct::XY, d, d);
+        r.max = Point(Construct::XY, -d, -d);
+        return r;
+    }
+    inline static Rectangle Empty() {
+        return Negative(std::numeric_limits<int>::max());
+    }
+
+    // Modifiers & Operators
+    inline bool operator==(const Rectangle& r) const noexcept {
+        return min == r.min && max == r.max;
+    }
+
+    inline bool operator!=(const Rectangle &r) const noexcept {
+        return !operator==(r);
+    }
+
+    inline void extend(const Point& p) noexcept {
+        min.min(p);
+        max.max(p);
+        Assert(isPositive());
+    }
+
+    inline void extend(const Rectangle& r) noexcept {
+        min.min(r.min);
+        max.max(r.max);
+        Assert(isPositive());
+    }
+
+    template<typename T>
+    inline void extend(const std::vector<T>& points) noexcept {
+        for (const T& t : points) {
+            extend(t);
+        }
+    }
+
+    inline bool contains(const Point& p) const noexcept {
+        if (p.x > max.x || p.x < min.x) return false;
+        if (p.y > max.y || p.y < min.y) return false;
+        return true;
+    }
+
+    inline bool contains(const Rectangle& r) const noexcept {
+        if (r.max.x > max.x || r.min.x < min.x) return false;
+        if (r.max.y > max.y || r.min.y < min.y) return false;
+        return true;
+    }
+
+    inline bool intersects(const Rectangle& r) const noexcept {
+        return Intersection(*this, r).isPositive();
+    }
+
+    inline void discretize(const double dx, const double dy) noexcept {
+        min.x = floor(min.x, dx);
+        min.y = floor(min.y, dy);
+        max.x = ceil(max.x, dx);
+        max.y = ceil(max.y, dy);
+    }
+
+    inline void clear(const Point& p = Point()) noexcept {
+        min = p;
+        max = p;
+    }
+
+    inline Point center() const noexcept {
+        return 0.5 * (min + max);
+    }
+
+    inline double area() const noexcept {
+        Assert(isPositive());
+        return dx() * dy();
+    }
+
+    inline Point closestPoint(const Point& p) const noexcept {
+        Point c = p;
+        if (p.x < min.x) {
+            c.x = min.x;
+        } else if (p.x > max.x) {
+            c.x = max.x;
+        }
+        if (p.y < min.y) {
+            c.y = min.y;
+        } else if (p.y > max.y) {
+            c.y = max.y;
+        }
+        return c;
+    }
+
+    inline bool isPositive() const noexcept {
+        return (min.x <= max.x) || (min.y <= max.y);
+    }
+
+    inline bool isPoint() const noexcept {
+        return min == max;
+    }
+
+    // String conversion
+    inline friend std::ostream& operator<<(std::ostream& os, const Rectangle& r) noexcept {
+        std::stringstream ss;
+        ss << "[" << r.min << " | " << r.max << "]";
+        return os << ss.str();
+    }
+
+    // Access
+    inline double dx() const noexcept {return max.x - min.x;}
+    inline double dy() const noexcept {return max.y - min.y;}
+    inline double d(const int dimension) const noexcept {return max[dimension] - min[dimension];}
+
+public:
+    union {
+        Point topLeft;
+        Point min;
+    };
+    union {
+        Point bottomRight;
+        Point max;
+    };
+
+};
+
+// Free functions
+inline double euclideanDistanceSquared(const Point& a, const Rectangle& b) noexcept {
+    return euclideanDistanceSquared(a, b.closestPoint(a));
+}
+
+inline double euclideanDistance(const Point& a, const Rectangle& b) noexcept {
+    return std::sqrt(euclideanDistanceSquared(a, b));
+}
+
+static_assert(sizeof(Rectangle) == 2 * sizeof(Point), "Rectangle layout is broken");
+
+}
diff --git a/DataStructures/Parameter.h b/DataStructures/Parameter.h
new file mode 100644 (file)
index 0000000..e2ea483
--- /dev/null
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <iostream>
+#include <string>
+
+#include "../Helpers/String/String.h"
+
+struct Parameter {
+
+    Parameter(std::string type = "", std::string name = "", std::string display = "", std::string defaultValue = "", bool recalculateOnChange = false, std::string min = "", std::string max = "", std::string step = "", int index = 0) :
+        type(type),
+        name(name),
+        display(display),
+        defaultValue(defaultValue),
+        value(defaultValue),
+        recalculateOnChange(recalculateOnChange),
+        min(min),
+        max(max),
+        step(step),
+        index(index) {
+    }
+
+    Parameter(std::string type, std::string name, std::string display, std::string defaultValue, bool recalculateOnChange, int index) :
+        Parameter(type, name, display, defaultValue, recalculateOnChange, "", "", "", index) {
+    }
+
+    inline void writeParameter(std::ostream &stream) const {
+        stream << "{";
+        stream << "\"Type\":\"" << type << "\""
+               << ", \"Name\":\"" << name << "\""
+               << ", \"Display\":\"" << display << "\""
+               << ", \"DefaultValue\":\"" << defaultValue << "\""
+               << ", \"Index\":\"" << index << "\"";
+        if (!min.empty()) stream << ", \"MinValue\":\"" << min << "\"";
+        if (!max.empty()) stream << ", \"MaxValue\":\"" << max << "\"";
+        if (!step.empty()) stream  << ", \"Step\":\"" << step << "\"";
+        if (recalculateOnChange) stream << ", \"RecalculateOnChange\":\"true\"";
+        stream << "}";
+    }
+
+    template <typename T>
+    inline T getValue() const  {
+        if (type == "bool") {
+            return value == "true";
+        } else {
+            return String::lexicalCast<T>(value);
+        }
+    }
+
+    template <typename T>
+    inline T getMax() const {return String::lexicalCast<T>(max);}
+
+    template <typename T>
+    inline T getMin() const {return String::lexicalCast<T>(min);}
+
+    template <typename T>
+    inline T getDefault() const {return String::lexicalCast<T>(defaultValue);}
+
+    template <typename T>
+    inline void validate() {
+        T v = getValue<T>();
+        if (v < getMin<T>()) {
+            value = min;
+        } else if (v > getMax<T>()) {
+            value = max;
+        }
+    }
+
+
+    std::string type;
+    std::string name;
+    std::string display;
+    std::string defaultValue;
+    std::string value;
+    bool recalculateOnChange;
+
+    std::string min;
+    std::string max;
+    std::string step;
+
+    int index;
+
+};
diff --git a/DataStructures/Result.h b/DataStructures/Result.h
new file mode 100644 (file)
index 0000000..e668d65
--- /dev/null
@@ -0,0 +1,174 @@
+#pragma once
+
+#include <vector>
+#include <string>
+
+#include "Geometry/Point.h"
+
+#include "../Helpers/Types.h"
+#include "../Helpers/JSONString.h"
+#include "../Helpers/Vector/Vector.h"
+
+namespace KIT {
+    const std::string green  = "009682";
+    const std::string blue   = "4664AA";
+    const std::string orange = "DF9B1B";
+    const std::string red    = "A22223";
+    const std::string violet = "A3107C";
+    const std::string cyan   = "23A1E0";
+}
+
+class Result {
+
+public:
+    struct PolyLine {
+        PolyLine(const std::vector<Geometry::Point>& points = std::vector<Geometry::Point>(), const std::string& info = "", const std::string& color = "FF0000") :
+            info(info),
+            color(color),
+            points(points) {
+        }
+        std::string info;
+        std::string color;
+        std::vector<Geometry::Point> points;
+    };
+
+    struct Path {
+        Path(const std::vector<Vertex>& nodeIDs = std::vector<Vertex>(), const std::string& info = "", const std::string& color = "FF0000") :
+            info(info),
+            color(color),
+            nodeIDs(nodeIDs) {
+        }
+        Path(const std::vector<StopId>& stopIDs, const std::string& info = "", const std::string& color = "FF0000") :
+            info(info),
+            color(color) {
+            Vector::assign(nodeIDs, stopIDs);
+        }
+        std::string info;
+        std::string color;
+        std::vector<Vertex> nodeIDs;
+    };
+
+    struct Node {
+        Node(const Vertex id, const std::string& info = "", const std::string& color = KIT::green) :
+            info(info),
+            color(color),
+            id(id) {
+        }
+        std::string info;
+        std::string color;
+        Vertex id;
+    };
+
+    Result(const std::string& info = "", const std::string& color = "FF0000") :
+        info(info),
+        color(color) {
+    }
+
+    static Result fromPath(const std::vector<Vertex>& path) noexcept {
+        Result result;
+        result.pathes.push_back(Path(path));
+        return result;
+    }
+
+    static Result fromNodeSet(const std::vector<Vertex>& nodeSet) noexcept {
+        Result result;
+        for (const Vertex v : nodeSet) {
+            result.nodes.push_back(Node(v));
+        }
+        return result;
+    }
+
+    std::string info;
+    std::string color;
+    std::vector<Path> pathes;
+    std::vector<Node> nodes;
+    std::vector<PolyLine> polyLines;
+    std::vector<Geometry::Point> function;
+    std::vector<std::pair<std::string, std::string>> parameter;
+
+};
+
+inline std::string resultsToJSON(const std::vector<Result>& results, const std::vector<Geometry::Point>& coordinates) {
+    JSONString json;
+    json.startNewObject();
+        json.addQualifier("ResultSet");
+        json.startNewArray();
+            for (size_t i = 0; i < results.size(); i++) {
+                const Result& result = results[i];
+                json.startNewObject();
+                    json.addQualifierWithValue("Info", result.info);
+                    json.addQualifierWithValue("Color", result.color);
+                    json.addQualifier("Pathes");
+                    json.startNewArray();
+                        for (size_t j = 0; j < result.pathes.size(); j++) {
+                            const Result::Path& path = result.pathes[j];
+                            json.startNewObject();
+                                json.addQualifierWithValue("Info", path.info);
+                                json.addQualifierWithValue("Color", path.color);
+                                json.addQualifier("Nodes");
+                                json.startNewArray();
+                                    for (size_t k = 0; k < path.nodeIDs.size(); k++) {
+                                        const Geometry::Point& pos = coordinates[path.nodeIDs[k]];
+                                        json.startNewObject();
+                                            json.addQualifierWithValue("Lat", pos.latitude);
+                                            json.addQualifierWithValue("Lon", pos.longitude);
+                                        json.endObject();
+                                    }
+                                json.endArray();
+                            json.endObject();
+                        }
+                        for (size_t j = 0; j < result.polyLines.size(); j++) {
+                            const Result::PolyLine& polyLine = result.polyLines[j];
+                            json.startNewObject();
+                                json.addQualifierWithValue("Info", polyLine.info);
+                                json.addQualifierWithValue("Color", polyLine.color);
+                                json.addQualifier("Nodes");
+                                json.startNewArray();
+                                    for (size_t k = 0; k < polyLine.points.size(); k++) {
+                                        const Geometry::Point& pos = polyLine.points[k];
+                                        json.startNewObject();
+                                            json.addQualifierWithValue("Lat", pos.latitude);
+                                            json.addQualifierWithValue("Lon", pos.longitude);
+                                        json.endObject();
+                                    }
+                                json.endArray();
+                            json.endObject();
+                        }
+                    json.endArray();
+                    json.addQualifier("Nodes");
+                    json.startNewArray();
+                        for (size_t j = 0; j < result.nodes.size(); j++) {
+                            const Result::Node& node = result.nodes[j];
+                            json.startNewObject();
+                                json.addQualifierWithValue("Info", node.info);
+                                json.addQualifierWithValue("Color", node.color);
+                                const Geometry::Point& pos = coordinates[node.id];
+                                json.addQualifier("Pos");
+                                json.startNewObject();
+                                    json.addQualifierWithValue("Lat", pos.latitude);
+                                    json.addQualifierWithValue("Lon", pos.longitude);
+                                json.endObject();
+                            json.endObject();
+                        }
+                    json.endArray();
+                    json.addQualifier("Function");
+                    json.startNewArray();
+                        for (const Geometry::Point& point : result.function) {
+                            json.startNewObject();
+                                json.addQualifierWithValue("X", point.x);
+                                json.addQualifierWithValue("Y", point.y);
+                            json.endObject();
+                        }
+                    json.endArray();
+                    json.addQualifier("Parameter");
+                    json.startNewObject();
+                        for (const std::pair<std::string, std::string>& parameter : result.parameter) {
+                            json.addQualifierWithValue(parameter.first, parameter.second);
+                        }
+                    json.endObject();
+                json.endObject();
+            }
+        json.endArray();
+    json.endObject();
+    return json.str();
+}
diff --git a/Helpers/Assert.h b/Helpers/Assert.h
new file mode 100644 (file)
index 0000000..ca8d147
--- /dev/null
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <cassert>
+
+#include "HighlightText.h"
+
+inline void ensure(const bool assumption) noexcept {
+    if (!assumption) exit(1);
+}
+
+#define Assert(assumption) assert(assumption)
+#define AssertMsg(assumption, msg) assert((assumption) || (std::cout << "\n\033[31mASSERTION FAILED: " << msg << "\033[0m\nFile: " << __FILE__ << "\nLine: " << __LINE__ << "\n" << std::flush && false))
+#define Ensure(assumption, msg) ensure((assumption) || (std::cout << "\n\033[31mERROR: " << msg << "\033[0m\nFile: " << __FILE__ << "\nLine: " << __LINE__ << "\n" << std::flush && false))
+
+void checkAsserts() {
+#ifdef NDEBUG
+    std::cout << "Running in release-mode!" << std::endl;
+    assert(false);
+#else
+    std::cout << "Running with active assertions!" << std::endl;
+#endif
+}
diff --git a/Helpers/Console/CommandLineParser.h b/Helpers/Console/CommandLineParser.h
new file mode 100644 (file)
index 0000000..26d10e3
--- /dev/null
@@ -0,0 +1,60 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "../String/String.h"
+
+class CommandLineParser {
+
+public:
+    CommandLineParser(int argc, char** argv) {
+        for (int currentIndex = 1; currentIndex < argc; ++currentIndex) {
+            if (argv[currentIndex][0] == '-') {
+                std::string key(&argv[currentIndex][1]);
+                std::string value("");
+                if ((currentIndex + 1) < argc) value.assign(argv[currentIndex + 1]);
+                arguments[key] = value;
+            }
+        }
+    }
+
+    CommandLineParser(const std::string& input) {
+        std::vector<std::string> argv = String::split(String::trim(input), ' ');
+        for (size_t currentIndex = 1; currentIndex < argv.size(); ++currentIndex) {
+            if (argv[currentIndex][0] == '-') {
+                std::string key(&argv[currentIndex][1]);
+                std::string value("");
+                if ((currentIndex + 1) < argv.size()) value.assign(argv[currentIndex + 1]);
+                arguments[key] = value;
+            }
+        }
+    }
+
+    template<typename T>
+    inline T value(const std::string key, const T defaultValue = T()) noexcept {
+        if (arguments.find(key) != arguments.end()) {
+            return String::lexicalCast<T>(arguments[key]);
+        } else {
+            return defaultValue;
+        }
+    }
+
+    template<typename T>
+    inline T get(const std::string key, const T defaultValue = T()) noexcept {
+        return value<T>(key, defaultValue);
+    }
+
+    inline bool isSet(const std::string key) const noexcept {
+        return arguments.find(key) != arguments.end();
+    }
+
+    inline size_t numberOfArguments() const noexcept {
+        return arguments.size();
+    }
+
+private:
+    std::map<std::string, std::string> arguments;
+
+};
diff --git a/Helpers/ConstructorTags.h b/Helpers/ConstructorTags.h
new file mode 100644 (file)
index 0000000..7c79e38
--- /dev/null
@@ -0,0 +1,25 @@
+#pragma once
+
+namespace Construct {
+
+    enum IdTag {Id};
+    enum SortTag {Sort};
+    enum RandomTag {Random};
+    enum InvertTag {Invert};
+    enum FromTextFileTag {FromTextFile};
+
+    enum XYTag {XY};
+    enum LatLongTag {LatLong};
+
+    enum CompleteTag {Complete};
+
+    enum MinMaxTag {MinMax};
+    enum BoundingBoxTag {BoundingBox};
+    enum UnionTag {Union};
+    enum IntersectionTag {Intersection};
+    enum NegativeTag {Negative};
+
+    enum SplitByOriginTag {SplitByOrigin};
+    enum SplitByDestinationTag {SplitByDestination};
+
+}
diff --git a/Helpers/Debug.h b/Helpers/Debug.h
new file mode 100644 (file)
index 0000000..2f6a193
--- /dev/null
@@ -0,0 +1,126 @@
+#pragma once
+
+#include <iostream>
+#include <vector>
+#include <string>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <execinfo.h>
+#include <cxxabi.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "Assert.h"
+#include "Types.h"
+#include "String/String.h"
+
+#undef AssertMsg
+#define AssertMsg(assumption, msg) assert((assumption) || (std::cout << "\n\033[31mASSERTION FAILED: " << msg << "\033[0m\nFile: " << __FILE__ << "\nLine: " << __LINE__ << "\n" << std::flush && printStackTrace()))
+
+#define __Print_Here__ std::cout << "\033[1;36mFile: " << __FILE__ << ", Line: " << __LINE__ << "\033[0m" << std::endl
+
+inline std::string shortType(const std::string& input) noexcept {
+    if (input.empty()) return input;
+    std::string cleanType = Meta::Implementation::type(input);
+    cleanType = String::replaceAll(cleanType, Meta::type<Vertex>(), "Vertex");
+    cleanType = String::replaceAll(cleanType, Meta::type<Edge>(), "Edge");
+    cleanType = String::replaceAll(cleanType, Meta::type<StopId>(), "StopId");
+    cleanType = String::replaceAll(cleanType, Meta::type<RouteId>(), "RouteId");
+    cleanType = String::replaceAll(cleanType, Meta::type<TripId>(), "TripId");
+    cleanType = String::replaceAll(cleanType, Meta::type<StopIndex>(), "StopIndex");
+    cleanType = String::replaceAll(cleanType, Meta::type<ConnectionId>(), "ConnectionId");
+    cleanType = String::replaceAll(cleanType, Meta::type<HyperVertexId>(), "HyperVertexId");
+    cleanType = String::replaceAll(cleanType, "basic_string<char, std::char_traits<char>, std::allocator<char> >", "string");
+    cleanType = String::replaceAll(cleanType, " >", ">");
+    cleanType = String::replaceAll(cleanType, "Meta::", "");
+    cleanType = String::replaceAll(cleanType, "std::", "");
+    cleanType = String::replaceAll(cleanType, "__gnu_cxx::", "");
+    cleanType = String::replaceAll(cleanType, "__cxx11::", "");
+    cleanType = String::replaceAll(cleanType, "__debug::", "");
+    cleanType = String::replaceAll(cleanType, "__ops::", "");
+    cleanType = String::replaceAll(cleanType, "__normal_", "");
+    cleanType = String::replaceAll(cleanType, "Implementation", "");
+    while (cleanType.size() > 130) {
+        size_t beginIndex = cleanType.find_last_of('<');
+        if (beginIndex >= cleanType.size()) break;
+        size_t endIndex = cleanType.find_first_of('>', beginIndex);
+        if (endIndex >= cleanType.size()) break;
+        if (endIndex - beginIndex + 1 < 6) {
+            cleanType[beginIndex] = '{';
+            cleanType[endIndex] = '}';
+        } else {
+            cleanType.replace(beginIndex, endIndex - beginIndex + 1, ";");
+        }
+    }
+    cleanType = String::replaceAll(cleanType, ";", "<...>");
+    cleanType = String::replaceAll(cleanType, "}", ">");
+    cleanType = String::replaceAll(cleanType, "{", "<");
+    return cleanType;
+}
+
+inline bool printStackTrace(std::ostream& out = std::cout, size_t maxFrames = 63) {
+    out << "Stack trace: " << std::endl;
+    void** addressList = new void*[maxFrames + 1];
+
+    size_t numFrames = backtrace(addressList, maxFrames);
+    if (numFrames <= 0) {
+        out << "    ERROR! No stack trace available!" << std::endl;
+        return false;
+    }
+
+    char** symbolList = backtrace_symbols(addressList, numFrames);
+
+    size_t functionNameSize = 256;
+    char* functionName = (char*)malloc(functionNameSize);
+
+    for (size_t i = 0; i < numFrames; i++) {
+        char* name = 0;
+        char* caller = 0;
+        for (char* j = symbolList[i]; *j; j++) {
+            if (*j == '(') {
+                *j = '\0';
+                name = j + 1;
+            } else if (*j == '+') {
+                *j = '\0';
+                caller = j + 1;
+            } else if (*j == ')' && caller) {
+                *j = '\0';
+                break;
+            }
+        }
+        if (name && caller && name < caller) {
+            int status;
+            char* demangledFunctionName = abi::__cxa_demangle(name, functionName, &functionNameSize, &status);
+            if (status == 0) {
+                functionName = demangledFunctionName;
+                out << "   " << shortType(functionName) << std::endl;
+            }
+            else {
+                out << "   " << name << std::endl;
+            }
+        }
+
+    }
+
+    free(functionName);
+    free(symbolList);
+    delete[] addressList;
+    return false;
+}
+
+std::vector<int> debugStack;
+
+void segfaultSigaction(int, siginfo_t* si, void*) {
+    printf("Caught segfault at address %p\n", si->si_addr);
+    std::cout << "Debug Stack:" << std::endl;
+    for (const int line : debugStack) {
+        std::cout << "   " << line << std::endl;
+    }
+    exit(0);
+}
+
+#define __Push__ debugStack.push_back(__LINE__)
+#define __Pop__ debugStack.pop_back()
+#define __Change__ debugStack.back() = __LINE__
+#define __Debug_Stack__ struct sigaction __sa__; memset(&__sa__, 0, sizeof(struct sigaction)); sigemptyset(&__sa__.sa_mask); __sa__.sa_sigaction = segfaultSigaction; __sa__.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &__sa__, NULL)
diff --git a/Helpers/FileSystem/FileSystem.h b/Helpers/FileSystem/FileSystem.h
new file mode 100644 (file)
index 0000000..55eebbb
--- /dev/null
@@ -0,0 +1,199 @@
+#pragma once
+
+#include <algorithm>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <vector>
+#include <string>
+
+#include <termios.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include <dirent.h>
+
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+
+#include "../Assert.h"
+#include "../String/String.h"
+
+namespace FileSystem {
+
+    inline std::string getWorkingDirectory() noexcept {
+        char buff[PATH_MAX];
+        ssize_t len = readlink("/proc/self/exe", buff, sizeof(buff)-1);
+        if (len != -1) {
+            buff[len] = '\0';
+            std::string path(buff);
+            return path.substr(0, path.rfind('/'));
+        }
+        return "/home";
+    }
+
+    inline std::vector<std::string> getFiles(const std::string& path) noexcept {
+        DIR* dir;
+        struct dirent* ent;
+        if ((dir = opendir(path.c_str())) != NULL) {
+            std::vector<std::string> dirs;
+            while ((ent = readdir(dir)) != NULL) {
+                dirs.push_back(ent->d_name);
+            }
+            closedir(dir);
+            std::sort(dirs.begin(), dirs.end());
+            return dirs;
+        }
+        return std::vector<std::string>();
+    }
+
+    inline bool isAbsolutePath(const std::string& path) noexcept {
+        return (!path.empty()) && (path[0] == '/');
+    }
+
+    inline bool isDirectory(const std::string& path) noexcept {
+        DIR* dir;
+        if ((dir = opendir(path.c_str())) != NULL) {
+            closedir(dir);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    inline bool isFile(const std::string& path) noexcept {
+        std::ifstream f(path.c_str());
+        if (f.good()) {
+            f.close();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    inline bool renameFile(const std::string& oldName, const std::string& newName) noexcept {
+        if (!isFile(oldName)) return false;
+        if (isFile(newName)) return false;
+        return rename(oldName.c_str(), newName.c_str()) == 0;
+    }
+
+    inline void deleteFile(const std::string& fileName) noexcept {
+        if (!isFile(fileName)) return;
+        remove(fileName.c_str());
+    }
+
+    inline bool copyFile(const std::string& oldName, const std::string& newName) noexcept {
+        if (!isFile(oldName)) return false;
+        if (isFile(newName)) return false;
+        std::ifstream source(oldName, std::ios::binary);
+        AssertMsg(source.is_open(), "cannot open file: " << oldName);
+        std::ofstream destination(newName, std::ios::binary);
+        AssertMsg(destination.is_open(), "cannot open file: " << newName);
+        destination << source.rdbuf();
+        source.close();
+        destination.close();
+        return true;
+    }
+
+    inline bool isFileOrDirectory(const std::string& path) noexcept {
+        return isFile(path) || isDirectory(path);
+    }
+
+    inline std::string getParentDirectory(const std::string& fileName) noexcept {
+        if (fileName.size() < 2) return "";
+        size_t directoryEnd = fileName.find_last_of('/', fileName.size() - 2);
+        if (directoryEnd >= fileName.size()) return "";
+        return fileName.substr(0, directoryEnd);
+    }
+
+    inline std::string extendPath(const std::string& base, const std::string& file) noexcept {
+        if (file.size() < 1) {
+            return base;
+        } else if (isAbsolutePath(file) || base.empty()) {
+            return file;
+        } else if (file[0] == '.') {
+            if (file.size() < 2) {
+                return base;
+            } else if (file[1] == '/') {
+                return extendPath(base, file.substr(2));
+            }
+            AssertMsg(file[1] == '.', "Cannot extend path '" << base << "' with file '" << file << "'!");
+            if (file.size() == 2) {
+                return getParentDirectory(base);
+            }
+            AssertMsg(file[2] == '/', "Cannot extend path '" << base << "' with file '" << file << "'!");
+            return extendPath(getParentDirectory(base), file.substr(3));
+        }
+        if (base[base.length() - 1] == '/') {
+            return base + file;
+        } else {
+            return base + "/" + file;
+        }
+    }
+
+    inline std::string getAbsolutePath(const std::string& path) noexcept {
+        if (isAbsolutePath(path)) {
+            return path;
+        } else {
+            return extendPath(getWorkingDirectory(), path);
+        }
+    }
+
+    inline std::string ensureExtension(const std::string& fileName, const std::string& extension) noexcept {
+        if (String::endsWith(fileName, extension)) {
+            return fileName;
+        } else {
+            return fileName + extension;
+        }
+    }
+
+    inline std::string getFileNameWithoutExtension(const std::string& fileName) noexcept {
+        size_t directoryEnd = fileName.find_last_of('/') + 1;
+        if (directoryEnd >= fileName.size()) directoryEnd = 0;
+        size_t extensionBegin  = fileName.find_last_of('.');
+        if (extensionBegin <= directoryEnd) extensionBegin = fileName.size();
+        return fileName.substr(directoryEnd, extensionBegin - directoryEnd);
+    }
+
+    inline void unzip(const std::string& zipFileName, const std::string& ouputDirectory, const bool verbose = true, const std::string& unzipExecutable = "/usr/bin/unzip") noexcept {
+        if (!isFile(zipFileName)) return;
+        if (!isDirectory(ouputDirectory)) return;
+        pid_t pid = fork();
+        switch (pid) {
+        case -1:
+            error("Failure during fork()!");
+            exit(1);
+        case 0:
+            if (verbose) {
+                execl(unzipExecutable.c_str(), "unzip", "-o", zipFileName.c_str(), "-d", ouputDirectory.c_str(), nullptr);
+            } else {
+                execl(unzipExecutable.c_str(), "unzip", "-o", "-qq", zipFileName.c_str(), "-d", ouputDirectory.c_str(), nullptr);
+            }
+            error(unzipExecutable + " failed!");
+            exit(1);
+        default:
+            int status = 0;
+            while (!WIFEXITED(status)) {
+                waitpid(pid, &status, 0);
+            }
+        }
+    }
+
+    inline void makeDirectory(const std::string& path) noexcept {
+        if (path.empty()) return;
+        if (isDirectory(path)) return;
+        makeDirectory(getParentDirectory(path));
+        mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+    }
+
+    inline const std::string& ensureDirectoryExists(const std::string& fileName) noexcept {
+        const std::string parentDirectory = getParentDirectory(fileName);
+        if (parentDirectory == "") return fileName;
+        if (isDirectory(parentDirectory)) return fileName;
+        makeDirectory(parentDirectory);
+        return fileName;
+    }
+
+}
diff --git a/Helpers/Function.h b/Helpers/Function.h
new file mode 100644 (file)
index 0000000..b510ee1
--- /dev/null
@@ -0,0 +1,124 @@
+#pragma once
+
+#include <iostream>
+#include <vector>
+#include <string>
+
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include "Timer.h"
+#include "Assert.h"
+#include "Debug.h"
+
+#include "String/String.h"
+
+inline pid_t forkFailure() {
+    error("Failure during fork()!");
+    exit(1);
+}
+
+template<typename FUNCTION>
+inline double timedFunctionCall(const FUNCTION& function, int = 60000) {
+    Timer timer;
+    function();
+    return timer.elapsedMilliseconds();
+}
+
+template<typename FUNCTION>
+inline double timedAsynchronousFunctionCall(const FUNCTION& function, int maxTimeInMilliSeconds = 60000) {
+    Timer timer;
+    double* time = static_cast<double*>(mmap(NULL, sizeof(double), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0));
+    pid_t work = fork();
+    if (work == -1) forkFailure();
+    if (work == 0) {
+        Timer timer;
+        function();
+        *time = timer.elapsedMilliseconds();
+        exit(0);
+    }
+    pid_t timeout = fork();
+    if (timeout == -1) forkFailure();
+    if (timeout == 0) {
+        sleep(maxTimeInMilliSeconds);
+        exit(0);
+    }
+    pid_t finished = wait(NULL);
+    while ((finished != work) && (finished != timeout)) {
+        finished = wait(NULL);
+    }
+    if (finished == work) {
+        kill(timeout, SIGKILL);
+        waitpid(timeout, 0, 0);
+        return *time;
+    } else {
+        kill(work, SIGKILL);
+        waitpid(work, 0, 0);
+        return timer.elapsedMilliseconds();
+    }
+}
+
+template<typename FUNCTION>
+inline double timedAsynchronousFunctionCall(const FUNCTION& function, int maxTimeInMilliSeconds, int intervallInMilliSeconds) {
+    Timer timer;
+    double* time = static_cast<double*>(mmap(NULL, sizeof(double), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0));
+    *time = -1;
+    bool* done = static_cast<bool*>(mmap(NULL, sizeof(bool), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0));
+    *done = false;
+    pid_t child = fork();
+    if (child == -1) forkFailure();
+    if (child == 0) {
+        Timer timer;
+        function();
+        *time = timer.elapsedMilliseconds();
+        *done = true;
+        exit(0);
+    }
+    while ((!(*done)) && (timer.elapsedMilliseconds() < maxTimeInMilliSeconds)) {
+        sleep(intervallInMilliSeconds);
+    }
+    if (!(*done)) kill(child, SIGKILL);
+    waitpid(child, 0, 0);
+    if (*done) {
+        return *time;
+    } else {
+        return timer.elapsedMilliseconds();
+    }
+}
+
+template<typename FUNCTION>
+inline double timedInterruptableFunctionCall(const FUNCTION& function, int = 60000) {
+    Timer timer;
+    double* time = static_cast<double*>(mmap(NULL, sizeof(double), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0));
+    pid_t work = fork();
+    if (work == -1) forkFailure();
+    if (work == 0) {
+        Timer timer;
+        function();
+        *time = timer.elapsedMilliseconds();
+        exit(0);
+    }
+    pid_t timeout = fork();
+    if (timeout == -1) forkFailure();
+    if (timeout == 0) {
+        getchar();
+        exit(0);
+    }
+    pid_t finished = wait(NULL);
+    while ((finished != work) && (finished != timeout)) {
+        finished = wait(NULL);
+    }
+    if (finished == work) {
+        kill(timeout, SIGKILL);
+        waitpid(timeout, 0, 0);
+        return *time;
+    } else {
+        kill(work, SIGKILL);
+        waitpid(work, 0, 0);
+        return timer.elapsedMilliseconds();
+    }
+}
diff --git a/Helpers/Helpers.h b/Helpers/Helpers.h
new file mode 100644 (file)
index 0000000..d89507c
--- /dev/null
@@ -0,0 +1,87 @@
+#pragma once
+
+#include <set>
+#include <map>
+#include <vector>
+#include <string>
+#include <sstream>
+#include <cmath>
+#include <algorithm>
+#include <ctime>
+#include <thread>
+#include <chrono>
+
+#include <strings.h>
+#include <string.h>
+
+#include "HighlightText.h"
+
+using alias = std::vector<std::string>;
+
+inline double degreesToRadians(const double radius) {
+    return (radius / 180.0) * M_PI;
+}
+
+inline double radiansToDegrees(const double radius) {
+    return (radius / M_PI) * 180;
+}
+
+inline int mostSignificantBit(int i) {
+    return __builtin_clz(i);
+}
+
+inline long long mostSignificantBit(long long i) {
+    return __builtin_clzll(i);
+}
+
+inline int leastSignificantBit(int i) {
+    return ffs(i);
+}
+
+inline long int leastSignificantBit(long int i) {
+    return ffsl(i);
+}
+
+inline long long int leastSignificantBit(long long int i) {
+    return ffsll(i);
+}
+
+inline void sleep(int milliSeconds) {
+    std::chrono::milliseconds timespan(milliSeconds);
+    std::this_thread::sleep_for(timespan);
+}
+
+template<typename T>
+inline T floor(const T value, const T unit) noexcept {
+    return std::floor((value - unit) / unit) * unit;
+}
+
+template<typename T>
+inline T ceil(const T value, const T unit) noexcept {
+    return std::ceil((value + unit) / unit) * unit;
+}
+
+template<typename T>
+inline void sort(std::vector<T>& data) noexcept {
+    std::sort(data.begin(), data.end());
+}
+
+template<typename T, typename LESS>
+inline void sort(std::vector<T>& data, const LESS& less) noexcept {
+    std::sort(data.begin(), data.end(), less);
+}
+
+template<typename T>
+inline void suppressUnusedParameterWarning(const T&) noexcept {}
+
+inline int branchlessConditional(const bool predicate, const int ifTrue, const int ifFalse) noexcept {
+    int result = 0;
+    __asm__ __volatile__(
+        "mov    %[ifTrue], %[result]\n\t"
+        "test   %[predicate], %[predicate]\n\t"
+        "cmovz  %[ifFalse], %[result]\n\t"
+        : [result] "=&r" (result)
+        : [predicate] "r" (predicate), [ifTrue] "r" (ifTrue), [ifFalse] "r" (ifFalse)
+        : "cc");
+    return result;
+}
diff --git a/Helpers/HighlightText.h b/Helpers/HighlightText.h
new file mode 100644 (file)
index 0000000..66374b0
--- /dev/null
@@ -0,0 +1,101 @@
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <sstream>
+
+class StreamWrapper {
+
+public:
+    StreamWrapper() :
+        flushed(false) {
+    }
+
+    StreamWrapper(const StreamWrapper& stream) :
+        flushed(false) {
+        stream.flush(text);
+    }
+
+    ~StreamWrapper() {
+        flush(std::cout);
+        std::cout << std::flush;
+    }
+
+    template<typename T>
+    inline void append(const T& t) const {
+        text.precision(std::cout.precision());
+        text << t;
+    }
+
+    template<typename T>
+    inline std::ostream& operator<<(const T& t) const {
+        flush(std::cout);
+        return std::cout << t << std::flush;
+    }
+
+    inline std::ostream& operator<<(std::ostream& (*manip)(std::ostream&)) {
+        flush(std::cout);
+        manip(std::cout);
+        return std::cout;
+    }
+
+    inline operator std::string() const {
+        std::stringstream ss;
+        flush(ss);
+        return ss.str();
+    }
+
+protected:
+    inline void flush(std::ostream& stream) const {
+        if (!flushed) {
+            stream << text.str();
+            flushed = true;
+        }
+    }
+
+private:
+    mutable std::stringstream text;
+    mutable bool flushed;
+
+};
+
+inline std::ostream& operator<<(std::ostream& out, const StreamWrapper& stream) {
+    return out << (std::string)stream;
+}
+
+inline void concatenate(StreamWrapper&) {}
+
+template<typename T, typename... U>
+inline void concatenate(StreamWrapper& stream, const T& t, const U&... u) {
+    stream.append(t);
+    concatenate(stream, u...);
+}
+
+template<typename... T>
+inline StreamWrapper concatenate(const T&... t) {
+    StreamWrapper stream;
+    concatenate(stream, t...);
+    return stream;
+}
+
+template<typename... T>
+inline StreamWrapper red(const T&... t)     {return concatenate("\033[1;31m", t..., "\033[0m");}
+template<typename... T>
+inline StreamWrapper green(const T&... t)   {return concatenate("\033[1;32m", t..., "\033[0m");}
+template<typename... T>
+inline StreamWrapper yellow(const T&... t)  {return concatenate("\033[1;33m", t..., "\033[0m");}
+template<typename... T>
+inline StreamWrapper blue(const T&... t)    {return concatenate("\033[1;34m", t..., "\033[0m");}
+template<typename... T>
+inline StreamWrapper magenta(const T&... t) {return concatenate("\033[1;35m", t..., "\033[0m");}
+template<typename... T>
+inline StreamWrapper cyan(const T&... t)    {return concatenate("\033[1;36m", t..., "\033[0m");}
+template<typename... T>
+inline StreamWrapper white(const T&... t)   {return concatenate("\033[1;37m", t..., "\033[0m");}
+template<typename... T>
+inline StreamWrapper grey(const T&... t)   {return concatenate("\033[1;30m", t..., "\033[0m");}
+
+template<typename... T>
+inline StreamWrapper warning(const T&... t) {return concatenate("\n\033[33mWARNING: ", t..., "\033[0m\n");}
+template<typename... T>
+inline StreamWrapper error(const T&... t)   {return concatenate("\n\033[31mERROR: ", t..., "\033[0m\n");}
\ No newline at end of file
diff --git a/Helpers/IO/File.h b/Helpers/IO/File.h
new file mode 100644 (file)
index 0000000..ee1264b
--- /dev/null
@@ -0,0 +1,188 @@
+#pragma once
+
+#include <vector>
+#include <string>
+#include <fstream>
+#include <iostream>
+#include <iomanip>
+#include <stdio.h>
+
+#include "../Assert.h"
+#include "../String/Enumeration.h"
+#include "../FileSystem/FileSystem.h"
+
+namespace IO {
+
+    //#################################################### File #####################################################################//
+    inline FILE* openFile(const std::string& fileName) noexcept {
+        FILE* file = std::fopen(fileName.c_str(), "rb");
+        Ensure(file != nullptr, "cannot open file: " << fileName);
+        return file;
+    }
+
+    inline FILE* openFile(const std::vector<std::string>& fileNameAliases) noexcept {
+        FILE* file;
+        for (const std::string& fileName : fileNameAliases) {
+            file = std::fopen(fileName.c_str(), "rb");
+            if (file != nullptr) {
+                break;
+            }
+        }
+        if (file == nullptr) {
+            Enumeration e;
+            for (const std::string& fileName : fileNameAliases) {
+                e << fileName << sep;
+            }
+            Ensure(false, "Cannot open any of the files: " << e);
+        }
+        return file;
+    }
+
+    //################################################## IFStream ###################################################################//
+    class IFStream {
+
+    public:
+        IFStream(const IFStream& other) = delete;
+        IFStream& operator=(const IFStream& other) = delete;
+
+        IFStream(const std::string& fileName, std::ios::openmode mode = std::ios::in) : stream(nullptr) {
+            stream = new std::ifstream(fileName, mode);
+            Ensure(stream->is_open(), "cannot open file: " << fileName);
+        }
+        IFStream(const std::vector<std::string>& fileNameAliases, std::ios::openmode mode = std::ios::in) : stream(nullptr) {
+            for (const std::string& fileName : fileNameAliases) {
+                stream = new std::ifstream(fileName, mode);
+                if (stream->is_open()) {
+                    break;
+                } else {
+                    delete stream;
+                    stream = nullptr;
+                }
+            }
+            if (stream == nullptr) {
+                Enumeration e;
+                for (const std::string& fileName : fileNameAliases) {
+                    e << fileName << sep;
+                }
+                Ensure(false, "Cannot open any of the files: " << e);
+            }
+        }
+        IFStream(IFStream&& other) : stream(nullptr) {
+            stream = other.stream;
+            other.stream = nullptr;
+        }
+
+        ~IFStream() {
+            close();
+        }
+
+        IFStream& operator=(IFStream&& other) noexcept {
+            delete stream;
+            stream = other.stream;
+            other.stream = nullptr;
+            return *this;
+        }
+
+        inline std::ifstream& getStream() noexcept {
+            return *stream;
+        }
+        inline operator std::ifstream&() noexcept {
+            return *stream;
+        }
+
+        inline void close() noexcept {
+            stream->close();
+            delete stream;
+            stream = nullptr;
+        }
+        inline void read(char* s, std::streamsize n) noexcept {
+            stream->read(s, n);
+        }
+
+    private:
+        std::ifstream* stream;
+
+    };
+
+    //################################################## OFStream ###################################################################//
+    class OFStream {
+
+    public:
+        OFStream(const OFStream& other) = delete;
+        OFStream& operator=(const OFStream& other) = delete;
+
+        OFStream(const std::string& fileName, std::ios::openmode mode = std::ios::out) : stream(nullptr) {
+            std::string fullFileName = FileSystem::ensureDirectoryExists(fileName);
+            stream = new std::ofstream(fullFileName, mode);
+            Ensure(stream->is_open(), "cannot open file: " << fullFileName);
+            (*stream) << std::setprecision(10);
+        }
+        OFStream(const std::vector<std::string>& fileNameAliases, std::ios::openmode mode = std::ios::out) : stream(nullptr) {
+            for (const std::string& fileName : fileNameAliases) {
+                stream = new std::ofstream(fileName, mode);
+                if (stream->is_open()) {
+                    break;
+                } else {
+                    delete stream;
+                    stream = nullptr;
+                }
+            }
+            if (stream == nullptr) {
+                Enumeration e;
+                for (const std::string& fileName : fileNameAliases) {
+                    e << fileName << sep;
+                }
+                Ensure(false, "Cannot open any of the files: " << e);
+            } else {
+                (*stream) << std::setprecision(10);
+            }
+        }
+        OFStream(OFStream&& other) : stream(nullptr) {
+            stream = other.stream;
+            other.stream = nullptr;
+        }
+
+        ~OFStream() {
+            close();
+        }
+
+        OFStream& operator=(OFStream&& other) noexcept {
+            delete stream;
+            stream = other.stream;
+            other.stream = nullptr;
+            return *this;
+        }
+
+        template<typename T>
+        OFStream& operator<<(const T& t) noexcept {
+            (*stream) << t;
+            return *this;
+        }
+
+        inline std::ofstream& getStream() noexcept {
+            return *stream;
+        }
+        inline operator std::ofstream&() noexcept {
+            return *stream;
+        }
+
+        inline void flush() noexcept {
+            stream->flush();
+        }
+
+        inline void close() noexcept {
+            stream->flush();
+            stream->close();
+            delete stream;
+            stream = nullptr;
+        }
+        inline void write(const char* s, std::streamsize n) noexcept {
+            stream->write(s, n);
+        }
+
+    private:
+        std::ofstream* stream;
+
+    };
+
+}
diff --git a/Helpers/IO/IO.h b/Helpers/IO/IO.h
new file mode 100644 (file)
index 0000000..6a100e0
--- /dev/null
@@ -0,0 +1,209 @@
+#pragma once
+
+#include <vector>
+#include <string>
+#include <fstream>
+#include <iostream>
+#include <stdexcept>
+
+#include "File.h"
+
+#include "../Assert.h"
+#include "../String/Enumeration.h"
+
+namespace OldIO {
+
+    //################################################# String IO ###################################################################//
+    inline void writeStringBinary(const std::string& str, std::ofstream& os) {
+        Assert(os.is_open());
+        unsigned int sz = str.size();
+        os.write(reinterpret_cast<char*>(&sz), sizeof(unsigned int));
+        os.write(str.data(), sz);
+    }
+
+    inline void readStringBinary(std::string& str, std::ifstream& is) {
+        Assert(is.is_open());
+        unsigned int sz(0);
+        is.read(reinterpret_cast<char*>(&sz), sizeof(unsigned int));
+        str.resize(sz);
+        is.read(&str[0], sz);
+    }
+
+    //################################################## Item IO ####################################################################//
+    template<typename T>
+    inline void readItem(std::ifstream& f, T& item) {
+        Assert(f.is_open());
+        f.read(reinterpret_cast<char*>(&item), sizeof(T));
+    }
+    template<>
+    inline void readItem(std::ifstream& f, std::string& item) {readStringBinary(item, f);}
+    template<>
+    inline void readItem(std::ifstream& f, bool& item) {char b; readItem(f, b); item = b;}
+
+    template<typename T>
+    inline void writeItem(std::ofstream& f, const T& item) {
+        Assert(f.is_open());
+        f.write(reinterpret_cast<const char*>(&item), sizeof(T));
+    }
+    template<>
+    inline void writeItem(std::ofstream& f, const std::string& item) {writeStringBinary(item, f);}
+    template<>
+    inline void writeItem(std::ofstream& f, const bool& item) {char b = item; writeItem(f, b);}
+
+    //############################################# Indirect Vector IO ###############################################################//
+    template<typename T>
+    inline void readVectorIndirectly(std::ifstream& is, std::vector<T>& vector) {
+        Assert(is.is_open());
+        int size = 0;
+        readItem(is, size);
+        vector.resize(size);
+        for (int i = 0; i < size; ++i) {
+            vector[i].read(is);
+        }
+    }
+
+    template<typename T>
+    inline void writeVectorIndirectly(std::ofstream& os, const std::vector<T>& vector) {
+        Assert(os.is_open());
+        int size = vector.size();
+        writeItem(os, size);
+        for (int i = 0; i < size; ++i) {
+            vector[i].write(os);
+        }
+    }
+
+    //############################################## Direct Vector IO ################################################################//
+    template<typename T>
+    inline void readVectorDirectly(std::ifstream& is, std::vector<T>& vector) {
+        Assert(is.is_open());
+        int size = 0;
+        readItem(is, size);
+        vector.resize(size);
+        for (T& e : vector) {
+            readItem(is, e);
+        }
+    }
+    template<>
+    inline void readVectorDirectly(std::ifstream& is, std::vector<bool>& vector) {
+        Assert(is.is_open());
+        int size = 0;
+        readItem(is, size);
+        vector.resize(size);
+        for (int i = 0; i < size; ++i) {
+            char b;
+            readItem(is, b);
+            vector[i] = b;
+        }
+    }
+    template<typename T>
+    inline void readVectorDirectly(std::ifstream& is, std::vector<std::vector<T>>& vector) {
+        Assert(is.is_open());
+        int size = 0;
+        readItem(is, size);
+        vector.resize(size);
+        for (std::vector<T>& e : vector) {
+            readVectorDirectly(is, e);
+        }
+    }
+
+    template<typename T>
+    inline void writeVectorDirectly(std::ofstream& os, const std::vector<T>& vector) {
+        Assert(os.is_open());
+        int size = vector.size();
+        writeItem(os, size);
+        for (const T& e : vector) {
+            writeItem(os, e);
+        }
+    }
+    template<>
+    inline void writeVectorDirectly(std::ofstream& os, const std::vector<bool>& vector) {
+        Assert(os.is_open());
+        int size = vector.size();
+        writeItem(os, size);
+        for (int i = 0; i < size; ++i) {
+            const char b = vector[i];
+            writeItem(os, b);
+        }
+    }
+    template<typename T>
+    inline void writeVectorDirectly(std::ofstream& os, const std::vector<std::vector<T>>& vector) {
+        Assert(os.is_open());
+        int size = vector.size();
+        writeItem(os, size);
+        for (const std::vector<T>& e : vector) {
+            writeVectorDirectly(os, e);
+        }
+    }
+
+    //############################################### Vector Dump #################################################################//
+    template<class T>
+    inline void dumpVector(const std::string& fileName, const std::vector<T>& vec) {
+        std::ofstream os(fileName, std::ios::binary);
+        AssertMsg(os, "cannot open file: " << fileName);
+        AssertMsg(os.is_open(), "cannot open file: " << fileName);
+        os.write(reinterpret_cast<const char*>(&vec[0]), vec.size() * sizeof(T));
+    }
+    template<>
+    inline void dumpVector(const std::string& fileName, const std::vector<bool>& vec) {
+        std::ofstream os(fileName, std::ios::binary);
+        AssertMsg(os, "cannot open file: " << fileName);
+        AssertMsg(os.is_open(), "cannot open file: " << fileName);
+        writeVectorDirectly(os, vec);
+    }
+    template<>
+    inline void dumpVector(const std::string& fileName, const std::vector<std::string>& vec) {
+        std::ofstream os(fileName, std::ios::binary);
+        AssertMsg(os, "cannot open file: " << fileName);
+        AssertMsg(os.is_open(), "cannot open file: " << fileName);
+        writeVectorDirectly(os, vec);
+    }
+    template<class T>
+    inline void dumpVector(const std::string& fileName, const std::vector<std::vector<T>>& vec) {
+        std::ofstream os(fileName, std::ios::binary);
+        AssertMsg(os, "cannot open file: " << fileName);
+        AssertMsg(os.is_open(), "cannot open file: " << fileName);
+        writeVectorDirectly(os, vec);
+    }
+
+    template<class T>
+    inline void loadVector(const std::string& fileName, std::vector<T>& vec) {
+        std::ifstream is(fileName, std::ios::binary);
+        AssertMsg(is, "cannot open file: " << fileName);
+        AssertMsg(is.is_open(), "cannot open file: " << fileName);
+        is.seekg(0, std::ios::end);
+        unsigned long long file_size = is.tellg();
+        Assert(file_size % sizeof(T) == 0);
+        is.seekg(0, std::ios::beg);
+        vec.resize(file_size / sizeof(T));
+        is.read(reinterpret_cast<char*>(&vec[0]), file_size);
+    }
+    template<>
+    inline void loadVector(const std::string& fileName, std::vector<bool>& vec) {
+        std::ifstream is(fileName, std::ios::binary);
+        AssertMsg(is, "cannot open file: " << fileName);
+        AssertMsg(is.is_open(), "cannot open file: " << fileName);
+        readVectorDirectly(is, vec);
+    }
+    template<>
+    inline void loadVector(const std::string& fileName, std::vector<std::string>& vec) {
+        std::ifstream is(fileName, std::ios::binary);
+        AssertMsg(is, "cannot open file: " << fileName);
+        AssertMsg(is.is_open(), "cannot open file: " << fileName);
+        readVectorDirectly(is, vec);
+    }
+    template<class T>
+    inline void loadVector(const std::string& fileName, std::vector<std::vector<T>>& vec) {
+        std::ifstream is(fileName, std::ios::binary);
+        AssertMsg(is, "cannot open file: " << fileName);
+        AssertMsg(is.is_open(), "cannot open file: " << fileName);
+        readVectorDirectly(is, vec);
+    }
+
+    template<class T>
+    inline std::vector<T> loadVector(const std::string& fileName) {
+        std::vector<T> vec;
+        loadVector(fileName, vec);
+        return vec;
+    }
+
+}
diff --git a/Helpers/IO/Serialization.h b/Helpers/IO/Serialization.h
new file mode 100644 (file)
index 0000000..dcebb44
--- /dev/null
@@ -0,0 +1,303 @@
+#pragma once
+
+#include <array>
+#include <vector>
+#include <string>
+#include <fstream>
+#include <iostream>
+#include <stdexcept>
+#include <utility>
+
+#include "IO.h"
+#include "File.h"
+
+#include "../Assert.h"
+#include "../Meta.h"
+#include "../Vector/Vector.h"
+#include "../FileSystem/FileSystem.h"
+
+namespace IO {
+
+    //################################################# Magic Header ##################################################################//
+    inline constexpr int FileHeader = -1;
+
+    //################################################# Type Traits ###################################################################//
+    class Serialization;
+    class Deserialization;
+
+    namespace ImplementationDetail {
+        template<typename T, typename = void>
+        struct IsSerializable : Meta::False {};
+
+        template<typename T>
+        struct IsSerializable<T, decltype(std::declval<const T>().serialize(std::declval<Serialization&>()), void())> : Meta::True {};
+
+        template<typename T, typename = void>
+        struct IsDeserializable : Meta::False {};
+
+        template<typename T>
+        struct IsDeserializable<T, decltype(std::declval<T>().deserialize(std::declval<Deserialization&>()), void())> : Meta::True {};
+
+        template<typename T, typename = void>
+        struct IsVectorType : Meta::False {};
+
+        template<typename T>
+        struct IsVectorType<std::vector<T>, void> : Meta::True {};
+
+        template<typename T, typename = void>
+        struct IsArrayType : Meta::False {};
+
+        template<typename T, size_t N>
+        struct IsArrayType<std::array<T, N>, void> : Meta::True {};
+    }
+
+    template<typename T>
+    inline constexpr bool IsSerializable() {return ImplementationDetail::IsSerializable<T>::Value;}
+
+    template<typename T>
+    inline constexpr bool IsDeserializable() {return ImplementationDetail::IsDeserializable<T>::Value;}
+
+    template<typename T>
+    inline constexpr bool IsVectorType() {return ImplementationDetail::IsVectorType<T>::Value;}
+
+    template<typename T>
+    inline constexpr bool IsArrayType() {return ImplementationDetail::IsArrayType<T>::Value;}
+
+    //############################################### Stream Utilities ################################################################//
+    template<typename STREAM>
+    inline void checkStream(STREAM& stream) {
+        Ensure(stream.is_open(), "Cannot access a stream that is not open!");
+    }
+
+    template<typename STREAM>
+    inline void checkStream(STREAM& stream, const std::string& fileName) {
+        Ensure(stream, "cannot open file: " << fileName);
+        Ensure(stream.is_open(), "cannot open file: " << fileName);
+    }
+
+    //################################################# Serialization #################################################################//
+    class Serialization {
+
+    public:
+        template<typename... Ts>
+        Serialization(const std::string& fileName, const Ts&... objects) :
+            fileName(FileSystem::ensureDirectoryExists(fileName)),
+            os(fileName, std::ios::binary) {
+            checkStream(os, fileName);
+            serialize(FileHeader);          // Magic Header signaling that the following data represents a vector serialized by this code
+            operator()(objects...);
+        }
+
+    public:
+        inline void operator()() noexcept {}
+
+        template<typename T, typename... Ts>
+        inline void operator()(const T& object, const Ts&... objects) noexcept {
+            serialize(object);
+            operator()(objects...);
+        }
+
+        inline const std::string& getFileName() const noexcept {
+            return fileName;
+        }
+
+        inline void version(const size_t versionId) noexcept {
+            serialize(versionId);
+        }
+
+    private:
+        template<typename T>
+        inline void serialize(const T& object) noexcept {
+            checkStream(os);
+            if constexpr (IsSerializable<T>()) {
+                object.serialize(*this);
+            } else {
+                os.write(reinterpret_cast<const char*>(&object), sizeof(T));
+            }
+        }
+
+        inline void serialize(const std::string& stringObject) noexcept {
+            checkStream(os);
+            serialize(stringObject.size());
+            os.write(reinterpret_cast<const char*>(stringObject.data()), stringObject.size());
+        }
+
+        template<typename T>
+        inline void serialize(const std::vector<T>& vectorObject) noexcept {
+            checkStream(os);
+            serialize(Meta::type<T>());     // Type of the vector elements, used to check consistency during deserialization
+            serialize(vectorObject.size()); // Size of the serialized vector
+            if constexpr (Meta::Equals<T, bool>()) {
+                serialize(Vector::packBool(vectorObject));
+            } else if constexpr (IsSerializable<T>() || IsVectorType<T>() || IsArrayType<T>() || Meta::Equals<T, std::string>()) {
+                for (const T& element : vectorObject) {
+                    serialize(element);
+                }
+            } else {
+                os.write(reinterpret_cast<const char*>(vectorObject.data()), vectorObject.size() * sizeof(T));
+            }
+        }
+
+        template<typename T, size_t N>
+        inline void serialize(const std::array<T, N>& arrayObject) noexcept {
+            checkStream(os);
+            serialize(Meta::type<T>());     // Type of the vector elements, used to check consistency during deserialization
+            serialize(arrayObject.size());  // Size of the serialized vector
+            if constexpr (IsSerializable<T>() || IsVectorType<T>() || IsArrayType<T>() || Meta::Equals<T, std::string>()) {
+                for (const T& element : arrayObject) {
+                    serialize(element);
+                }
+            } else {
+                os.write(reinterpret_cast<const char*>(arrayObject.data()), arrayObject.size() * sizeof(T));
+            }
+        }
+
+    private:
+        const std::string fileName;
+        std::ofstream os;
+
+    };
+
+    //################################################ Deserialization ################################################################//
+    class Deserialization {
+
+    public:
+        template<typename T, typename... Ts>
+        Deserialization(const std::string& fileName, T& object, Ts&... objects) :
+            fileName(fileName),
+            is(fileName, std::ios::binary) {
+            checkStream(is, fileName);
+            int header;
+            deserialize(header);
+            Ensure(header == FileHeader, "No file header found, cannot read the file: " << fileName);
+            operator()(object, objects...);
+        }
+        template<typename T>
+        Deserialization(const std::string& fileName, std::vector<T>& object) :
+            fileName(fileName),
+            is(fileName, std::ios::binary) {
+            checkStream(is, fileName);
+            int header;
+            deserialize(header);
+            if (header == FileHeader) {         // Assume that the following vector was serialized using this code
+                operator()(object);
+            } else {                            // The following data was not serialized using this code, try to red data using OldIO::loadVector
+                warning("Trying to deserialize a file (", fileName, ") without magic header, using OldIO::loadVector instead!");
+                OldIO::loadVector(fileName, object);
+            }
+        }
+
+    public:
+        inline void operator()() noexcept {}
+
+        template<typename T, typename... Ts>
+        inline void operator()(T& object, Ts&... objects) noexcept {
+            deserialize(object);
+            operator()(objects...);
+        }
+
+        inline const std::string& getFileName() const noexcept {
+            return fileName;
+        }
+
+        inline void version(const size_t expectedVersionId) noexcept {
+            size_t fileVersionId = expectedVersionId - 1;
+            deserialize(fileVersionId);
+            Ensure(fileVersionId == expectedVersionId, "Expected version " << expectedVersionId << ", but file " << fileName << " has version " << fileVersionId);
+        }
+
+    private:
+        template<typename T>
+        inline void deserialize(T& object) noexcept {
+            checkStream(is);
+            if constexpr (IsDeserializable<T>()) {
+                object.deserialize(*this);
+            } else {
+                is.read(reinterpret_cast<char*>(&object), sizeof(T));
+            }
+        }
+
+        inline void deserialize(std::string& stringObject) noexcept {
+            checkStream(is);
+            decltype(stringObject.size()) size = 0;
+            deserialize(size);
+            stringObject.resize(size);
+            is.read(reinterpret_cast<char*>(&stringObject[0]), size);
+        }
+
+        template<typename T>
+        inline void deserialize(std::vector<T>& vectorObject) noexcept {
+            checkStream(is);
+            std::string type;
+            deserialize(type);
+            Ensure(type == Meta::type<T>(), "Trying to deserialize an std::vector<" << Meta::type<T>() << "> from a file that contains an std::vector<" << type << ">!");
+            decltype(vectorObject.size()) size = 0;
+            deserialize(size);
+            if constexpr (Meta::Equals<T, bool>()) {
+                std::vector<uint8_t> packedVector;
+                deserialize(packedVector);
+                vectorObject = Vector::unpackBool(packedVector);
+                vectorObject.resize(size);
+            } else if constexpr (IsSerializable<T>() || IsVectorType<T>() || IsArrayType<T>() || Meta::Equals<T, std::string>()) {
+                vectorObject.resize(size);
+                for (T& element : vectorObject) {
+                    deserialize(element);
+                }
+            } else {
+                vectorObject.resize(size);
+                is.read(reinterpret_cast<char*>(vectorObject.data()), size * sizeof(T));
+            }
+        }
+
+        template<typename T, size_t N>
+        inline void deserialize(std::array<T, N>& arrayObject) noexcept {
+            checkStream(is);
+            std::string type;
+            deserialize(type);
+            Ensure(type == Meta::type<T>(), "Trying to deserialize an std::array<" << Meta::type<T>() << "> from a file that contains an std::array<" << type << ">!");
+            decltype(arrayObject.size()) size = 0;
+            deserialize(size);
+            Ensure(size == N, "Trying to deserialize an array of size " << N << " from a file that contains an array of size " << size << "!");
+            if constexpr (IsSerializable<T>() || IsVectorType<T>() || IsArrayType<T>() || Meta::Equals<T, std::string>()) {
+                for (T& element : arrayObject) {
+                    deserialize(element);
+                }
+            } else {
+                is.read(reinterpret_cast<char*>(arrayObject.data()), N * sizeof(T));
+            }
+        }
+
+    private:
+        const std::string fileName;
+        std::ifstream is;
+
+    };
+
+    //################################################ File Utilities #################################################################//
+    template<typename... Ts>
+    inline void serialize(const std::string& fileName, const Ts&... objects) noexcept {
+        Serialization(fileName, objects...);
+    }
+
+    template<typename T, typename... Ts>
+    inline void deserialize(const std::string& fileName, T& object, Ts&... objects) noexcept {
+        Deserialization(fileName, object, objects...);
+    }
+
+    template<typename T>
+    inline T deserialize(const std::string& fileName) noexcept {
+        T object;
+        deserialize(fileName, object);
+        return object;
+    }
+
+    template<typename T>
+    inline bool checkHeader(const std::string& fileName) noexcept {
+        std::ifstream is(fileName, std::ios::binary);
+        checkStream(is, fileName);
+        int header;
+        is.read(reinterpret_cast<char*>(&header), sizeof(int));
+        return header == FileHeader;
+    }
+
+}
diff --git a/Helpers/JSONString.h b/Helpers/JSONString.h
new file mode 100644 (file)
index 0000000..e954216
--- /dev/null
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <string>
+#include <sstream>
+#include <iostream>
+
+class JSONString {
+
+public:
+
+    JSONString() :
+        json(""),
+        indentation(0),
+        emptyElement(true) {
+        json.precision(10);
+    }
+
+    void startNewObject() {
+        addNewElement();
+        indentation++;
+        json << "{";
+        newLine();
+    }
+
+    void endObject() {
+        indentation--;
+        newLine();
+        json << "}";
+        emptyElement = false;
+    }
+
+    void startNewArray() {
+        addNewElement();
+        indentation++;
+        json << "[";
+        newLine();
+    }
+
+    void endArray() {
+        indentation--;
+        newLine();
+        json << "]";
+        emptyElement = false;
+    }
+
+    void addQualifier(std::string name) {
+        addNewElement();
+        json << "\"" << name << "\":";
+    }
+
+    void addValue(std::string value) {
+        json << "\"" << value << "\"";
+        emptyElement = false;
+    }
+
+    void addValue(int value) {
+        json << value;
+        emptyElement = false;
+    }
+
+    void addValue(double value) {
+        json << value;
+        emptyElement = false;
+    }
+
+    void addQualifierWithValue(std::string name, std::string value) {
+        addQualifier(name);
+        addValue(value);
+    }
+
+    void addQualifierWithValue(std::string name, int value) {
+        addQualifier(name);
+        addValue(value);
+    }
+
+    void addQualifierWithValue(std::string name, double value) {
+        addQualifier(name);
+        addValue(value);
+    }
+
+    void newLine() {
+        json << std::endl;
+        addIndentation(indentation);
+    }
+
+    std::string str() {
+        json << std::flush;
+        return json.str();
+    }
+
+protected:
+
+    void addNewElement() {
+        if (!emptyElement) {
+            json << ",";
+            newLine();
+            emptyElement = true;
+        }
+    }
+
+    void addIndentation(int depth = 1) {
+        for (int i = 0; i < depth; i++) {
+            json << "    ";
+        }
+    }
+
+    std::stringstream json;
+    int indentation;
+    bool emptyElement;
+
+};
diff --git a/Helpers/Meta.h b/Helpers/Meta.h
new file mode 100644 (file)
index 0000000..7314fc1
--- /dev/null
@@ -0,0 +1,311 @@
+#pragma once
+
+#include <utility>
+#include <typeinfo>
+#include <type_traits>
+#include <iostream>
+#include <typeinfo>
+#include <emmintrin.h>
+
+#include <cstdlib>
+#include <memory>
+#include <cxxabi.h>
+
+#include "String/String.h"
+
+// Introducing namespace Meta, which shall contain all functions used for template meta programming
+
+namespace Meta {
+
+    // ID
+
+    template<typename T>
+    struct ID {using Type = T;};
+
+    // TRUE & FALSE
+
+    struct True {constexpr static bool Value = true;};
+
+    struct False {constexpr static bool Value = false;};
+
+    template<typename T>
+    struct FalseIfInstantiated {constexpr static bool Value = false;};
+
+    // EQUALS
+
+    namespace Implementation {
+        template<typename T, typename U>
+        struct Equals : False {};
+
+        template<typename T>
+        struct Equals<T, T> : True {};
+    }
+
+    template<typename T, typename U>
+    inline constexpr bool Equals() {return Implementation::Equals<T, U>::Value;}
+
+    // IF THEN ELSE
+
+    namespace Implementation {
+        template<bool CONDITION, typename IF_TYPE, typename ELSE_TYPE>
+        struct If {using Type = ELSE_TYPE;};
+
+        template<typename IF_TYPE, typename ELSE_TYPE>
+        struct If<true, IF_TYPE, ELSE_TYPE> {using Type = IF_TYPE;};
+    }
+
+    template<bool CONDITION, typename IF_TYPE, typename ELSE_TYPE>
+    using IF = typename Implementation::If<CONDITION, IF_TYPE, ELSE_TYPE>::Type;
+
+    // LIST
+
+    template<typename... VALUES>
+    struct List {
+        using Type = List<VALUES...>;
+        constexpr static size_t Size = sizeof...(VALUES);
+    };
+
+    // LIST HEAD
+
+    namespace Implementation {
+        template<typename LIST>
+        struct Head;
+
+        template<typename HEAD, typename... TAIL>
+        struct Head<List<HEAD, TAIL...>> : ID<HEAD> {};
+    }
+
+    template<typename LIST>
+    using Head = typename Implementation::Head<LIST>::Type;
+
+    // LIST TAIL
+
+    namespace Implementation {
+        template<typename LIST>
+        struct Tail;
+
+        template<typename HEAD, typename... TAIL>
+        struct Tail<List<HEAD, TAIL...>> : List<TAIL...> {};
+    }
+
+    template<typename LIST>
+    using Tail = typename Implementation::Head<LIST>::Type;
+
+    // LIST CONCAT
+
+    namespace Implementation {
+        template<typename LIST_A, typename LIST_B>
+        struct Concat;
+
+        template<typename... VALUES_A, typename... VALUES_B>
+        struct Concat<List<VALUES_A...>, List<VALUES_B...>> : List<VALUES_A..., VALUES_B...> {};
+    }
+
+    template<typename LIST_A, typename LIST_B>
+    using Concat = typename Implementation::Concat<LIST_A, LIST_B>::Type;
+
+    // LIST CONTAINS
+
+    namespace Implementation {
+        template<typename T, typename LIST>
+        struct Contains;
+
+        template<typename T>
+        struct Contains<T, List<>> : False {};
+
+        template<typename T, typename HEAD, typename... TAIL>
+        struct Contains<T, List<HEAD, TAIL...>> : IF<!Meta::Equals<T, HEAD>(),
+            Contains<T, List<TAIL...>>,
+            True> {
+        };
+    }
+
+    template<typename T, typename LIST>
+    inline constexpr bool Contains() {return Implementation::Contains<T, LIST>::Value;}
+
+    // TO STRING (for Types)
+
+    namespace Implementation {
+        inline std::string type(const char* name) noexcept {
+            int status = -4;
+            std::unique_ptr<char, void(*)(void*)> res {
+                abi::__cxa_demangle(name, NULL, NULL, &status),
+                std::free
+            };
+            return (status==0) ? res.get() : name;
+        }
+
+        inline std::string type(const std::string& name) noexcept {
+            return type(name.c_str());
+        }
+
+        inline std::string cleanType(const char* name) noexcept {
+            std::string typeID = type(name);
+            typeID = typeID.substr(9, typeID.size() - 10);
+            typeID = String::replaceAll(typeID, "> ", ">");
+            typeID = String::replaceAll(typeID, "::__debug::", "::");
+            size_t i = String::firstIndexOf(typeID, ", std::allocator<");
+            while (i < typeID.size()) {
+                int parenthesisCount = 1;
+                size_t j;
+                for (j = i + 17; j < typeID.size(); j++) {
+                    if (parenthesisCount == 0) break;
+                    if (typeID[j] == '<') parenthesisCount++;
+                    if (typeID[j] == '>') parenthesisCount--;
+                }
+                typeID = typeID.substr(0, i) + typeID.substr(j);
+                i = String::firstIndexOf(typeID, ", std::allocator<");
+            }
+            return typeID;
+        }
+
+        inline std::string cleanType(const std::string& name) noexcept {
+            return cleanType(name.c_str());
+        }
+
+        template<typename T>
+        struct Type;
+    }
+
+    template<typename T>
+    inline std::string type(T&&) noexcept {
+        return Implementation::cleanType(typeid(ID<T&&>).name());
+    }
+
+    template<typename T>
+    inline std::string type() noexcept {
+        return Implementation::cleanType(typeid(ID<T>).name());
+    }
+
+    template<typename T>
+    struct Type {Type(){Implementation::Type<T> type;}};
+
+    // MAKE CONST
+
+    namespace Implementation {
+        template<typename T>
+        struct MakeConst {using Type = const T;};
+
+        template<typename T>
+        struct MakeConst<T*> {using Type = const T*;};
+
+        template<typename T>
+        struct MakeConst<T* const> {using Type = const T* const;};
+
+        template<typename T>
+        struct MakeConst<T&> {using Type = const T&;};
+
+        template<typename T>
+        struct MakeConst<T&&> {using Type = const T&&;};
+    }
+
+    template<typename T>
+    using MakeConst = typename Implementation::MakeConst<T>::Type;
+
+    // IS CONST
+
+    template<typename T>
+    inline constexpr bool IsConst() {return Equals<T, MakeConst<T>>();}
+
+    // REMOVE CONSTNESS
+
+    namespace Implementation {
+        template<typename T>
+        struct RemoveConstness {using Type = T;};
+
+        template<typename T>
+        struct RemoveConstness<const T> {using Type = T;};
+
+        template<typename T>
+        struct RemoveConstness<const T*> {using Type = T*;};
+
+        template<typename T>
+        struct RemoveConstness<const T* const> {using Type = T*;};
+
+        template<typename T>
+        struct RemoveConstness<const T&> {using Type = T&;};
+
+        template<typename T>
+        struct RemoveConstness<const T&&> {using Type = T&&;};
+    }
+
+    template<typename T>
+    using RemoveConstness = typename Implementation::RemoveConstness<T>::Type;
+
+    // COPY CONSTNESS
+
+    template<typename FROM, typename TO>
+    using CopyConstness = IF<IsConst<FROM>(), MakeConst<TO>, TO>;
+
+    // IS REFERENCE
+
+    namespace Implementation {
+        template<typename T>
+        struct IsReference : False {};
+
+        template<typename T>
+        struct IsReference<T&> : True {};
+
+        template<typename T>
+        struct IsReference<T&&> : True {};
+    }
+
+    template<typename T>
+    inline constexpr bool IsReference() {return Implementation::IsReference<T>::Value;}
+
+    // REMOVE REFERENCE
+
+    namespace Implementation {
+        template<typename T>
+        struct RemoveReference {using Type = T;};
+
+        template<typename T>
+        struct RemoveReference<T&> {using Type = T;};
+
+        template<typename T>
+        struct RemoveReference<T&&> {using Type = T;};
+    }
+
+    template<typename T>
+    using RemoveReference = typename Implementation::RemoveReference<T>::Type;
+
+    // IS POINTER
+
+    namespace Implementation {
+        template<typename T>
+        struct IsPointer : False {};
+
+        template<typename T>
+        struct IsPointer<T*> : True {};
+    }
+
+    template<typename T>
+    inline constexpr bool IsPointer() {return Implementation::IsPointer<RemoveConstness<T>>::Value;}
+
+    // REMOVE POINTER
+
+    namespace Implementation {
+        template<typename T>
+        struct RemovePointer {using Type = T;};
+
+        template<typename T>
+        struct RemovePointer<T*> {using Type = T;};
+
+        template<typename T>
+        struct RemovePointer<T* const> {using Type = T;};
+    }
+
+    template<typename T>
+    using RemovePointer = typename Implementation::RemovePointer<T>::Type;
+
+    // PLAIN TYPE
+
+    template<typename T>
+    using PlainType = IF<IsReference<T>(), RemoveReference<RemoveConstness<T>>, RemovePointer<RemoveConstness<T>>>;
+
+    // IS MOVEABLE
+
+    template<typename T>
+    inline constexpr bool IsMovable() {return std::is_move_assignable<T>::value && std::is_move_constructible<T>::value;}
+
+}
diff --git a/Helpers/Ranges/ReverseRange.h b/Helpers/Ranges/ReverseRange.h
new file mode 100644 (file)
index 0000000..c221501
--- /dev/null
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <vector>
+
+#include "../Assert.h"
+
+template<typename RANGE>
+class ReverseRange {
+
+public:
+    using Range = RANGE;
+    using Type = ReverseRange<Range>;
+
+public:
+    class Iterator {
+    public:
+        Iterator(const Range* const range, const size_t i) : range(range), i(i) {}
+        inline bool operator!=(const Iterator& other) const noexcept {return i != other.i;}
+        inline auto operator*() const noexcept {return (*range)[i];}
+        inline Iterator& operator++() noexcept {--i; return *this;}
+        inline Iterator& operator+=(const size_t n) noexcept {i -= n; return *this;}
+        inline Iterator operator+(const size_t n) const noexcept {return Iterator(range, i - n);}
+        inline auto operator[](const size_t n) const noexcept {return (*range)[i - n];}
+    private:
+        const Range* range;
+        size_t i;
+    };
+
+    ReverseRange() :
+        range(nullptr) {
+    }
+
+    ReverseRange(const Range& range) :
+        range(&range) {
+    }
+
+    ReverseRange(const Range&&) = delete;
+
+    inline Iterator begin() const noexcept {
+        return Iterator(range, size() - 1);
+    }
+
+    inline Iterator end() const noexcept {
+        return Iterator(range, static_cast<size_t>(-1));
+    }
+
+    inline bool empty() const noexcept {
+        return (range) ? range->empty() : true;
+    }
+
+    inline size_t size() const noexcept {
+        return (range) ? range->size() : 0;
+    }
+
+    inline auto operator[](const size_t i) const noexcept {
+        AssertMsg(i < size(), "Index " << i << " is out of range!");
+        return (*range)[range->size() - 1 - i];
+    }
+
+    inline auto front() const noexcept {
+        AssertMsg(!empty(), "Range is empty!");
+        return range->back();
+    }
+
+    inline auto back() const noexcept {
+        AssertMsg(!empty(), "Range is empty!");
+        return range->front();
+    }
+
+private:
+    const Range* range;
+
+};
+
+template<typename RANGE>
+inline ReverseRange<RANGE> descending(const RANGE& range) {
+    return ReverseRange<RANGE>(range);
+}
+
+template<typename RANGE>
+inline ReverseRange<RANGE> descending(const RANGE&&) = delete;
diff --git a/Helpers/String/Enumeration.h b/Helpers/String/Enumeration.h
new file mode 100644 (file)
index 0000000..8416aab
--- /dev/null
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <sstream>
+
+struct Sep {
+    Sep(const std::string& sep = ", ") : sep(sep) {}
+    std::string sep;
+};
+Sep newline("\n\r");
+Sep clear("");
+Sep sep(", ");
+
+class Enumeration {
+
+public:
+    template<typename T>
+    friend inline Enumeration& operator<<(Enumeration& out, const T& en);
+    friend inline std::ostream& operator<<(std::ostream& out, const Enumeration& en);
+
+public:
+    Enumeration(const std::string& sep = "") : sep(sep) {}
+
+    inline operator std::string() const noexcept {
+        return state.str();
+    }
+
+    inline std::string str() const noexcept {
+        return state.str();
+    }
+
+    inline bool empty() noexcept {
+        return state.str().size() == 0;
+    }
+
+private:
+
+    std::stringstream state;
+    std::string sep;
+
+};
+
+inline std::ostream& operator<<(std::ostream& out, const Enumeration& en) {
+    return out << en.state.str();
+}
+
+template<typename T>
+inline Enumeration& operator<<(Enumeration& out, const T& t) {
+    out.state << out.sep << t;
+    out.sep = "";
+    return out;
+}
+
+template<>
+inline Enumeration& operator<<(Enumeration& out, const Sep& sep) {
+    out.sep = sep.sep;
+    return out;
+}
+
+template<>
+inline Enumeration& operator<<(Enumeration& out, const Enumeration& en) {
+    out.state << out.sep << en.state.str();
+    out.sep = "";
+    return out;
+}
diff --git a/Helpers/String/String.h b/Helpers/String/String.h
new file mode 100644 (file)
index 0000000..ee10d31
--- /dev/null
@@ -0,0 +1,563 @@
+#pragma once
+
+#include <ctime>
+#include <cmath>
+#include <vector>
+#include <string>
+#include <bitset>
+#include <sstream>
+#include <cstdlib>
+#include <iostream>
+#include <limits.h>
+#include <algorithm>
+
+#include "../Assert.h"
+#include "../Ranges/ReverseRange.h"
+
+namespace std {
+
+    std::string to_string(bool b) {
+        if (b) {
+            return "true";
+        } else {
+            return "false";
+        }
+    }
+
+}
+
+namespace String {
+
+    inline std::string toUpper(const std::string& s) {
+        std::string out;
+        std::transform(s.begin(), s.end(), back_inserter(out), ::toupper);
+        return out;
+    }
+
+    inline std::string toLower(const std::string& s) {
+        std::string out;
+        std::transform(s.begin(), s.end(), back_inserter(out), ::tolower);
+        return out;
+    }
+
+    inline std::string firstToUpper(const std::string& s) {
+        if (s.empty()) return s;
+        std::string result = s;
+        if (s[0] >= 'a' && s[0] <= 'z') {
+            result[0] = result[0] - 'a' + 'A';
+        }
+        return result;
+    }
+
+    inline std::string firstToLower(const std::string& s) {
+        if (s.empty()) return s;
+        std::string result = s;
+        if (s[0] >= 'A' && s[0] <= 'Z') {
+            result[0] = result[0] - 'A' + 'a';
+        }
+        return result;
+    }
+
+    inline bool contains(const std::string& fullString, const char searchChar) noexcept {
+        for (const char c : fullString) {
+            if (c == searchChar) return true;
+        }
+        return false;
+    }
+
+    inline bool containsSubString(const std::string& fullString, const size_t index, const std::string& subString) noexcept {
+        if (index + subString.size() > fullString.size()) return false;
+        for (size_t i = 0; i < subString.size(); i++) {
+            if (fullString[index + i] != subString[i]) return false;
+        }
+        return true;
+    }
+
+    inline bool containsSubString(const std::string& fullString, const std::string& subString) noexcept {
+        if (subString.size() > fullString.size()) return false;
+        for (size_t i = 0; i <= fullString.size() - subString.size(); i++) {
+            if (containsSubString(fullString, i, subString)) return true;
+        }
+        return false;
+    }
+
+    inline size_t firstIndexOf(const std::string& fullString, const std::string& subString) noexcept {
+        if (subString.size() > fullString.size()) return fullString.size();
+        for (size_t i = 0; i <= fullString.size() - subString.size(); i++) {
+            if (containsSubString(fullString, i, subString)) return i;
+        }
+        return fullString.size();
+    }
+
+    inline size_t lastIndexOf(const std::string& fullString, const std::string& subString) noexcept {
+        if (subString.size() > fullString.size()) return fullString.size();
+        for (size_t i = fullString.size() - subString.size(); i < fullString.size(); i--) {
+            if (containsSubString(fullString, i, subString)) return i;
+        }
+        return fullString.size();
+    }
+
+    inline bool endsWith(const std::string& fullString, const std::string& endString) {
+        if (fullString.size() < endString.size()) return false;
+        return fullString.compare(fullString.size() - endString.size(), endString.size(), endString) == 0;
+    }
+
+    inline bool beginsWith(const std::string& fullString, const std::string& endString) {
+        if (fullString.size() < endString.size()) return false;
+        return fullString.compare(0, endString.size(), endString) == 0;
+    }
+
+    template <typename CONTAINER_TYPE>
+    inline std::string join(const CONTAINER_TYPE& container, const std::string& delimiter) {
+        std::ostringstream s;
+        for (typename CONTAINER_TYPE::const_iterator i = container.begin(); i != container.end(); ++i) {
+            if (i != container.begin()) {
+                s << delimiter;
+            }
+            s << *i;
+        }
+        return s.str();
+    }
+
+    template<typename TYPE>
+    inline bool isNumber(const std::string& s) {
+        TYPE n;
+        return ((std::istringstream(s) >> n >> std::ws).eof());
+    }
+
+    template<typename U, typename T>
+    inline U lexicalCast(const T& in) {
+        std::stringstream ss;
+        ss << in;
+        U out;
+        ss >> out;
+        return out;
+    }
+    template<>
+    inline bool lexicalCast(const std::string& in) {
+        return (in == "1") || (toLower(in) == "true");
+    }
+    template<>
+    inline int lexicalCast(const std::string& in) {return atoi(in.c_str());}
+    template<>
+    inline double lexicalCast(const std::string& in) {return atof(in.c_str());}
+    template<>
+    inline std::string lexicalCast(const std::string& in) {return in;}
+
+    inline std::vector<std::string>& split(const std::string& s, const char delim, std::vector<std::string>& elems) {
+        std::stringstream ss(s);
+        std::string item;
+        while(std::getline(ss, item, delim)) {
+            elems.push_back(item);
+        }
+        return elems;
+    }
+    inline std::vector<std::string> split(const std::string& s, const char delim) {
+        std::vector<std::string> elems;
+        return split(s, delim, elems);
+    }
+
+    inline size_t count(const std::string& s, const char c) {
+        size_t result = 0;
+        for (const char x : s) {
+            if (x == c) result++;
+        }
+        return result;
+    }
+
+    inline std::string whiteSpace(const int size) {
+        std::stringstream result;
+        for (int i = 0; i < size; i++) {
+            result << " ";
+        }
+        return result.str();
+    }
+
+    inline bool isWhiteSpace(const char c) {
+        return (c == ' ') || (c == '\t') || (c == '\n');
+    }
+
+    inline bool isDigit(const char c) {
+        return (c >= '0') & (c <= '9');
+    }
+
+    inline std::string percent(double p) {
+        if (p != p) return "100.00%";
+        int a = p * 100;
+        int b = ((int)(p * 10000)) % 100;
+        if (b < 10) {
+            return std::to_string(a) + ".0" + std::to_string(b) + "%";
+        } else {
+            return std::to_string(a) + "." + std::to_string(b) + "%";
+        }
+    }
+
+    template<typename T>
+    inline std::string prettyInt(T i) {
+        if (i >= std::numeric_limits<T>::max()) return "infinity";
+        std::vector<std::string> tokens;
+        std::stringstream ss;
+        if (i < 0) {
+            ss << "-";
+            i = -i;
+        }
+        if (i >= std::numeric_limits<T>::max()) return "-infinity";
+        while (i >= 1000) {
+            const short block = i % 1000;
+            i = i / 1000;
+            tokens.push_back(std::to_string(block));
+            if (block < 10) {
+                tokens.push_back("00");
+            } else if (block < 100) {
+                tokens.push_back("0");
+            }
+            tokens.push_back(",");
+        }
+        tokens.push_back(std::to_string(static_cast<short>(i)));
+        for (const std::string& s : descending(tokens)) ss << s;
+        return ss.str();
+    }
+
+    inline std::string prettyInt(double i) {return prettyInt<int>(i);}
+
+    inline std::string prettyDouble(const double number, const int precision = 2, const bool includePreDecimals = false) {
+        const std::string numberString = std::to_string(number);
+        std::vector<char> digits;
+        digits.reserve(numberString.size() + 1);
+        int decimalPoint = 0;
+        for (int i = numberString.size() - 1; i >= 0; i--) {
+            if (numberString[i] == '.') {
+                decimalPoint = digits.size();
+            } else if (numberString[i] != '-') {
+                digits.emplace_back(numberString[i]);
+            }
+        }
+        const int numberOfDigits = digits.size();
+        const int roundingPoint = (includePreDecimals) ? (numberOfDigits - precision - 1) : (decimalPoint - precision - 1);
+        if (roundingPoint >= numberOfDigits) return "0";
+        if (roundingPoint >= 0) {
+            int lastDigit = roundingPoint + 1;
+            if (digits[roundingPoint] >= '5') {
+                digits.emplace_back('0');
+                while (digits[lastDigit] == '9') {
+                    lastDigit++;
+                }
+                digits[lastDigit]++;
+            }
+            for (int i = 0; i < lastDigit; i++) {
+                digits[i] = '0';
+            }
+            while ((digits.back() == '0') && (decimalPoint + 1 < static_cast<int>(digits.size()))) {
+                digits.pop_back();
+            }
+        }
+        std::stringstream result;
+        if (number < 0) result << '-';
+        for (int i = digits.size() - 1; i > decimalPoint; i--) {
+            result << digits[i];
+            if (i % 3 == decimalPoint % 3) result << ',';
+        }
+        result << digits[decimalPoint];
+        if (roundingPoint + 1 < decimalPoint) result << '.';
+        for (int i = decimalPoint - 1; (i > roundingPoint) && (i >= 0); i--) {
+            result << digits[i];
+        }
+        for (int i = -1; i > roundingPoint; i--) {
+            result << '0';
+        }
+        return result.str();
+    }
+
+    template<typename T>
+    inline std::string binary(const T& value) {
+        std::stringstream result;
+        const char* end = reinterpret_cast<const char*>(&value) - 1;
+        const char* begin = end + sizeof(T);
+        result << std::bitset<CHAR_BIT>(*begin--);
+        while (begin != end) result << " " << std::bitset<CHAR_BIT>(*begin--);
+        return result.str();
+    }
+
+    inline std::string colorToString(int r, int g, int b) {
+        int rLow = r % 16;
+        int rHigh = (r / 16) % 16;
+        int gLow = g % 16;
+        int gHigh = (g / 16) % 16;
+        int bLow = b % 16;
+        int bHigh = (b / 16) % 16;
+        std::stringstream color;
+        color << std::hex << rHigh << std::hex << rLow << std::hex << gHigh << std::hex << gLow << std::hex << bHigh << std::hex << bLow;
+        return color.str();
+    }
+
+    inline bool isColor(const std::string& color) {
+        if (color.size() != 6) return false;
+        for (int i = 0; i < 6; i++) {
+            const char c = color[i];
+            if (c >= '0' && c <= '9') continue;
+            if (c >= 'a' && c <= 'f') continue;
+            if (c >= 'A' && c <= 'F') continue;
+            return false;
+        }
+        return true;
+    }
+
+    template<typename T>
+    inline std::string secToString(T t, T infinity = std::numeric_limits<T>::max()) {
+        if (t >= infinity) return "infinity";
+        if ((!(t >= 0)) && (t <= -infinity)) return "-infinity";
+        const bool negative = (t < 0);
+        t = negative ? -t : t;
+        if (t == 0) return "0s";
+        std::string result = std::to_string(t % 60) + "s";
+        std::string fillIn = ((t % 60) < 10) ? " " : "";
+        t = t / 60;
+        if (t > 0) {
+            result = std::to_string(t % 60) + "m " + fillIn + result;
+            fillIn = ((t % 60) < 10) ? " " : "";
+            t = t / 60;
+            if (t > 0) {
+                result = std::to_string(t % 24) + "h " + fillIn + result;
+                fillIn = ((t % 60) < 10) ? " " : "";
+                t = t / 24;
+                if (t > 0) {
+                    result = std::to_string(t) + "d " + fillIn + result;
+                }
+            }
+        }
+        if (negative) result = "- " + result;
+        return result;
+    }
+
+    inline std::string secToString(double t) {return secToString<int>(t);}
+    inline std::string secToString(double t, int infinity) {return secToString<int>(t, infinity);}
+
+    template<typename T>
+    inline std::string secToTime(T t, T infinity = std::numeric_limits<T>::max(), bool displaySeconds = false) {
+        if (t >= infinity) return "infinity";
+        if ((!(t >= 0)) && (t <= -infinity)) return "-infinity";
+        const bool negative = (t < 0);
+        t = negative ? -t : t;
+        if (t == 0) return "0:00";
+        T s = t % 60;
+        T m = (t / 60) % 60;
+        T h = (t / 3600) % 24;
+        T d = t / 86400;
+        std::string result;
+        if (d != 0) result += std::to_string(d) + "d ";
+        result += std::to_string(h) + ":";
+        if (m < 10) result += "0";
+        result += std::to_string(m);
+        if (s != 0 || displaySeconds) {
+            result += ":";
+            if (s < 10) result += "0";
+            result += std::to_string(s);
+        }
+        if (negative) result = "- " + result;
+        return result;
+    }
+
+    template<typename T>
+    inline std::string secToTime(T t, bool displaySeconds) {return secToTime(t, std::numeric_limits<T>::max(), displaySeconds);}
+    inline std::string secToTime(double t, int infinity, bool displaySeconds = false) {return secToTime<int>(t, infinity, displaySeconds);}
+    inline std::string secToTime(double t, bool displaySeconds = false) {return secToTime<int>(t, std::numeric_limits<int>::max(), displaySeconds);}
+
+    template<typename T>
+    inline std::string msToString(T t, T infinity = std::numeric_limits<T>::max()) {
+        if (t >= infinity) return "infinity";
+        if ((!(t >= 0)) && (t <= -infinity)) return "-infinity";
+        if (t == 0) return "0ms";
+        std::string result = std::to_string(t % 1000) + "ms";
+        if (t >= 1000) {
+            std::string fillIn = "";
+            if ((t % 1000) < 10) fillIn = "  ";
+            else if ((t % 1000) < 100) fillIn = " ";
+            result = secToString(t / 1000) + " " + fillIn + result;
+        }
+        return result;
+    }
+
+    inline std::string msToString(double t) {return msToString<int>(t);}
+    inline std::string msToString(double t, int infinity) {return msToString<int>(t, infinity);}
+
+    template<typename T>
+    inline std::string musToString(T t, T infinity = std::numeric_limits<T>::max()) {
+        if (t >= infinity) return "infinity";
+        if ((!(t >= 0)) && (t <= -infinity)) return "-infinity";
+        if (t == 0) return "0µs";
+        std::string result = std::to_string(t % 1000) + "µs";
+        if (t >= 1000) {
+            std::string fillIn = "";
+            if ((t % 1000) < 10) fillIn = "  ";
+            else if ((t % 1000) < 100) fillIn = " ";
+            result = msToString(t / 1000) + " " + fillIn + result;
+        }
+        return result;
+    }
+
+    inline std::string musToString(double t) {return musToString<int>(t);}
+    inline std::string musToString(double t, int infinity) {return musToString<int>(t, infinity);}
+
+    inline std::string timeString() {
+        time_t now = time(0);
+        tm *ltm = localtime(&now);
+        const int seconds = (((ltm->tm_hour * 60) + ltm->tm_min) * 60) + ltm->tm_sec;
+        return secToTime(seconds, true);
+    }
+
+    inline std::string dateString() {
+        time_t now = time(0);
+        tm *ltm = localtime(&now);
+        const std::string year = std::to_string(ltm->tm_year + 1900);
+        const std::string month = std::to_string(ltm->tm_mon + 1);
+        const std::string day = std::to_string(ltm->tm_mday);
+        std::stringstream ss;
+        if (year.size() == 1) ss << "000";
+        if (year.size() == 2) ss << "00";
+        if (year.size() == 3) ss << '0';
+        ss << year;
+        if (month.size() == 1) ss << '0';
+        ss << month;
+        if (day.size() == 1) ss << '0';
+        ss << day;
+        return ss.str();
+    }
+
+    inline std::string bytesToString(const long long bytes, const double k = 1000.0) {
+        if (bytes <= 1) return prettyInt(bytes) + "Byte";
+        if (bytes < k) return prettyInt(bytes) + "Bytes";
+        double b = bytes / k;
+        if (b < k) return prettyDouble(b) + "KB";
+        b = b / k;
+        if (b < k) return prettyDouble(b) + "MB";
+        b = b / k;
+        if (b < k) return prettyDouble(b) + "GB";
+        b = b / k;
+        return prettyDouble(b) + "TB";
+    }
+
+    inline int parseSeconds(const std::string& time) {
+        int seconds = 0;
+        int value = 0;
+        for (size_t i = 0; i < time.size(); i++) {
+            if (String::isDigit(time[i])) {
+                value = (value * 10) + static_cast<int>(time[i] - '0');
+            } else if (time[i] == ':') {
+                seconds = (seconds * 60) + value;
+                value = 0;
+            } else {
+                error("The string " + time + " is not in the format HH:MM:SS");
+                return -1;
+            }
+        }
+        return (seconds * 60) + value;
+    }
+
+    inline int parseDay(const std::string& time) {
+        if (time.size() != 8) error("The string " + time + " is not in the format YYYYMMDD");
+        int year = lexicalCast<int>(time.substr(0, 4)) - 1900;
+        int month = lexicalCast<int>(time.substr(4, 2)) - 1;
+        int day = lexicalCast<int>(time.substr(6, 2));
+        std::tm t = {0,0,12,day,month,year, 0, 0, 0, 0, 0};
+        time_t seconds = std::mktime(&t);
+        return (seconds < 0) ? (seconds / (60 * 60 * 24)) - 1 : (seconds / (60 * 60 * 24));
+    }
+
+    std::string trim(const std::string& s) {
+        std::stringstream ss;
+        int end = s.size() - 1;
+        while (end >= 0 && isWhiteSpace(s[end])) end--;
+        bool omitWhiteSpace = true;
+        for (int i = 0; i <= end; i++) {
+            char c = s[i];
+            if (isWhiteSpace(c)) {
+                if (!omitWhiteSpace) {
+                    ss << " ";
+                    omitWhiteSpace = true;
+                }
+            } else {
+                ss << c;
+                omitWhiteSpace = false;
+            }
+        }
+        return ss.str();
+    }
+
+    std::string replaceAll(const std::string& s, const char c, const std::string& replacement) {
+        std::stringstream ss;
+        for (size_t i = 0; i < s.size(); i++) {
+            char cs = s[i];
+            if (cs == c) {
+                ss << replacement;
+            } else {
+                ss << cs;
+            }
+        }
+        return ss.str();
+    }
+
+    std::string replaceAll(const std::string& s, const std::string& pattern, const std::string& replacement) {
+        if (pattern.size() > s.size()) return s;
+        std::stringstream ss;
+        size_t i = 0;
+        while (i <= s.size() - pattern.size()) {
+            bool matches = true;
+            for (size_t j = 0; j < pattern.size(); j++) {
+                if (s[i + j] != pattern[j]) {
+                    matches = false;
+                    break;
+                }
+            }
+            if (matches) {
+                ss << replacement;
+                i += pattern.size();
+            } else {
+                ss << s[i];
+                i++;
+            }
+        }
+        while (i < s.size()) {
+            ss << s[i];
+            i++;
+        }
+        return ss.str();
+    }
+
+    inline std::string longestCommonSubstring(const std::string& str1, const std::string& str2) {
+        if(str1.empty() || str2.empty()) return "";
+
+        size_t* curr = new size_t[str2.size()];
+        size_t* prev = new size_t[str2.size()];
+        size_t maxSubstr = 0;
+        size_t pos;
+
+        for(size_t i = 0; i < str1.size(); ++i) {
+            for(size_t j = 0; j < str2.size(); ++j) {
+                if(str1[i] != str2[j]) {
+                    curr[j] = 0;
+                } else {
+                    if(i == 0 || j == 0) {
+                        curr[j] = 1;
+                    } else {
+                        curr[j] = prev[j - 1] + 1;
+                    }
+                    if (maxSubstr < curr[j]) {
+                        maxSubstr = curr[j];
+                        pos = j - curr[j] + 1;
+                    }
+                }
+            }
+            std::swap(curr, prev);
+        }
+
+        delete[] curr;
+        delete[] prev;
+
+        std::stringstream ss;
+        for (size_t i = 0; i < maxSubstr; i++) {
+            ss << str2[pos + i];
+        }
+        return ss.str();
+    }
+
+}
diff --git a/Helpers/TaggedInteger.h b/Helpers/TaggedInteger.h
new file mode 100644 (file)
index 0000000..d1dbae4
--- /dev/null
@@ -0,0 +1,185 @@
+#pragma once
+
+#include <sstream>
+#include <iostream>
+
+#include "Assert.h"
+
+#include "Meta.h"
+#include "String/String.h"
+
+template<int TAG, typename VALUE_TYPE, VALUE_TYPE INVALID, VALUE_TYPE DEFAULT = INVALID, typename... ADDITIONAL_CASTS>
+class TaggedInteger {
+
+public:
+    using ValueType = VALUE_TYPE;
+    constexpr static ValueType InvalidValue = INVALID;
+    constexpr static ValueType DefaultValue = DEFAULT;
+    using AdditionalCasts = Meta::List<ADDITIONAL_CASTS...>;
+    using Type = TaggedInteger<TAG, ValueType, InvalidValue, DefaultValue, ADDITIONAL_CASTS...>;
+
+    using iterator_category = std::random_access_iterator_tag;
+    using value_type = ValueType;
+    using difference_type = ValueType;
+    using pointer = Type;
+    using reference = ValueType&;
+
+public:
+    constexpr TaggedInteger() : internalValue(DefaultValue) {}
+
+    constexpr explicit TaggedInteger(const ValueType& value) : internalValue(value) {}
+
+    constexpr inline operator const ValueType&() const noexcept {return internalValue;}
+
+    template<typename T, typename = typename std::enable_if_t<Meta::Contains<T, AdditionalCasts>()>>
+    constexpr inline operator T() const noexcept {return T(internalValue);}
+
+    constexpr inline const ValueType& value() const noexcept {return internalValue;}
+    constexpr inline const Type& operator*() const noexcept {return *this;}
+
+    constexpr static Type Invalid() {return TaggedInteger(InvalidValue);}
+
+    inline bool isValid()  const noexcept {return internalValue != InvalidValue;}
+    inline bool isInvalid() const noexcept {return internalValue == InvalidValue;}
+    inline void invalidate() noexcept {internalValue = InvalidValue;}
+
+    inline bool operator<(const Type& other)  const noexcept {return internalValue < other.internalValue;}
+    inline bool operator>(const Type& other)  const noexcept {return internalValue > other.internalValue;}
+    inline bool operator<=(const Type& other) const noexcept {return internalValue <= other.internalValue;}
+    inline bool operator>=(const Type& other) const noexcept {return internalValue >= other.internalValue;}
+    inline bool operator==(const Type& other) const noexcept {return internalValue == other.internalValue;}
+    inline bool operator!=(const Type& other) const noexcept {return internalValue != other.internalValue;}
+
+    inline Type& operator+=(const Type& other) noexcept {
+        AssertMsg(isValid(), "Cannot add something to an Invalid value.");
+        AssertMsg(other.isValid(), "Cannot add an Invalid value to something.");
+        internalValue += other.internalValue;
+        return *this;
+    }
+    inline Type& operator+=(const ValueType& other) noexcept {
+        AssertMsg(isValid(), "Cannot add something to an Invalid value.");
+        internalValue += other;
+        return *this;
+    }
+
+    inline Type& operator-=(const Type& other) noexcept {
+        AssertMsg(isValid(), "Cannot subtract from an Invalid value.");
+        AssertMsg(other.isValid(), "Cannot subtract an Invalid value");
+        internalValue -= other.internalValue;
+        return *this;
+    }
+    inline Type& operator-=(const ValueType& other) noexcept {
+        AssertMsg(isValid(), "Cannot subtract from an Invalid value.");
+        internalValue -= other;
+        return *this;
+    }
+
+    inline Type& operator*=(const Type& other) noexcept {
+        AssertMsg(isValid(), "Cannot multiply something with an Invalid value.");
+        AssertMsg(other.isValid(), "Cannot multiply an Invalid value with something.");
+        internalValue *= other.internalValue;
+        return *this;
+    }
+    inline Type& operator*=(const ValueType& other) noexcept {
+        AssertMsg(isValid(), "Cannot multiply something with an Invalid value.");
+        internalValue *= other;
+        return *this;
+    }
+
+    inline Type& operator/=(const Type& other) noexcept {
+        AssertMsg(isValid(), "Cannot divide an Invalid value.");
+        AssertMsg(other.isValid(), "Cannot divide something by an Invalid value.");
+        internalValue /= other.internalValue;
+        return *this;
+    }
+    inline Type& operator/=(const ValueType& other) noexcept {
+        AssertMsg(isValid(), "Cannot divide an Invalid value.");
+        internalValue /= other;
+        return *this;
+    }
+
+    inline Type operator+(const Type& other) const noexcept {
+        AssertMsg(isValid(), "Cannot add something to an Invalid value.");
+        AssertMsg(other.isValid(), "Cannot add an Invalid value to something.");
+        return Type(internalValue + other.internalValue);
+    }
+
+    inline Type operator-() const noexcept {
+        AssertMsg(isValid(), "Cannot subtract from an Invalid value.");
+        return Type(-internalValue);
+    }
+
+    inline Type operator-(const Type& other) const noexcept {
+        AssertMsg(isValid(), "Cannot subtract from an Invalid value.");
+        AssertMsg(other.isValid(), "Cannot subtract an Invalid value");
+        return Type(internalValue - other.internalValue);
+    }
+
+    inline Type operator*(const Type& other) const noexcept {
+        AssertMsg(isValid(), "Cannot multiply something with an Invalid value.");
+        AssertMsg(other.isValid(), "Cannot multiply an Invalid value with something.");
+        return Type(internalValue * other.internalValue);
+    }
+
+    inline Type operator/(const Type& other) const noexcept {
+        AssertMsg(isValid(), "Cannot divide an Invalid value.");
+        AssertMsg(other.isValid(), "Cannot divide something by an Invalid value.");
+        return Type(internalValue / other.internalValue);
+    }
+
+    inline Type& operator++() noexcept {
+        AssertMsg(isValid(), "Cannot increment an Invalid value.");
+        internalValue++;
+        return *this;
+    }
+
+    inline Type operator++(int) noexcept {
+        AssertMsg(isValid(), "Cannot increment an Invalid value.");
+        internalValue++;
+        return Type(internalValue - 1);
+    }
+
+    inline Type& operator--() noexcept {
+        AssertMsg(isValid(), "Cannot decrement an Invalid value.");
+        internalValue--;
+        return *this;
+    }
+
+    inline Type operator--(int) noexcept {
+        AssertMsg(isValid(), "Cannot decrement an Invalid value.");
+        internalValue--;
+        return Type(internalValue + 1);
+    }
+
+    inline friend std::istream& operator>>(std::istream& in, Type& type) noexcept {
+        in >> type.internalValue;
+        return in;
+    }
+
+    inline friend std::ostream& operator<<(std::ostream& out, const Type& type) noexcept {
+        if (type.isValid()) {
+            return out << String::prettyInt(type.internalValue);
+        } else {
+            return out << "Invalid";
+        }
+    }
+
+    inline friend std::string operator+(const std::string& string, const Type& type) noexcept {
+        std::stringstream result;
+        result << string << type;
+        return result.str();
+    }
+
+    inline friend std::string operator+(const char string[], const Type& type) noexcept {
+        std::stringstream result;
+        result << string << type;
+        return result.str();
+    }
+
+private:
+    ValueType internalValue;
+
+};
+
+template<int TAG, typename DEPENDENCE>
+using DependentTaggedInteger = TaggedInteger<TAG, typename DEPENDENCE::ValueType, DEPENDENCE::InvalidValue, DEPENDENCE::DefaultValue, DEPENDENCE>;
diff --git a/Helpers/Timer.h b/Helpers/Timer.h
new file mode 100644 (file)
index 0000000..14fe409
--- /dev/null
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <sys/time.h>
+
+class Timer {
+
+public:
+    Timer() : start(timestamp()) {}
+
+    inline void restart() noexcept {
+        start = timestamp();
+    }
+
+    inline double elapsedMicroseconds() const noexcept {
+        double cur = timestamp();
+        return cur - start;
+    }
+
+    inline double elapsedMilliseconds() const noexcept {
+        double cur = timestamp();
+        return (cur - start) / 1000.0;
+    }
+
+private:
+    inline static double timestamp() noexcept {
+        timeval tp;
+        gettimeofday(&tp, nullptr);
+        double mus = static_cast<double>(tp.tv_usec);
+        return (tp.tv_sec * 1000000.0) + mus;
+    }
+
+private:
+    double start;
+
+};
+
diff --git a/Helpers/Types.h b/Helpers/Types.h
new file mode 100644 (file)
index 0000000..22d885a
--- /dev/null
@@ -0,0 +1,67 @@
+#pragma once
+
+#include <limits>
+#include <vector>
+#include <string>
+
+#include "TaggedInteger.h"
+
+template<int TAG, typename VALUE_TYPE>
+using TaggedIntegerX = TaggedInteger<TAG, typename VALUE_TYPE::ValueType, VALUE_TYPE::InvalidValue, VALUE_TYPE::DefaultValue, VALUE_TYPE>;
+
+using Vertex = TaggedInteger<0, u_int32_t, -u_int32_t(1)>;
+constexpr Vertex noVertex(Vertex::InvalidValue);
+
+using Edge = TaggedInteger<1, u_int32_t, -u_int32_t(1)>;
+constexpr Edge noEdge(Edge::InvalidValue);
+
+using StopId = DependentTaggedInteger<2, Vertex>;
+constexpr StopId noStop(StopId::InvalidValue);
+
+using RouteId = TaggedInteger<3, u_int32_t, -u_int32_t(1)>;
+constexpr RouteId noRouteId(RouteId::InvalidValue);
+
+using TripId = TaggedInteger<4, u_int32_t, -u_int32_t(1)>;
+constexpr TripId noTripId(TripId::InvalidValue);
+
+using StopIndex = TaggedInteger<5, u_int32_t, -u_int32_t(1)>;
+constexpr StopIndex noStopIndex(StopIndex::InvalidValue);
+
+using ConnectionId = TaggedInteger<6, u_int32_t, -u_int32_t(1)>;
+constexpr ConnectionId noConnection(ConnectionId::InvalidValue);
+
+using HyperVertexId = TaggedInteger<7, u_int32_t, -u_int32_t(1)>;
+constexpr HyperVertexId noHyperVertex(HyperVertexId::InvalidValue);
+
+using StopEventId = TaggedInteger<8, u_int32_t, -u_int32_t(1)>;
+constexpr StopEventId noStopEvent(StopEventId::InvalidValue);
+
+using LinkId = TaggedInteger<9, u_int32_t, -u_int32_t(1)>;
+constexpr LinkId noLink(LinkId::InvalidValue);
+
+inline constexpr int intMax = std::numeric_limits<int>::max();
+inline constexpr double doubleMax = std::numeric_limits<double>::max();
+
+inline constexpr int EARTH_RADIUS_IN_CENTIMETRE = 637813700;
+inline constexpr double PI = 3.141592653589793;
+
+inline constexpr int INFTY = std::numeric_limits<int>::max() / 2;
+inline constexpr int never = INFTY;
+inline constexpr int DAY = 24 * 60 * 60;
+inline constexpr int VOID = -32768;
+
+inline constexpr int FORWARD = 0;
+inline constexpr int BACKWARD = 1;
+
+inline constexpr int DEPARTURE = 0;
+inline constexpr int ARRIVAL = 1;
+
+using PerceivedTime = double;
+inline constexpr PerceivedTime Unreachable = INFTY;
+
+struct NO_OPERATION {
+    template<typename... ARGS>
+    constexpr inline bool operator() (ARGS...) const noexcept {return false;}
+};
+
+NO_OPERATION NoOperation;
diff --git a/Helpers/Vector/Permutation.h b/Helpers/Vector/Permutation.h
new file mode 100644 (file)
index 0000000..68c87ec
--- /dev/null
@@ -0,0 +1,321 @@
+#pragma once
+
+#include <algorithm>
+#include <iostream>
+#include <vector>
+#include <string>
+#include <random>
+
+#include "Vector.h"
+
+#include "../Meta.h"
+#include "../Assert.h"
+#include "../ConstructorTags.h"
+#include "../IO/Serialization.h"
+
+/*
+ * Permutation = maps old IDs to new IDs
+ * Order       = maps new IDs to old IDs
+ */
+
+#ifdef _GLIBCXX_DEBUG
+namespace std {
+    inline void swap(std::vector<bool>::reference a, std::vector<bool>::reference b) noexcept {
+        const bool temp = a;
+        a = b;
+        b = temp;
+    }
+}
+#endif
+
+class IdMapping : public std::vector<size_t> {
+
+protected:
+    IdMapping(const size_t n = 0) : std::vector<size_t>(n) {}
+    IdMapping(const std::vector<size_t>& data) : std::vector<size_t>(data) {}
+    IdMapping(std::vector<size_t>&& data) : std::vector<size_t>(std::move(data)) {}
+    IdMapping(const std::string& fileName) {this->deserialize(fileName);}
+    IdMapping(IO::Deserialization& deserialize) {this->deserialize(deserialize);}
+
+    template<typename T, typename = std::enable_if_t<!Meta::Equals<T, size_t>()>>
+    IdMapping(const std::vector<T>& data) {
+        reserve(data.size());
+        for (const size_t i : data) {
+            emplace_back(i);
+        }
+    }
+
+    IdMapping(const Construct::IdTag, const size_t n) :
+        std::vector<size_t>() {
+        reserve(n);
+        for (size_t i = 0; i < n; i++) {
+            emplace_back(i);
+        }
+    }
+
+    IdMapping(const Construct::RandomTag, const size_t n, const int seed = 42) :
+        IdMapping(Construct::Id, n) {
+        std::mt19937 randomGenerator(seed);
+        std::uniform_int_distribution<> distribution(0, n - 1);
+        for (size_t i = 0; i < n; i++) {
+            std::swap((*this)[i], (*this)[distribution(randomGenerator)]);
+        }
+    }
+
+    template<typename T>
+    IdMapping(const Construct::SortTag, const std::vector<T>& vector) :
+        IdMapping(Construct::Id, vector.size()) {
+        std::sort(begin(), end(), [&](const size_t& a, const size_t& b){return vector[a] < vector[b];});
+    }
+
+    template<typename T, typename COMPARE>
+    IdMapping(const Construct::SortTag, const std::vector<T>& vector, const COMPARE& comp) :
+        IdMapping(Construct::Id, vector.size()) {
+        std::sort(begin(), end(), [&](const size_t& a, const size_t& b){return comp(vector[a], vector[b]);});
+    }
+
+    template<typename T>
+    IdMapping(const Construct::InvertTag, const T& original) :
+        std::vector<size_t>(original.size()) {
+        AssertMsg(original.isValid(), "The original is not valid!");
+        for (size_t i = 0; i < size(); i++) {
+            (*this)[original[i]] = i;
+        }
+    }
+
+    template<typename T>
+    IdMapping(const Construct::InvertTag, T&& original) :
+        std::vector<size_t>(std::move(original)) {
+        AssertMsg(isValid(), "The original is not valid!");
+        std::vector<bool> seen(size(), false);
+        for (size_t i = 0; i < size(); i++) {
+            if (seen[i]) continue;
+            size_t value = i;
+            size_t index = (*this)[value];
+            while (!seen[index]) {
+                seen[index] = true;
+                (*this)[i] = (*this)[index];
+                (*this)[index] = value;
+                value = index;
+                index = (*this)[i];
+            }
+        }
+    }
+
+    inline std::vector<size_t>& vector() noexcept {
+        return *this;
+    }
+
+    inline const std::vector<size_t>& vector() const noexcept {
+        return *this;
+    }
+
+public:
+    inline bool isValid() const noexcept {
+        std::vector<bool> seen(size(), false);
+        for (const size_t entry : (*this)) {
+            if (entry >= size()) return false;
+            if (seen[entry]) return false;
+            seen[entry] = true;
+        }
+        return true;
+    }
+
+    inline void serialize(IO::Serialization& serialize) const noexcept {
+        serialize(vector());
+    }
+
+    inline void deserialize(IO::Deserialization& deserialize) noexcept {
+        deserialize(vector());
+    }
+
+    inline void serialize(const std::string& fileName) const noexcept {
+        IO::serialize(fileName, vector());
+    }
+
+    inline void deserialize(const std::string& fileName) noexcept {
+        IO::deserialize(fileName, vector());
+    }
+
+};
+
+class Order;
+
+class Permutation : public IdMapping {
+
+public:
+    Permutation(const size_t n = 0) : IdMapping(n) {}
+    explicit Permutation(const std::vector<size_t>& data) : IdMapping(data) {}
+    explicit Permutation(std::vector<size_t>&& data) : IdMapping(std::move(data)) {}
+    Permutation(const std::string& fileName) {this->deserialize(fileName);}
+    Permutation(IO::Deserialization& deserialize) {this->deserialize(deserialize);}
+
+    template<typename T, typename = std::enable_if_t<!Meta::Equals<T, size_t>()>>
+    explicit Permutation(const std::vector<T>& data) : IdMapping(data) {}
+
+    Permutation(const Construct::IdTag, const size_t n) :
+        IdMapping(Construct::Id, n) {
+    }
+
+    Permutation(const Construct::RandomTag, const size_t n, const int seed = 42) :
+        IdMapping(Construct::Random, n, seed) {
+    }
+
+    Permutation(const Construct::InvertTag, const Order& order) :
+        IdMapping(Construct::Invert, order) {
+    }
+
+    Permutation(const Construct::InvertTag, Order&& order) :
+        IdMapping(Construct::Invert, std::move(order)) {
+    }
+
+public:
+    template<typename T>
+    inline std::vector<T> getPermuted(const std::vector<T>& vector) const noexcept {
+        AssertMsg(vector.size() == size(), "Cannot permute a vector of size " << vector.size() << " with a permutation of size " << size() << "!");
+        AssertMsg(isValid(), "The permutation is not valid!");
+        std::vector<T> result(size());
+        for (size_t i = 0; i < size(); i++) {
+            result[(*this)[i]] = vector[i];
+        }
+        return result;
+    }
+
+    template<typename T>
+    inline T permutate(const T element) const noexcept {
+        return T((*this)[element]);
+    }
+
+    template<typename T>
+    inline void permutate(std::vector<T>& vector) const noexcept {
+        std::vector<T> result = getPermuted(vector);
+        vector.swap(result);
+    }
+
+    template<typename T>
+    inline void mapPermutation(std::vector<T>& vector) const noexcept {
+        AssertMsg(isValid(), "The permutation is not valid!");
+        mapPermutationImplementation(vector);
+    }
+
+    inline Permutation splitAt(const size_t limit) const noexcept;
+
+    inline Permutation extend(const size_t newSize) const noexcept {
+        AssertMsg(newSize >= size(), "newSize is smaller than size!");
+        Permutation result = *this;
+        for (size_t i = size(); i < newSize; i++) {
+            result.emplace_back(i);
+        }
+        return result;
+    }
+
+protected:
+    template<typename T>
+    inline void mapPermutationImplementation(std::vector<T>& vector) const noexcept {
+        for (size_t i = 0; i < vector.size(); i++) {
+            const size_t element = vector[i];
+            if (element < size()) {
+                vector[i] = T((*this)[element]);
+            }
+        }
+    }
+
+    template<typename T>
+    inline void mapPermutationImplementation(std::vector<std::vector<T>>& vectors) const noexcept {
+        for (std::vector<T>& vector : vectors) {
+            mapPermutationImplementation(vector);
+        }
+    }
+
+};
+
+class Order : public IdMapping {
+
+public:
+    Order(const size_t n = 0) : IdMapping(n) {}
+    explicit Order(const std::vector<size_t>& data) : IdMapping(data) {}
+    explicit Order(std::vector<size_t>&& data) : IdMapping(std::move(data)) {}
+    Order(const std::string& fileName) {this->deserialize(fileName);}
+    Order(IO::Deserialization& deserialize) {this->deserialize(deserialize);}
+
+    template<typename T, typename = std::enable_if_t<!Meta::Equals<T, size_t>()>>
+    explicit Order(const std::vector<T>& data) : IdMapping(data) {}
+
+    Order(const Construct::IdTag, const size_t n) :
+        IdMapping(Construct::Id, n) {
+    }
+
+    Order(const Construct::RandomTag, const size_t n, const int seed = 42) :
+        IdMapping(Construct::Random, n, seed) {
+    }
+
+    template<typename T>
+    Order(const Construct::SortTag, const std::vector<T>& vector) :
+        IdMapping(Construct::Sort, vector) {
+    }
+
+    template<typename T, typename COMPARE>
+    Order(const Construct::SortTag, const std::vector<T>& vector, const COMPARE& comp) :
+        IdMapping(Construct::Sort, vector, comp) {
+    }
+
+    Order(const Construct::InvertTag, const Permutation& permutation) :
+        IdMapping(Construct::Invert, permutation) {
+    }
+
+    Order(const Construct::InvertTag, Permutation&& permutation) :
+        IdMapping(Construct::Invert, std::move(permutation)) {
+    }
+
+    Order(const Construct::FromTextFileTag, const std::string& fileName) {
+        std::ifstream file(fileName);
+        IO::checkStream(file, fileName);
+        std::string line;
+        while (std::getline(file, line)) {
+            emplace_back(String::lexicalCast<size_t>(line));
+        }
+    }
+
+public:
+    template<typename T>
+    inline std::vector<T> getOrdered(const std::vector<T>& vector) const noexcept {
+        std::vector<T> result;
+        result.reserve(size());
+        for (size_t i = 0; i < size(); i++) {
+            result.emplace_back(vector[(*this)[i]]);
+        }
+        return result;
+    }
+
+    template<typename T>
+    inline void order(std::vector<T>& vector) const noexcept {
+        std::vector<T> result = getOrdered(vector);
+        vector.swap(result);
+    }
+
+    inline Order splitAt(const size_t limit) const noexcept {
+        std::vector<size_t> left;
+        std::vector<size_t> right;
+        for (const size_t i : *this) {
+            if (i < limit) {
+                left.emplace_back(i);
+            } else {
+                right.emplace_back(i);
+            }
+        }
+        return Order(left + right);
+    }
+
+    inline Order extend(const size_t newSize) const noexcept {
+        AssertMsg(newSize >= size(), "newSize is smaller than size!");
+        Order result = *this;
+        for (size_t i = size(); i < newSize; i++) {
+            result.emplace_back(i);
+        }
+        return result;
+    }
+};
+
+inline Permutation Permutation::splitAt(const size_t limit) const noexcept {
+    return Permutation(Construct::Invert, Order(Construct::Invert, *this).splitAt(limit));
+}
diff --git a/Helpers/Vector/Vector.h b/Helpers/Vector/Vector.h
new file mode 100644 (file)
index 0000000..8e7bb35
--- /dev/null
@@ -0,0 +1,521 @@
+#pragma once
+
+#include <iostream>
+#include <algorithm>
+#include <vector>
+#include <string>
+#include <limits>
+
+#include "../Meta.h"
+#include "../Helpers.h"
+#include "../Assert.h"
+
+namespace Vector {
+
+    template<typename T>
+    inline std::vector<T> id(const size_t size) {
+        std::vector<T> result;
+        result.reserve(size);
+        while (result.size() < size) {
+            result.emplace_back(T(result.size()));
+        }
+        return result;
+    }
+
+    template<typename T>
+    inline size_t count(const std::vector<T>& container, const T& element) {
+        size_t c = 0;
+        for (size_t i = 0; i < container.size(); i++) {
+            if (container[i] == element) c++;
+        }
+        return c;
+    }
+
+    template<typename T, typename COUNT_IF, typename = decltype(std::declval<COUNT_IF>()(std::declval<T>()))>
+    inline size_t count(const std::vector<T>& container, const COUNT_IF& countIf) {
+        size_t c = 0;
+        for (size_t i = 0; i < container.size(); i++) {
+            if (countIf(container[i])) c++;
+        }
+        return c;
+    }
+
+    template<typename T>
+    inline size_t indexOf(const std::vector<T>& vec, const T& elem) {
+        for (size_t i = 0; i < vec.size(); i++) {
+            if (vec[i] == elem) return i;
+        }
+        return static_cast<size_t>(-1);
+    }
+
+    template<typename T>
+    inline std::vector<T> reverse(std::vector<T>&& a) {
+        std::vector<T> result(a);
+        std::reverse(result.begin(), result.end());
+        return result;
+    }
+
+    template<typename T>
+    inline std::vector<T>& reverse(std::vector<T>& a) {
+        std::reverse(a.begin(), a.end());
+        return a;
+    }
+
+    template<typename T>
+    inline T& remove(T& array, size_t index) {
+        Assert(index >= 0);
+        Assert(index < array.size());
+        array[index] = array.back();
+        array.pop_back();
+        return array;
+    }
+
+    template<typename T, typename FUNCTION>
+    inline void removeIf(std::vector<T>& container, const FUNCTION& condition) noexcept {
+        container.erase(std::remove_if(container.begin(), container.end(), condition), container.end());
+    }
+
+    template<typename T, typename FUNCTION>
+    inline void removeIf(std::vector<T>& container, const size_t beginIndex, const FUNCTION& condition) noexcept {
+        AssertMsg(beginIndex < container.size(), "Index " << beginIndex << " is out of bounds! (" << container.size() << ")");
+        container.erase(std::remove_if(container.begin() + beginIndex, container.end(), condition), container.end());
+    }
+
+    template<typename T, typename FUNCTION>
+    inline void removeIf(std::vector<T>& container, const size_t beginIndex, const size_t endIndex, const FUNCTION& condition) noexcept {
+        AssertMsg(beginIndex < container.size(), "Index " << beginIndex << " is out of bounds! (" << container.size() << ")");
+        AssertMsg(endIndex <= container.size(), "Index " << endIndex << " is out of bounds! (" << container.size() << ")");
+        container.erase(std::remove_if(container.begin() + beginIndex, container.begin() + endIndex, condition), container.begin() + endIndex);
+    }
+
+    template<typename T>
+    inline bool contains(const std::vector<T>& container, const T& element) {
+        for (size_t i = 0; i < container.size(); i++) {
+            if (container[i] == element) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    template<typename T, bool ALLOW_DUPLICATES = false>
+    inline void insertSorted(std::vector<T>& container, const T& element) {
+        size_t insertionIndex = container.size();
+        while ((insertionIndex > 0) && container[insertionIndex - 1] >= element) {
+            insertionIndex--;
+        }
+        if (insertionIndex == container.size()) {
+            container.emplace_back(element);
+        } else {
+            if constexpr (!ALLOW_DUPLICATES) {
+                if (container[insertionIndex] == element) return;
+            }
+            container.emplace_back(container.back());
+            for (size_t i = container.size() - 2; i > insertionIndex; i-- ) {
+                container[i] = container[i - 1];
+            }
+            container[insertionIndex] = element;
+        }
+    }
+
+    template<typename T>
+    inline bool equals(const std::vector<T>& a, const std::vector<T>& b) {
+        if (a.size() != b.size()) return false;
+        for (size_t i = 0; i < a.size(); i++) {
+            if (!(a[i] == b[i])) return false;
+        }
+        return true;
+    }
+
+    template<typename T>
+    inline bool equals(const std::vector<std::vector<T>>& a, const std::vector<std::vector<T>>& b) {
+        if (a.size() != b.size()) return false;
+        for (size_t i = 0; i < a.size(); i++) {
+            if (!equals(a[i], b[i])) return false;
+        }
+        return true;
+    }
+
+    template<typename T, typename LESS>
+    inline bool isSorted(const std::vector<T>& vec, const LESS& less) {
+        for (size_t i = 0; i + 1 < vec.size(); i++) {
+            if (less(vec[i + 1], vec[i])) return false;
+        }
+        return true;
+    }
+
+    template<typename T>
+    inline bool isSorted(const std::vector<T>& vec) {
+        return isSorted(vec, [](const T& a, const T& b){return a < b;});
+    }
+
+    template<typename T>
+    inline std::vector<T> sortedIntersection(const std::vector<T>& a, const std::vector<T>& b) noexcept {
+        std::vector<T> result;
+        AssertMsg(isSorted(a), "First vector is not sorted!");
+        AssertMsg(isSorted(b), "Second vector is not sorted!");
+        std::set_intersection(a.begin(), a.end(), b.begin(), b.end(), std::back_inserter(result));
+        return result;
+    }
+
+    template<typename T>
+    inline std::vector<T> sortedUnion(const std::vector<T>& a, const std::vector<T>& b) noexcept {
+        std::vector<T> result;
+        AssertMsg(isSorted(a), "First vector is not sorted!");
+        AssertMsg(isSorted(b), "Second vector is not sorted!");
+        std::set_union(a.begin(), a.end(), b.begin(), b.end(), std::back_inserter(result));
+        return result;
+    }
+
+    template<typename T, typename U, typename COMPARE>
+    inline size_t lowerBound(const std::vector<T>& vec, const U& val, const COMPARE& compare) noexcept {
+        AssertMsg(isSorted(vec), "Vector is not sorted!");
+        return std::lower_bound(vec.begin(), vec.end(), val, compare) - vec.begin();
+    }
+
+    template<typename T, typename U, typename COMPARE>
+    inline size_t upperBound(const std::vector<T>& vec, const U& val, const COMPARE& compare) noexcept {
+        AssertMsg(isSorted(vec), "Vector is not sorted!");
+        return std::upper_bound(vec.begin(), vec.end(), val, compare) - vec.begin();
+    }
+
+    template<typename T>
+    inline long long byteSize(const std::vector<T>& vec) {
+        return sizeof(T) * vec.size();
+    }
+
+    template<>
+    inline long long byteSize(const std::vector<bool>& vec) {
+        return vec.size();
+    }
+
+    template<typename T>
+    inline long long byteSize(const std::vector<std::vector<T>>& vec) {
+        long long size = 0;
+        for (const std::vector<T>& e : vec) size += sizeof(std::vector<T>) + byteSize(e);
+        return size;
+    }
+
+    template<typename T>
+    inline long long memoryUsageInBytes(const std::vector<T>& vec) {
+        return sizeof(std::vector<T>) + (sizeof(T) * vec.capacity());
+    }
+
+    template<>
+    inline long long memoryUsageInBytes(const std::vector<bool>& vec) {
+        return vec.capacity() /  8;
+    }
+
+    template<typename T>
+    inline long long memoryUsageInBytes(const std::vector<std::vector<T>>& vec) {
+        long long size = sizeof(std::vector<std::vector<T>>);
+        for (const std::vector<T>& e : vec) size += memoryUsageInBytes(e);
+        return size + ((vec.capacity() - vec.size()) * sizeof(std::vector<T>));
+    }
+
+    template<typename T, typename LESS>
+    inline T max(const std::vector<T>& vec, const LESS& less) {
+        Assert(!vec.empty());
+        T result = vec.front();
+        for (const T& element : vec) {
+            if (less(result, element)) result = element;
+        }
+        return result;
+    }
+
+    template<typename T, typename LESS>
+    inline T min(const std::vector<T>& vec, const LESS& less) {
+        Assert(!vec.empty());
+        T result = vec.front();
+        for (const T& element : vec) {
+            if (less(element, result)) result = element;
+        }
+        return result;
+    }
+
+    template<typename T>
+    inline T max(const std::vector<T>& vec) {
+        return max(vec, [](const T& a, const T& b){return a < b;});
+    }
+
+    template<typename T>
+    inline T min(const std::vector<T>& vec) {
+        return min(vec, [](const T& a, const T& b){return a < b;});
+    }
+
+    template<typename T, typename LESS>
+    inline std::array<T, 2> twoSmallestValues(const std::vector<T>& vec, const LESS& less) {
+        Assert(!vec.empty());
+        std::array<T, 2> result{std::numeric_limits<T>::max(), std::numeric_limits<T>::max()};
+        for (const T& element : vec) {
+            if (less(element, result[1])) {
+                if (less(element, result[0])) {
+                    result[1] = result[0];
+                    result[0] = element;
+                } else {
+                    result[1] = element;
+                }
+            }
+        }
+        return result;
+    }
+
+    template<typename T>
+    inline std::array<T, 2> twoSmallestValues(const std::vector<T>& vec) {
+        return twoSmallestValues(vec, [](const T& a, const T& b){return a < b;});
+    }
+
+    template<typename T>
+    inline T sum(const std::vector<T>& vec) {
+        T result = 0;
+        for (const T& element : vec) {
+            result += element;
+        }
+        return result;
+    }
+
+    template<typename T, typename EVALUATE>
+    inline T sum(const std::vector<T>& vec, const EVALUATE& evaluate) {
+        T result = 0;
+        for (const T& element : vec) {
+            result += evaluate(element);
+        }
+        return result;
+    }
+
+    template<typename T, typename U, typename EVALUATE>
+    inline T sum(const std::vector<U>& vec, const EVALUATE& evaluate) {
+        T result = 0;
+        for (const U& element : vec) {
+            result += evaluate(element);
+        }
+        return result;
+    }
+
+    template<typename T>
+    inline double mean(const std::vector<T>& vec) {
+        long double sum = 0;
+        for (const T& element : vec) {
+            sum += element;
+        }
+        return static_cast<double>(sum / vec.size());
+    }
+
+    template<typename T, typename EVALUATE>
+    inline double mean(const std::vector<T>& vec, const EVALUATE& evaluate) {
+        long double sum = 0;
+        for (const T& element : vec) {
+            sum += evaluate(element);
+        }
+        return static_cast<double>(sum / vec.size());
+    }
+
+    template<typename T, typename EVALUATE>
+    inline double percentile(const std::vector<T>& sortedData, const double p, const EVALUATE& evaluate) noexcept {
+        AssertMsg(!sortedData.empty(), "Percentile is not defined for empty data sets!");
+        AssertMsg(p >= 0, "Percentile cannot be negative!");
+        AssertMsg(p <= 1, "Percentile cannot be greater than one!");
+        if (sortedData.size() == 1) return evaluate(sortedData.front());
+        const double index = (sortedData.size() - 1) * p;
+        const size_t lowerIndex = index;
+        const size_t higherIndex = lowerIndex + 1;
+        if (higherIndex == sortedData.size()) return evaluate(sortedData.back());
+        const double lambda = higherIndex - index;
+        return (lambda * evaluate(sortedData[lowerIndex])) + ((1 - lambda) * evaluate(sortedData[higherIndex]));
+    }
+
+    template<typename T>
+    inline double percentile(const std::vector<T>& sortedData, const double p) noexcept {
+        return percentile(sortedData, p, [&](const T& element) {
+            return element;
+        });
+    }
+
+    template<typename T, typename EVALUATE>
+    inline double median(const std::vector<T>& sortedData, const EVALUATE& evaluate) noexcept {
+        return percentile(sortedData, 0.5, evaluate);
+    }
+
+    template<typename T>
+    inline double median(const std::vector<T>& sortedData) noexcept {
+        return percentile(sortedData, 0.5);
+    }
+
+    template<typename T>
+    inline void fill(std::vector<T>& vector, const T& value = T()) noexcept {
+        std::fill(vector.begin(), vector.end(), value);
+    }
+
+    template<typename T, typename U, typename = std::enable_if_t<!Meta::Equals<T, U>()>>
+    inline void assign(std::vector<T>& to, const std::vector<U>& from) noexcept {
+        to.clear();
+        to.reserve(from.size());
+        for (const U& u : from) {
+            to.push_back(u);
+        }
+    }
+
+    template<typename T, typename U>
+    inline void assign(std::vector<std::vector<T>>& to, const std::vector<std::vector<U>>& from) noexcept {
+        to.clear();
+        to.resize(from.size());
+        for (size_t i = 0; i < from.size(); i++) {
+            assign(to[i], from[i]);
+        }
+    }
+
+    template<typename T>
+    inline void assign(std::vector<std::vector<T>>& to, const std::vector<std::vector<T>>& from) noexcept {
+        to = from;
+    }
+
+    template<typename T>
+    inline void assign(std::vector<T>& to, const std::vector<T>& from) noexcept {
+        to = from;
+    }
+
+    template<typename T>
+    inline void assign(std::vector<T>& to, std::vector<T>&& from) noexcept {
+        to = std::move(from);
+    }
+
+    inline std::vector<uint8_t> packBool(const std::vector<bool>& vector) noexcept {
+        std::vector<uint8_t> result;
+        result.resize(std::ceil(vector.size() / 8.0), 0);
+        size_t vectorIndex = 0;
+        size_t resultIndex = 0;
+        while (vectorIndex + 7 < vector.size()) {
+            uint8_t& resultValue = result[resultIndex];
+            for (size_t i = 0; i < 8; i++) {
+                resultValue = (resultValue << 1) | static_cast<uint8_t>(vector[vectorIndex]);
+                vectorIndex++;
+            }
+            resultIndex++;
+        }
+        if (resultIndex == result.size()) return result;
+        uint8_t& resultValue = result[resultIndex];
+        for (size_t i = 0; i < 8; i++) {
+            resultValue = (resultValue << 1) | static_cast<uint8_t>((vectorIndex < vector.size()) ? (vector[vectorIndex]) : (false));
+            vectorIndex++;
+        }
+        return result;
+    }
+
+    inline constexpr uint8_t bitMask = (1 << 7);
+
+    inline std::vector<bool> unpackBool(const std::vector<uint8_t>& vector) noexcept {
+        std::vector<bool> result;
+        result.reserve(vector.size() * 8);
+        for (size_t i = 0; i < vector.size(); i++) {
+            uint8_t vectorValue = vector[i];
+            for (size_t j = 0; j < 8; j++) {
+                result.emplace_back(vectorValue & bitMask);
+                vectorValue = vectorValue << 1;
+            }
+        }
+        return result;
+    }
+
+    template<typename T, typename STREAM = std::ostream>
+    inline void printConcise(const std::vector<T>& vector, STREAM& out = std::cout, const std::string& separator = ", ") noexcept {
+        for (size_t i = 0; i < vector.size(); i++) {
+            out << vector[i];
+            if (i != vector.size() - 1) out << separator;
+        }
+    }
+
+    template<typename T, typename TO_STRING, typename STREAM = std::ostream>
+    inline void printConciseMapped(const std::vector<T>& vector, const TO_STRING& toString, STREAM& out = std::cout, const std::string& separator = ", ") noexcept {
+        for (size_t i = 0; i < vector.size(); i++) {
+            out << toString(vector[i]);
+            if (i != vector.size() - 1) out << separator;
+        }
+    }
+
+    inline double difference(const std::vector<bool>& firstVector, const std::vector<bool>& secondVector) noexcept {
+        AssertMsg(firstVector.size() == secondVector.size(), "Vectors have different sizes!");
+        if (firstVector.size() != secondVector.size()) return -1;
+        size_t common = 1;
+        size_t different = 0;
+        for (size_t i = 0; i < firstVector.size(); i++) {
+            if (firstVector[i] && secondVector[i]) {
+                common++;
+            } else if (firstVector[i] != secondVector[i]) {
+                different++;
+            }
+        }
+        return different / (double)common;
+    }
+
+    template<typename T>
+    inline std::vector<T> flatten(const std::vector<std::vector<T>>& vector) noexcept {
+        std::vector<T> result;
+        for (const std::vector<T>& e : vector) {
+            result += e;
+        }
+        return result;
+    }
+
+    template<typename T, typename F>
+    inline std::vector<decltype(std::declval<F>()(std::declval<T>()))> map(const std::vector<T>& vector, const F& function) noexcept {
+        std::vector<decltype(std::declval<F>()(std::declval<T>()))> result;
+        for (const T& e : vector) {
+            result.emplace_back(function(e));
+        }
+        return result;
+    }
+}
+
+template<typename T>
+inline std::vector<T> operator+(const T& a, const std::vector<T>& b) {
+    std::vector<T> result;
+    for (const T& t : b) {
+        result.emplace_back(a + t);
+    }
+    return result;
+}
+
+template<typename T>
+inline std::vector<T> operator+(std::vector<T>&& a, const std::vector<T>& b) {
+    std::vector<T> result;
+    result.swap(a);
+    result.reserve(result.size() + b.size());
+    result.insert(result.end(), b.begin(), b.end());
+    return result;
+}
+
+template<typename T>
+inline std::vector<T> operator+(const std::vector<T>& a, const std::vector<T>& b) {
+    std::vector<T> result;
+    result.reserve(a.size() + b.size());
+    result.insert(result.end(), a.begin(), a.end());
+    result.insert(result.end(), b.begin(), b.end());
+    return result;
+}
+
+template<typename T>
+inline std::vector<T>& operator+=(std::vector<T>& a, const std::vector<T>& b) {
+    a.reserve(a.size() + b.size());
+    a.insert(a.end(), b.begin(), b.end());
+    return a;
+}
+
+template<typename T>
+inline std::vector<T>& operator+=(std::vector<T>& a, std::vector<T>&& b) {
+    a.reserve(a.size() + b.size());
+    std::move(b.begin(), b.end(), std::back_inserter(a));
+    b.clear();
+    return a;
+}
+
+namespace std {
+
+    template<typename T>
+    inline std::ostream& operator<<(std::ostream& out, const std::vector<T>& a) {
+        for (size_t i = 0; i < a.size(); i++) {
+            out << a[i] << std::endl;
+        }
+        return out;
+    }
+
+}
diff --git a/MapServer/HTTPServer.h b/MapServer/HTTPServer.h
new file mode 100644 (file)
index 0000000..2ac514f
--- /dev/null
@@ -0,0 +1,172 @@
+#pragma once
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <vector>
+#include <map>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+
+#include "../Helpers/Assert.h"
+#include "../Helpers/String/String.h"
+#include "../Helpers/FileSystem/FileSystem.h"
+
+#include "Servlets/Servlet.h"
+
+#include "mongoose/mongoose.h"
+
+namespace MapServer {
+
+class HTTPServer {
+
+public:
+    HTTPServer(const bool asynchronousServletExecution = true) :
+        rootDirectory("."),
+        mgx(0),
+        runningServlet(-1),
+        asynchronousServletExecution(asynchronousServletExecution) {
+        server = this;
+        registerMimeType("txt", "text/plain");
+        registerMimeType("html", "text/html");
+        registerMimeType("png", "image/png");
+        registerMimeType("jpg", "image/jpeg");
+        registerMimeType("js", "application/x-javascript");
+        registerMimeType("css", "text/css");
+    }
+
+    inline void setRootDirectory(const std::string d) noexcept {
+        rootDirectory = d;
+        if (rootDirectory.empty()) rootDirectory = ".";
+        if (rootDirectory[rootDirectory.length()-1] == '/') rootDirectory = rootDirectory.substr(0, rootDirectory.length()-1);
+    }
+
+    inline void start(const int port, const bool verbose = true) noexcept {
+        std::string portString = String::lexicalCast<std::string>(port);
+        const char *options[] = {"listening_ports", portString.c_str(), nullptr};
+        if (verbose) std::cout << "Starting HTTP Server on port " << portString << ". Press any key to stop." << std::endl;
+        mgx = mg_start(&callback, nullptr, options);
+    }
+
+    inline bool stop(const bool verbose = true) noexcept {
+        if (runningServlet != -1) {
+            kill(runningServlet, SIGKILL);
+            return false;
+        }
+        if (verbose) std::cout << "Stopping HTTP Server... " << std::flush;
+        mg_stop(mgx);
+        if (verbose) std::cout << "OK." << std::endl;
+        return true;
+    }
+
+    inline void registerMimeType(const std::string extension, const std::string type) noexcept {
+        mimeTypes[extension] = type;
+    }
+
+    inline void registerServlet(Servlet* servlet) noexcept {
+        servlets.push_back(servlet);
+    }
+
+    inline void process(mg_connection *conn, const mg_request_info *requestInfo) noexcept {
+        std::cout << "\n\033[1;34m[" << requestInfo->remote_ip << "] Requesting " << requestInfo->uri << "\033[0m" << std::endl;
+        std::string uri(requestInfo->uri + 1);
+
+        for (size_t i = 0; i < servlets.size(); ++i) {
+            if (servlets[i]->Handler() == uri) {
+                if (asynchronousServletExecution) {
+                    waitpid(runningServlet, 0, 0);
+                    runningServlet = fork();
+                    if (runningServlet == -1) {
+                        const int errorNumber = errno;
+                        const std::string errorMsg(strerror(errorNumber));
+                        error("Failure during fork()!\n       errno is: ", errorNumber, " (", errorMsg, ")");
+                        servlets[i]->Process(conn, requestInfo);
+                        return;
+                    }
+                    if (runningServlet == 0) {
+                        servlets[i]->Process(conn, requestInfo);
+                        exit(0);
+                    }
+                    waitpid(runningServlet, 0, 0);
+                    runningServlet = -1;
+                    return;
+                } else {
+                    servlets[i]->Process(conn, requestInfo);
+                    return;
+                }
+            }
+        }
+
+        std::string filename = rootDirectory + "/" + (uri == "" ? IndexFilename : uri);
+        std::cout << "No servlet found, trying to serve " << filename << " as a file..." << std::endl;
+        if (!FileSystem::isFile(filename)) {
+            filename = "/" + uri;
+            std::cout << "File not found, trying to serve " << filename << " as a file..." << std::endl;
+        }
+        if (!FileSystem::isFile(filename)) {
+            std::cout << "File not found!" << std::endl;
+            serveError(conn, 404, "Not Found", "The file " + filename + " could not be found.");
+            return;
+        }
+
+        std::ifstream f(filename, std::ios::binary);
+        AssertMsg(f.is_open(), "The file " << filename << " could not be opened!");
+
+        std::string extension(getFileExtension(filename));
+        if (mimeTypes.find(extension) == mimeTypes.end()) extension = "txt";
+
+        std::cout << "Serving with mime-type " << mimeTypes[extension] << "..." << std::endl;
+        mg_printf(conn, "HTTP/1.1 200 OK\r\n");
+        mg_printf(conn, "Content-Type: %s\r\n\r\n", mimeTypes[extension].c_str());
+        while (!f.eof()) {
+            char buffer[1024];
+            f.read(buffer, sizeof(buffer));
+            mg_write(conn, buffer, f.gcount());
+        }
+    }
+
+protected:
+    inline static void* callback(mg_event event, mg_connection *conn, const mg_request_info *requestInfo) noexcept {
+        Assert(server != nullptr);
+        if (event == MG_NEW_REQUEST) {
+            server->process(conn, requestInfo);
+        }
+        return (void*)"";
+    }
+
+    inline static void serveError(mg_connection *conn, const int errorCode, const std::string errorString, const std::string errorMessage) noexcept {
+        mg_printf(conn, "HTTP/1.1 %d %s\r\nContent-Type: text/plain\r\n\r\n%s", errorCode, errorString.c_str(), errorMessage.c_str());
+    }
+
+    inline std::string getFileExtension(const std::string filename) const noexcept {
+        if(filename.find_last_of(".") != std::string::npos) return filename.substr(filename.find_last_of(".")+1);
+        return "";
+    }
+
+protected:
+    static HTTPServer *server;
+    static const std::string IndexFilename;
+    std::string rootDirectory;
+
+    mg_context *mgx;
+
+    std::vector<Servlet*> servlets;
+
+    std::map<std::string, std::string> mimeTypes;
+
+    pid_t runningServlet;
+
+    const bool asynchronousServletExecution;
+
+};
+
+HTTPServer* HTTPServer::server = NULL;
+const std::string HTTPServer::IndexFilename = "index.html";
+
+}
diff --git a/MapServer/HalloWorldMapServer.cpp b/MapServer/HalloWorldMapServer.cpp
new file mode 100644 (file)
index 0000000..65207bc
--- /dev/null
@@ -0,0 +1,89 @@
+#include <iostream>
+#include <vector>
+#include <string>
+
+#include "HTTPServer.h"
+
+#include "Servlets/AlgorithmsAndParameterInformationServlet.h"
+#include "Servlets/BoundingBoxServlet.h"
+#include "Servlets/JSONServlet.h"
+#include "Servlets/FileServlet.h"
+#include "Servlets/QueryServlet.h"
+
+#include "../DataStructures/Result.h"
+#include "../DataStructures/Parameter.h"
+#include "../DataStructures/Geometry/CoordinateTree.h"
+#include "../DataStructures/Geometry/Point.h"
+
+#include "../Helpers/Console/CommandLineParser.h"
+
+#include "../Algorithms/MapServer/HelloWorld.h"
+
+using CoordinateTreeType = CoordinateTree<Geometry::GeoMetric>;
+
+void usage(char *program) {
+    std::cout << "Usage: " << program << " <options>" << std::endl
+              << std::endl
+              << "-d   <directory> -- root directory to be served" << std::endl
+              << "-p   <integer>   -- port number of server (Default: 8080)" << std::endl
+              << "-h               -- print help" << std::endl
+              << std::endl
+              << "(Try " << program << " -d ../Sites/MapServer)" << std::endl
+              << std::endl;
+    exit(0);
+}
+
+int main(int argc, char **argv) {
+    checkAsserts();
+
+    CommandLineParser clp(argc, argv);
+    if (clp.isSet("h")) usage(argv[0]);
+
+    const int port(clp.value<int>("p", 8080));
+    const std::string directory(clp.value<std::string>("d", ""));
+    if (directory == "") usage(argv[0]);
+
+    // Use "server(true)" in release mode and "server(false)" for debugging!
+    MapServer::HTTPServer server(true);
+    server.setRootDirectory(directory);
+
+    // Load a list of coordinates
+    // Clicking on the map will select the nearest neighbor from this vector
+    std::vector<Geometry::Point> coordinates;
+    coordinates.push_back(Geometry::Point(Construct::LatLong, 49.013747, 8.420266));
+    coordinates.push_back(Geometry::Point(Construct::LatLong, 49.012540, 8.415863));
+    coordinates.push_back(Geometry::Point(Construct::LatLong, 49.011860, 8.416898));
+    coordinates.push_back(Geometry::Point(Construct::LatLong, 49.013932, 8.404513));
+    coordinates.push_back(Geometry::Point(Construct::LatLong, 49.006878, 8.403641));
+    coordinates.push_back(Geometry::Point(Construct::LatLong, 49.009232, 8.403902));
+    coordinates.push_back(Geometry::Point(Construct::LatLong, 49.015442, 8.425663));
+    coordinates.push_back(Geometry::Point(Construct::LatLong, 49.009774, 8.412496));
+    CoordinateTreeType ct(coordinates);
+
+    BoundingBoxServlet bbs(coordinates);
+    server.registerServlet(&bbs);
+
+    AlgorithmsAndParameterInformationServlet ais;
+    server.registerServlet(&ais);
+
+    JSONServlet js;
+    ais.registerAlgorithm(js.Handler(), "JSON Texteingabe", js.parameters);
+    server.registerServlet(&js);
+
+    // Add an algorithm to the server in 4 steps
+    // 1. Create an instance of a class that inherits from class Algorithm in Algorithms/MapServer/Algorithm.h (here HelloWorld).
+    // 2. Create a QueryServlet serving your algorithm (First parameter (here "halloWorldServlet") is used via HTTP GET to call the algorithm).
+    // 3. Register the QueryServlet with the AlgorithmsAndParameterInformationServlet so that the algorithm can be selected within the browser (Second parameter (here "Hello World!") is displayed to the user as name of the algorithm).
+    // 4. Register the QueryServlet with the Server so that the server can invoke execution of the Algorithm if requested.
+    HelloWorld halloWorld;
+    QueryServlet<CoordinateTreeType> halloWorldServlet("halloWorldServlet", halloWorld, coordinates, ct);
+    ais.registerAlgorithm(halloWorldServlet, "Hello World!");
+    server.registerServlet(&halloWorldServlet);
+
+    server.start(port);
+    do {
+        getchar();
+    } while(!server.stop());
+
+    return 0;
+}
diff --git a/MapServer/Makefile b/MapServer/Makefile
new file mode 100644 (file)
index 0000000..7dd8b2f
--- /dev/null
@@ -0,0 +1,10 @@
+CC=g++
+FLAGS=-O3 -std=c++17
+DEBUG=-rdynamic -Werror -Wpedantic -pedantic-errors -Wall -Wextra -Wparentheses -Wno-sign-compare -Wfatal-errors
+
+all: HalloWorldMapServer
+
+HalloWorldMapServer:
+       $(CC) $(FLAGS) -pthread -o mongoose.so -c mongoose/mongoose.c
+       $(CC) $(FLAGS) $(DEBUG) -o HalloWorldMapServer HalloWorldMapServer.cpp -pthread mongoose.so -ldl
+       rm mongoose.so
diff --git a/MapServer/QueryStringParser.h b/MapServer/QueryStringParser.h
new file mode 100644 (file)
index 0000000..3007e15
--- /dev/null
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <string>
+#include <cstring>
+
+#include "mongoose/mongoose.h"
+
+#include "../Helpers/String/String.h"
+
+namespace MapServer {
+
+class QueryStringParser {
+
+public:
+    QueryStringParser(const mg_request_info *ri) : requestInfo(ri) {}
+
+    template<typename T>
+    inline T getValue(const std::string key, const T defaultValue = T()) {
+        int length = mg_get_var(requestInfo->query_string, strlen(requestInfo->query_string), key.c_str(), valueBuffer, sizeof(valueBuffer));
+        if (length == -1) return defaultValue;
+        std::string value(valueBuffer);
+        return String::lexicalCast<T>(value);
+    }
+
+private:
+    const mg_request_info *requestInfo;
+    char valueBuffer[1024];
+
+};
+
+}
diff --git a/MapServer/Responses/Response.h b/MapServer/Responses/Response.h
new file mode 100644 (file)
index 0000000..d573f2b
--- /dev/null
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <string>
+
+#include "../mongoose/mongoose.h"
+
+namespace MapServer {
+
+class Response {
+
+public:
+    Response(const std::string mt = "text/plain", const std::string contents = "") : mimeType(mt), buffer(contents) {}
+    virtual ~Response() {}
+
+    virtual void Write(mg_connection *conn) {
+        mg_printf(conn, "HTTP/1.1 200 OK\r\n");
+        mg_printf(conn, "Content-Type: %s\r\n", mimeType.c_str());
+        mg_printf(conn, "Content-Length: %d\r\n\r\n", buffer.length());
+        mg_write(conn, buffer.c_str(), buffer.length());
+    }
+
+    virtual void SetContents(const std::string &contents) {
+        buffer = contents;
+    }
+
+protected:
+    const std::string mimeType;
+    std::string buffer;
+
+};
+
+}
diff --git a/MapServer/Responses/ResponseJSON.h b/MapServer/Responses/ResponseJSON.h
new file mode 100644 (file)
index 0000000..b9c4c63
--- /dev/null
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <string>
+
+#include "Response.h"
+
+namespace MapServer {
+
+class ResponseJSON : public Response {
+
+public:
+    ResponseJSON(const std::string contents) : Response("application/json", contents) {}
+    virtual ~ResponseJSON() {}
+
+};
+
+}
diff --git a/MapServer/Responses/ResponseText.h b/MapServer/Responses/ResponseText.h
new file mode 100644 (file)
index 0000000..99de442
--- /dev/null
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <string>
+
+#include "Response.h"
+
+namespace MapServer {
+
+class ResponseText : public Response {
+
+public:
+    ResponseText(const std::string contents) : Response("text/plain", contents) {}
+
+};
+
+}
diff --git a/MapServer/Servlets/AlgorithmsAndParameterInformationServlet.h b/MapServer/Servlets/AlgorithmsAndParameterInformationServlet.h
new file mode 100644 (file)
index 0000000..ed4de66
--- /dev/null
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <string>
+#include <sstream>
+#include <map>
+
+#include "Servlet.h"
+#include "QueryServlet.h"
+#include "AlgorithmsInformationServlet.h"
+
+#include "../Responses/ResponseJSON.h"
+
+#include "../../DataStructures/Parameter.h"
+
+class AlgorithmsAndParameterInformationServlet : public MapServer::AlgorithmsInformationServlet {
+public:
+
+    AlgorithmsAndParameterInformationServlet() : AlgorithmsInformationServlet() {}
+
+    virtual ~AlgorithmsAndParameterInformationServlet() {}
+
+    virtual void Process(mg_connection* conn, const mg_request_info*) {
+        std::cout << "Dumping information about algorithms... " << std::flush;
+
+        std::stringstream json;
+        json << "{" << std::endl
+             << "\"NumberOfAlgorithms\":" << algorithms.size() << "," << std::endl
+             << "\"Algorithms\": [" << std::endl;
+        for (std::map<std::string, std::string>::iterator iterator = algorithms.begin(); iterator != algorithms.end(); iterator++) {
+            if (iterator != algorithms.begin()) json << ", ";
+            std::map<std::string, const std::map<std::string, Parameter>*>::iterator parameters = algorithmsParameters.find(iterator->first);
+            json << "{\"Key\":\"" << iterator->first << "\", \"Name\":\"" << iterator->second << "\"";
+            if (parameters != algorithmsParameters.end()) {
+                json << ", \"Parameter\":[";
+                for (std::map<std::string, Parameter>::const_iterator parameter = parameters->second->begin(); parameter != parameters->second->end(); parameter++) {
+                    if (parameter != parameters->second->begin()) json << ", ";
+                    parameter->second.writeParameter(json);
+                }
+                json << "]";
+            }
+            json << "}";
+        }
+        json << "]}" << std::endl;
+
+        std::cout << "done (" << json.str().length() << " bytes)" << std::endl;
+
+        MapServer::ResponseJSON(json.str()).Write(conn);
+    }
+
+    template<typename ALGORITHM_TYPE>
+    inline void registerAlgorithm(const ALGORITHM_TYPE& hls, const std::string name) {
+        registerAlgorithm(hls.Handler(), name, hls.getParameters());
+    }
+
+    inline void registerAlgorithm(const std::string& key, const std::string& name) {
+        algorithms[key] = name;
+    }
+
+    inline void registerAlgorithm(const std::string& key, const std::string& name, const std::map<std::string, Parameter>& parameters) {
+        algorithms[key] = name;
+        algorithmsParameters[key] = &parameters;
+    }
+
+protected:
+
+    std::map<std::string, const std::map<std::string, Parameter>*> algorithmsParameters;
+
+};
diff --git a/MapServer/Servlets/AlgorithmsInformationServlet.h b/MapServer/Servlets/AlgorithmsInformationServlet.h
new file mode 100644 (file)
index 0000000..afc969b
--- /dev/null
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <string>
+#include <sstream>
+#include <map>
+
+#include "Servlet.h"
+#include "../Responses/ResponseJSON.h"
+
+namespace MapServer {
+
+class AlgorithmsInformationServlet : public Servlet {
+
+public:
+    AlgorithmsInformationServlet() : Servlet("algorithms_information") {}
+    virtual ~AlgorithmsInformationServlet() {}
+
+    virtual void Process(mg_connection* conn, const mg_request_info*) {
+        std::cout << "Dumping information about algorithms... " << std::flush;
+
+        std::stringstream json;
+        json << "{" << std::endl
+             << "\"NumberOfAlgorithms\":" << algorithms.size() << "," << std::endl
+             << "\"Algorithms\": [" << std::endl;
+        for (auto iterator = algorithms.begin(); iterator != algorithms.end(); ++iterator) {
+            if (iterator != algorithms.begin())
+                json << ", ";
+            json << "{\"Key\":\"" << iterator->first << "\", \"Name\":\"" << iterator->second << "\"}";
+        }
+        json << "]}" << std::endl;
+
+        std::cout << "done (" << json.str().length() << " bytes)" << std::endl;
+        ResponseJSON(json.str()).Write(conn);
+    }
+
+    inline void registerAlgorithm(const std::string key, const std::string name) {
+        algorithms[key] = name;
+    }
+
+protected:
+    std::map<std::string, std::string> algorithms;
+
+};
+
+}
diff --git a/MapServer/Servlets/BoundingBoxServlet.h b/MapServer/Servlets/BoundingBoxServlet.h
new file mode 100644 (file)
index 0000000..bcf142f
--- /dev/null
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <vector>
+
+#include "Servlet.h"
+
+#include "../Responses/ResponseJSON.h"
+
+#include "../../DataStructures/Geometry/Point.h"
+#include "../../DataStructures/Geometry/Rectangle.h"
+
+class BoundingBoxServlet : public MapServer::Servlet {
+
+public:
+    BoundingBoxServlet(const Geometry::Rectangle& boundingBox) :
+        Servlet("bounding_box"),
+        boundingBox(boundingBox) {
+    }
+    BoundingBoxServlet(const std::vector<Geometry::Point>& coordinates) :
+        Servlet("bounding_box"),
+        boundingBox(Geometry::Rectangle::BoundingBox(coordinates)) {
+    }
+
+    virtual ~BoundingBoxServlet() {}
+
+    virtual void Process(mg_connection* conn, const mg_request_info*) {
+        std::stringstream json;
+        json << "{" << std::endl
+             << "\"Min\": { \"Latitude\":" << boundingBox.min.latitude << ", \"Longitude\":" << boundingBox.min.longitude << "}, " << std::endl
+             << "\"Max\": { \"Latitude\":" << boundingBox.max.latitude << ", \"Longitude\":" << boundingBox.max.longitude << "}" << std::endl
+             << "}" << std::endl;
+
+        MapServer::ResponseJSON response(json.str());
+        response.Write(conn);
+    }
+
+private:
+    Geometry::Rectangle boundingBox;
+
+};
diff --git a/MapServer/Servlets/FileServlet.h b/MapServer/Servlets/FileServlet.h
new file mode 100644 (file)
index 0000000..e41b31d
--- /dev/null
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <iostream>
+
+#include "Servlet.h"
+
+#include "../Responses/ResponseJSON.h"
+#include "../QueryStringParser.h"
+#include "..//mongoose/mongoose.h"
+
+#include "../../DataStructures/Geometry/Point.h"
+
+class FileServlet : public MapServer::Servlet {
+
+public:
+
+    FileServlet(std::string handler = "fileservlet") : Servlet(handler) {
+        parameters["filename"] = Parameter("string", "filename", "JSON Filename", "", true);
+    }
+
+    virtual ~FileServlet() {}
+
+    virtual void Process(mg_connection *conn, const mg_request_info *requestInfo) {
+        MapServer::QueryStringParser qsp(requestInfo);
+        std::string filename = qsp.getValue<std::string>("filename", "");
+        if (filename.find("file://") == 0) filename = filename.substr(7, filename.size() - 7);
+        std::cout << "Requesting file: " << filename << std::endl;
+        std::string json("");
+        if (filename != "") {
+            std::ifstream in(filename);
+            if (in) {
+                std::cout << "File found!" << std::endl;
+                in.seekg(0, std::ios::end);
+                json.resize(in.tellg());
+                in.seekg(0, std::ios::beg);
+                in.read(&json[0], json.size());
+                in.close();
+            }
+        } else {
+            json = "{}";
+        }
+        std::cout << "Result:" << std::endl;
+        if (json.size() <= 200) {
+            std::cout << json << std::endl;
+        } else {
+            std::cout << json.substr(0, 199) << "... (" << std::to_string(json.size() - 200) << " more characters)" << std::endl;
+        }
+        MapServer::ResponseJSON jsonresponse(json);
+        jsonresponse.Write(conn);
+        std::cout << "The calculation has been completed." << std::endl << std::endl;
+    }
+
+    std::map<std::string, Parameter> parameters;
+
+};
diff --git a/MapServer/Servlets/JSONServlet.h b/MapServer/Servlets/JSONServlet.h
new file mode 100644 (file)
index 0000000..06201d5
--- /dev/null
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <iostream>
+
+#include "Servlet.h"
+
+#include "../Responses/ResponseJSON.h"
+#include "../QueryStringParser.h"
+#include "../mongoose/mongoose.h"
+
+#include "../../DataStructures/Geometry/Point.h"
+
+class JSONServlet : public MapServer::Servlet {
+
+public:
+
+    JSONServlet(std::string handler = "jsonservlet") : Servlet(handler) {
+        parameters["input"] = Parameter("string", "input", "JSON String", "", true);
+    }
+
+    virtual ~JSONServlet() {}
+
+    virtual void Process(mg_connection *conn, const mg_request_info *requestInfo) {
+        int maxLength = strlen(requestInfo->query_string);
+        char* valueBuffer = new char[maxLength];
+        int length = mg_get_var(requestInfo->query_string, maxLength, "input", valueBuffer, maxLength);
+        if (length <= 0) {
+            std::cout << "no result" << std::endl;
+            MapServer::ResponseJSON jsonresponse("{}");
+            jsonresponse.Write(conn);
+        } else {
+            std::cout << "returned " << length << " chars." << std::endl;
+            std::string json(valueBuffer);
+            MapServer::ResponseJSON jsonresponse(json);
+            jsonresponse.Write(conn);
+        }
+        delete[] valueBuffer;
+        std::cout << "The calculation has been completed." << std::endl << std::endl;
+    }
+
+    std::map<std::string, Parameter> parameters;
+
+};
diff --git a/MapServer/Servlets/QueryServlet.h b/MapServer/Servlets/QueryServlet.h
new file mode 100644 (file)
index 0000000..0bbcb8f
--- /dev/null
@@ -0,0 +1,98 @@
+#pragma once
+
+#include <iostream>
+#include <vector>
+#include <string>
+
+#include "Servlet.h"
+
+#include "../QueryStringParser.h"
+#include "../Responses/ResponseJSON.h"
+
+#include "../../Helpers/Types.h"
+#include "../../Helpers/Helpers.h"
+#include "../../Helpers/JSONString.h"
+#include "../../Helpers/Function.h"
+
+#include "../../Algorithms/MapServer/Algorithm.h"
+
+#include "../../DataStructures/Geometry/Point.h"
+#include "../../DataStructures/Parameter.h"
+#include "../../DataStructures/Result.h"
+
+template<typename COORDINATE_TREE_TYPE>
+class QueryServlet : public MapServer::Servlet {
+
+public:
+
+    QueryServlet(std::string handler, Algorithm& algorithm, const std::vector<Geometry::Point>& coordinates, const COORDINATE_TREE_TYPE& cs) :
+        Servlet(handler),
+        coordinates(coordinates),
+        cs(cs),
+        algorithm(algorithm) {
+    }
+
+    virtual ~QueryServlet() {}
+
+    virtual void Process(mg_connection* conn, const mg_request_info* requestInfo) {
+        MapServer::QueryStringParser qsp(requestInfo);
+        readParameters(qsp);
+        const Vertex sourceNode = getSourceNodeId(qsp);
+        const Vertex targetNode = getTargetNodeId(qsp);
+        std::cout << "   Source = " << sourceNode << std::endl;
+        std::cout << "   Target = " << targetNode  << std::endl;
+        if (targetNode == noVertex) {
+            sendResponse(conn, algorithm.runSourceOnly(sourceNode));
+        } else if (sourceNode == noVertex) {
+            sendResponse(conn, algorithm.runTargetOnly(targetNode));
+        } else {
+            sendResponse(conn, algorithm.run(sourceNode, targetNode));
+        }
+        std::cout << "The calculation has been completed." << std::endl << std::endl << std::flush;
+    }
+
+    inline const std::map<std::string, Parameter>& getParameters() const {
+        return algorithm.getParameters();
+    }
+
+protected:
+    Vertex getSourceNodeId(MapServer::QueryStringParser& qsp) {
+        double lat = qsp.getValue<double>("SourceLat", 1000.0);
+        double lon = qsp.getValue<double>("SourceLon", 1000.0);
+        return getNodeId(Geometry::Point(Construct::LatLong, lat, lon));
+    }
+
+    Vertex getTargetNodeId(MapServer::QueryStringParser& qsp) {
+        double lat = qsp.getValue<double>("TargetLat", 1000.0);
+        double lon = qsp.getValue<double>("TargetLon", 1000.0);
+        return getNodeId(Geometry::Point(Construct::LatLong, lat, lon));
+    }
+
+    virtual Vertex getNodeId(const Geometry::Point& pos) {
+        if (pos.latitude == 1000.0 || pos.longitude == 1000.0) return noVertex;
+        return cs.getNearestNeighbor(pos);
+    }
+
+    virtual void readParameters(MapServer::QueryStringParser& qsp) {
+        for (std::map<std::string, Parameter>::iterator iterator = algorithm.getParameters().begin(); iterator != algorithm.getParameters().end(); iterator++) {
+            iterator->second.value = qsp.getValue<std::string>(iterator->second.name, iterator->second.defaultValue);
+            std::cout << "   Parameter(" << iterator->second.name << ") = " << iterator->second.value << std::endl;
+        }
+    }
+
+    virtual void sendResponse(mg_connection* conn, std::vector<Result>&& response) {
+        if (!response.empty()) {
+            for (std::map<std::string, Parameter>::iterator iterator = algorithm.getParameters().begin(); iterator != algorithm.getParameters().end(); iterator++) {
+                response.front().parameter.push_back(std::make_pair(iterator->second.name, iterator->second.value));
+            }
+        }
+        MapServer::ResponseJSON jsonresponse(resultsToJSON(response, coordinates));
+        jsonresponse.Write(conn);
+    }
+
+private:
+    const std::vector<Geometry::Point>& coordinates;
+    const COORDINATE_TREE_TYPE& cs;
+    Algorithm& algorithm;
+
+};
diff --git a/MapServer/Servlets/QueryServletWithVertexInput.h b/MapServer/Servlets/QueryServletWithVertexInput.h
new file mode 100644 (file)
index 0000000..39baa21
--- /dev/null
@@ -0,0 +1,117 @@
+#pragma once
+
+#include <iostream>
+#include <vector>
+#include <string>
+
+#include "Servlet.h"
+
+#include "../QueryStringParser.h"
+#include "../Responses/ResponseJSON.h"
+
+#include "../../Helpers/Types.h"
+#include "../../Helpers/Helpers.h"
+#include "../../Helpers/JSONString.h"
+#include "../../Helpers/Function.h"
+
+#include "../../Algorithms/MapServer/Algorithm.h"
+
+#include "../../DataStructures/Geometry/Point.h"
+#include "../../DataStructures/Parameter.h"
+#include "../../DataStructures/Result.h"
+
+template<typename COORDINATE_TREE_TYPE>
+class QueryServletWithVertexInput : public MapServer::Servlet {
+
+public:
+
+    QueryServletWithVertexInput(std::string handler, Algorithm& algorithm, const std::vector<Geometry::Point>& coordinates, const COORDINATE_TREE_TYPE& cs) :
+        Servlet(handler),
+        coordinates(coordinates),
+        cs(cs),
+        algorithm(algorithm),
+        parameters(algorithm.getParameters()) {
+        parameters["overwriteVertices"] = Parameter("bool", "overwriteVertices", "Use manual vertex IDs", "false", false, 1000);
+        parameters["sourceId"] = Parameter("size_t", "sourceId", "Source Id", "-1", true, 1001);
+        parameters["targetId"] = Parameter("size_t", "targetId", "Target Id", "-1", true, 1002);
+    }
+
+    virtual ~QueryServletWithVertexInput() {}
+
+    virtual void Process(mg_connection* conn, const mg_request_info* requestInfo) {
+        MapServer::QueryStringParser qsp(requestInfo);
+        readParameters(qsp);
+        const Vertex sourceNode = getSourceNodeId(qsp);
+        const Vertex targetNode = getTargetNodeId(qsp);
+        std::cout << "   Source = " << sourceNode << std::endl;
+        std::cout << "   Target = " << targetNode  << std::endl;
+        if (targetNode == noVertex) {
+            sendResponse(conn, algorithm.runSourceOnly(sourceNode), sourceNode, targetNode);
+        } else if (sourceNode == noVertex) {
+            sendResponse(conn, algorithm.runTargetOnly(targetNode), sourceNode, targetNode);
+        } else {
+            sendResponse(conn, algorithm.run(sourceNode, targetNode), sourceNode, targetNode);
+        }
+        std::cout << "The calculation has been completed." << std::endl << std::endl << std::flush;
+    }
+
+    inline const std::map<std::string, Parameter>& getParameters() const {
+        return parameters;
+    }
+
+protected:
+    Vertex getSourceNodeId(MapServer::QueryStringParser& qsp) {
+        if (parameters.find("overwriteVertices")->second.template getValue<bool>()) {
+            return Vertex(parameters.find("sourceId")->second.template getValue<size_t>());
+        } else {
+            double lat = qsp.getValue<double>("SourceLat", 1000.0);
+            double lon = qsp.getValue<double>("SourceLon", 1000.0);
+            return getNodeId(Geometry::Point(Construct::LatLong, lat, lon));
+        }
+    }
+
+    Vertex getTargetNodeId(MapServer::QueryStringParser& qsp) {
+        if (parameters.find("overwriteVertices")->second.template getValue<bool>()) {
+            return Vertex(parameters.find("targetId")->second.template getValue<size_t>());
+        } else {
+            double lat = qsp.getValue<double>("TargetLat", 1000.0);
+            double lon = qsp.getValue<double>("TargetLon", 1000.0);
+            return getNodeId(Geometry::Point(Construct::LatLong, lat, lon));
+        }
+    }
+
+    virtual Vertex getNodeId(Geometry::Point pos) {
+        if (pos.latitude == 1000.0 || pos.longitude == 1000.0) return noVertex;
+        return cs.getNearestNeighbor(pos);
+    }
+
+    virtual void readParameters(MapServer::QueryStringParser& qsp) {
+        for (std::map<std::string, Parameter>::iterator iterator = algorithm.getParameters().begin(); iterator != algorithm.getParameters().end(); iterator++) {
+            iterator->second.value = qsp.getValue<std::string>(iterator->second.name, iterator->second.defaultValue);
+        }
+        for (std::map<std::string, Parameter>::iterator iterator = parameters.begin(); iterator != parameters.end(); iterator++) {
+            iterator->second.value = qsp.getValue<std::string>(iterator->second.name, iterator->second.defaultValue);
+            std::cout << "   Parameter(" << iterator->second.name << ") = " << iterator->second.value << std::endl;
+        }
+    }
+
+    virtual void sendResponse(mg_connection* conn, std::vector<Result>&& response, Vertex sourceId = noVertex, Vertex targetId = noVertex) {
+        if (!response.empty()) {
+            for (std::map<std::string, Parameter>::iterator iterator = algorithm.getParameters().begin(); iterator != algorithm.getParameters().end(); iterator++) {
+                response.front().parameter.push_back(std::make_pair(iterator->second.name, iterator->second.value));
+            }
+            response.front().parameter.push_back(std::make_pair("sourceId", std::to_string(sourceId)));
+            response.front().parameter.push_back(std::make_pair("targetId", std::to_string(targetId)));
+        }
+        MapServer::ResponseJSON jsonresponse(resultsToJSON(response, coordinates));
+        jsonresponse.Write(conn);
+    }
+
+private:
+    const std::vector<Geometry::Point>& coordinates;
+    const COORDINATE_TREE_TYPE& cs;
+    Algorithm& algorithm;
+
+    std::map<std::string, Parameter> parameters;
+
+};
diff --git a/MapServer/Servlets/Servlet.h b/MapServer/Servlets/Servlet.h
new file mode 100644 (file)
index 0000000..3157c68
--- /dev/null
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <string>
+
+#include "../mongoose/mongoose.h"
+
+namespace MapServer {
+
+class Servlet {
+
+public:
+    Servlet(const std::string h) : handler(h) {}
+    virtual ~Servlet() {}
+
+    virtual void Process(mg_connection *conn, const mg_request_info *requestInfo) = 0;
+
+    virtual std::string Handler() const {return handler; }
+
+protected:
+    std::string handler;
+
+};
+
+}
diff --git a/MapServer/mongoose/mongoose.c b/MapServer/mongoose/mongoose.c
new file mode 100644 (file)
index 0000000..e248e26
--- /dev/null
@@ -0,0 +1,4141 @@
+// Copyright (c) 2004-2010 Sergey Lyubka\r
+//\r
+// Permission is hereby granted, free of charge, to any person obtaining a copy\r
+// of this software and associated documentation files (the "Software"), to deal\r
+// in the Software without restriction, including without limitation the rights\r
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+// copies of the Software, and to permit persons to whom the Software is\r
+// furnished to do so, subject to the following conditions:\r
+//\r
+// The above copyright notice and this permission notice shall be included in\r
+// all copies or substantial portions of the Software.\r
+//\r
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
+// THE SOFTWARE.\r
+\r
+#if defined(_WIN32)\r
+#define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005\r
+#else\r
+#define _XOPEN_SOURCE 600 // For flockfile() on Linux\r
+#define _LARGEFILE_SOURCE // Enable 64-bit file offsets\r
+#define __STDC_FORMAT_MACROS // <inttypes.h> wants this for C++\r
+#endif\r
+\r
+#if defined(__SYMBIAN32__)\r
+#define NO_SSL // SSL is not supported\r
+#define NO_CGI // CGI is not supported\r
+#define PATH_MAX FILENAME_MAX\r
+#endif // __SYMBIAN32__\r
+\r
+#ifndef _WIN32_WCE // Some ANSI #includes are not available on Windows CE\r
+#include <sys/types.h>\r
+#include <sys/stat.h>\r
+#include <errno.h>\r
+#include <signal.h>\r
+#include <fcntl.h>\r
+#endif // !_WIN32_WCE\r
+\r
+#include <time.h>\r
+#include <stdlib.h>\r
+#include <stdarg.h>\r
+#include <assert.h>\r
+#include <string.h>\r
+#include <ctype.h>\r
+#include <limits.h>\r
+#include <stddef.h>\r
+#include <stdio.h>\r
+\r
+#if defined(_WIN32) && !defined(__SYMBIAN32__) // Windows specific\r
+#define _WIN32_WINNT 0x0400 // To make it link in VS2005\r
+#include <windows.h>\r
+\r
+#ifndef PATH_MAX\r
+#define PATH_MAX MAX_PATH\r
+#endif\r
+\r
+#ifndef _WIN32_WCE\r
+#include <process.h>\r
+#include <direct.h>\r
+#include <io.h>\r
+#else // _WIN32_WCE\r
+#include <winsock2.h>\r
+#define NO_CGI // WinCE has no pipes\r
+\r
+typedef long off_t;\r
+#define BUFSIZ  4096\r
+\r
+#define errno   GetLastError()\r
+#define strerror(x)  _ultoa(x, (char *) _alloca(sizeof(x) *3 ), 10)\r
+#endif // _WIN32_WCE\r
+\r
+#define MAKEUQUAD(lo, hi) ((uint64_t)(((uint32_t)(lo)) | \\r
+      ((uint64_t)((uint32_t)(hi))) << 32))\r
+#define RATE_DIFF 10000000 // 100 nsecs\r
+#define EPOCH_DIFF MAKEUQUAD(0xd53e8000, 0x019db1de)\r
+#define SYS2UNIX_TIME(lo, hi) \\r
+  (time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF)\r
+\r
+// Visual Studio 6 does not know __func__ or __FUNCTION__\r
+// The rest of MS compilers use __FUNCTION__, not C99 __func__\r
+// Also use _strtoui64 on modern M$ compilers\r
+#if defined(_MSC_VER) && _MSC_VER < 1300\r
+#define STRX(x) #x\r
+#define STR(x) STRX(x)\r
+#define __func__ "line " STR(__LINE__)\r
+#define strtoull(x, y, z) strtoul(x, y, z)\r
+#define strtoll(x, y, z) strtol(x, y, z)\r
+#else\r
+#define __func__  __FUNCTION__\r
+#define strtoull(x, y, z) _strtoui64(x, y, z)\r
+#define strtoll(x, y, z) _strtoi64(x, y, z)\r
+#endif // _MSC_VER\r
+\r
+#define ERRNO   GetLastError()\r
+#define NO_SOCKLEN_T\r
+#define SSL_LIB   "ssleay32.dll"\r
+#define CRYPTO_LIB  "libeay32.dll"\r
+#define DIRSEP '\\'\r
+#define IS_DIRSEP_CHAR(c) ((c) == '/' || (c) == '\\')\r
+#define O_NONBLOCK  0\r
+#if !defined(EWOULDBLOCK)\r
+#define EWOULDBLOCK  WSAEWOULDBLOCK\r
+#endif // !EWOULDBLOCK\r
+#define _POSIX_\r
+#define INT64_FMT  "I64d"\r
+\r
+#define WINCDECL __cdecl\r
+#define SHUT_WR 1\r
+#define snprintf _snprintf\r
+#define vsnprintf _vsnprintf\r
+#define sleep(x) Sleep((x) * 1000)\r
+\r
+#define pipe(x) _pipe(x, BUFSIZ, _O_BINARY)\r
+#define popen(x, y) _popen(x, y)\r
+#define pclose(x) _pclose(x)\r
+#define close(x) _close(x)\r
+#define dlsym(x,y) GetProcAddress((HINSTANCE) (x), (y))\r
+#define RTLD_LAZY  0\r
+#define fseeko(x, y, z) fseek((x), (y), (z))\r
+#define fdopen(x, y) _fdopen((x), (y))\r
+#define write(x, y, z) _write((x), (y), (unsigned) z)\r
+#define read(x, y, z) _read((x), (y), (unsigned) z)\r
+#define flockfile(x) (void) 0\r
+#define funlockfile(x) (void) 0\r
+\r
+#if !defined(fileno)\r
+#define fileno(x) _fileno(x)\r
+#endif // !fileno MINGW #defines fileno\r
+\r
+typedef HANDLE pthread_mutex_t;\r
+typedef struct {HANDLE signal, broadcast;} pthread_cond_t;\r
+typedef DWORD pthread_t;\r
+#define pid_t HANDLE // MINGW typedefs pid_t to int. Using #define here.\r
+\r
+struct timespec {\r
+  long tv_nsec;\r
+  long tv_sec;\r
+};\r
+\r
+static int pthread_mutex_lock(pthread_mutex_t *);\r
+static int pthread_mutex_unlock(pthread_mutex_t *);\r
+static FILE *mg_fopen(const char *path, const char *mode);\r
+\r
+#if defined(HAVE_STDINT)\r
+#include <stdint.h>\r
+#else\r
+typedef unsigned int  uint32_t;\r
+typedef unsigned short  uint16_t;\r
+typedef unsigned __int64 uint64_t;\r
+typedef __int64   int64_t;\r
+#define INT64_MAX  9223372036854775807\r
+#endif // HAVE_STDINT\r
+\r
+// POSIX dirent interface\r
+struct dirent {\r
+  char d_name[PATH_MAX];\r
+};\r
+\r
+typedef struct DIR {\r
+  HANDLE   handle;\r
+  WIN32_FIND_DATAW info;\r
+  struct dirent  result;\r
+} DIR;\r
+\r
+#else    // UNIX  specific\r
+#include <sys/wait.h>\r
+#include <sys/socket.h>\r
+#include <sys/select.h>\r
+#include <netinet/in.h>\r
+#include <arpa/inet.h>\r
+#include <sys/time.h>\r
+#include <stdint.h>\r
+#include <inttypes.h>\r
+#include <netdb.h>\r
+\r
+#include <pwd.h>\r
+#include <unistd.h>\r
+#include <dirent.h>\r
+#if !defined(NO_SSL_DL) && !defined(NO_SSL)\r
+#include <dlfcn.h>\r
+#endif\r
+#include <pthread.h>\r
+#if defined(__MACH__)\r
+#define SSL_LIB   "libssl.dylib"\r
+#define CRYPTO_LIB  "libcrypto.dylib"\r
+#else\r
+#if !defined(SSL_LIB)\r
+#define SSL_LIB   "libssl.so"\r
+#endif\r
+#if !defined(CRYPTO_LIB)\r
+#define CRYPTO_LIB  "libcrypto.so"\r
+#endif\r
+#endif\r
+#define DIRSEP   '/'\r
+#define IS_DIRSEP_CHAR(c) ((c) == '/')\r
+#ifndef O_BINARY\r
+#define O_BINARY  0\r
+#endif // O_BINARY\r
+#define closesocket(a) close(a)\r
+#define mg_fopen(x, y) fopen(x, y)\r
+#define mg_mkdir(x, y) mkdir(x, y)\r
+#define mg_remove(x) remove(x)\r
+#define mg_rename(x, y) rename(x, y)\r
+#define ERRNO errno\r
+#define INVALID_SOCKET (-1)\r
+#define INT64_FMT PRId64\r
+typedef int SOCKET;\r
+#define WINCDECL\r
+\r
+#endif // End of Windows and UNIX specific includes\r
+\r
+#include "mongoose.h"\r
+\r
+#define MONGOOSE_VERSION "3.0"\r
+#define PASSWORDS_FILE_NAME ".htpasswd"\r
+#define CGI_ENVIRONMENT_SIZE 4096\r
+#define MAX_CGI_ENVIR_VARS 64\r
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))\r
+\r
+#if defined(DEBUG)\r
+#define DEBUG_TRACE(x) do { \\r
+  flockfile(stdout); \\r
+  printf("*** %lu.%p.%s.%d: ", \\r
+         (unsigned long) time(NULL), (void *) pthread_self(), \\r
+         __func__, __LINE__); \\r
+  printf x; \\r
+  putchar('\n'); \\r
+  fflush(stdout); \\r
+  funlockfile(stdout); \\r
+} while (0)\r
+#else\r
+#define DEBUG_TRACE(x)\r
+#endif // DEBUG\r
+\r
+// Darwin prior to 7.0 and Win32 do not have socklen_t\r
+#ifdef NO_SOCKLEN_T\r
+typedef int socklen_t;\r
+#endif // NO_SOCKLEN_T\r
+\r
+typedef void * (*mg_thread_func_t)(void *);\r
+\r
+static const char *http_500_error = "Internal Server Error";\r
+\r
+// Snatched from OpenSSL includes. I put the prototypes here to be independent\r
+// from the OpenSSL source installation. Having this, mongoose + SSL can be\r
+// built on any system with binary SSL libraries installed.\r
+typedef struct ssl_st SSL;\r
+typedef struct ssl_method_st SSL_METHOD;\r
+typedef struct ssl_ctx_st SSL_CTX;\r
+\r
+#define SSL_ERROR_WANT_READ 2\r
+#define SSL_ERROR_WANT_WRITE 3\r
+#define SSL_FILETYPE_PEM 1\r
+#define CRYPTO_LOCK  1\r
+\r
+#if defined(NO_SSL_DL)\r
+extern void SSL_free(SSL *);\r
+extern int SSL_accept(SSL *);\r
+extern int SSL_connect(SSL *);\r
+extern int SSL_read(SSL *, void *, int);\r
+extern int SSL_write(SSL *, const void *, int);\r
+extern int SSL_get_error(const SSL *, int);\r
+extern int SSL_set_fd(SSL *, int);\r
+extern SSL *SSL_new(SSL_CTX *);\r
+extern SSL_CTX *SSL_CTX_new(SSL_METHOD *);\r
+extern SSL_METHOD *SSLv23_server_method(void);\r
+extern int SSL_library_init(void);\r
+extern void SSL_load_error_strings(void);\r
+extern int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int);\r
+extern int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int);\r
+extern int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *);\r
+extern void SSL_CTX_set_default_passwd_cb(SSL_CTX *, mg_callback_t);\r
+extern void SSL_CTX_free(SSL_CTX *);\r
+extern unsigned long ERR_get_error(void);\r
+extern char *ERR_error_string(unsigned long, char *);\r
+extern int CRYPTO_num_locks(void);\r
+extern void CRYPTO_set_locking_callback(void (*)(int, int, const char *, int));\r
+extern void CRYPTO_set_id_callback(unsigned long (*)(void));\r
+#else\r
+// Dynamically loaded SSL functionality\r
+struct ssl_func {\r
+  const char *name;   // SSL function name\r
+  void  (*ptr)(void); // Function pointer\r
+};\r
+\r
+#define SSL_free (* (void (*)(SSL *)) ssl_sw[0].ptr)\r
+#define SSL_accept (* (int (*)(SSL *)) ssl_sw[1].ptr)\r
+#define SSL_connect (* (int (*)(SSL *)) ssl_sw[2].ptr)\r
+#define SSL_read (* (int (*)(SSL *, void *, int)) ssl_sw[3].ptr)\r
+#define SSL_write (* (int (*)(SSL *, const void *,int)) ssl_sw[4].ptr)\r
+#define SSL_get_error (* (int (*)(SSL *, int)) ssl_sw[5].ptr)\r
+#define SSL_set_fd (* (int (*)(SSL *, SOCKET)) ssl_sw[6].ptr)\r
+#define SSL_new (* (SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr)\r
+#define SSL_CTX_new (* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr)\r
+#define SSLv23_server_method (* (SSL_METHOD * (*)(void)) ssl_sw[9].ptr)\r
+#define SSL_library_init (* (int (*)(void)) ssl_sw[10].ptr)\r
+#define SSL_CTX_use_PrivateKey_file (* (int (*)(SSL_CTX *, \\r
+        const char *, int)) ssl_sw[11].ptr)\r
+#define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \\r
+        const char *, int)) ssl_sw[12].ptr)\r
+#define SSL_CTX_set_default_passwd_cb \\r
+  (* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[13].ptr)\r
+#define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr)\r
+#define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr)\r
+#define SSL_CTX_use_certificate_chain_file \\r
+  (* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr)\r
+\r
+#define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr)\r
+#define CRYPTO_set_locking_callback \\r
+  (* (void (*)(void (*)(int, int, const char *, int))) crypto_sw[1].ptr)\r
+#define CRYPTO_set_id_callback \\r
+  (* (void (*)(unsigned long (*)(void))) crypto_sw[2].ptr)\r
+#define ERR_get_error (* (unsigned long (*)(void)) crypto_sw[3].ptr)\r
+#define ERR_error_string (* (char * (*)(unsigned long,char *)) crypto_sw[4].ptr)\r
+\r
+// set_ssl_option() function updates this array.\r
+// It loads SSL library dynamically and changes NULLs to the actual addresses\r
+// of respective functions. The macros above (like SSL_connect()) are really\r
+// just calling these functions indirectly via the pointer.\r
+static struct ssl_func ssl_sw[] = {\r
+  {"SSL_free",   NULL},\r
+  {"SSL_accept",   NULL},\r
+  {"SSL_connect",   NULL},\r
+  {"SSL_read",   NULL},\r
+  {"SSL_write",   NULL},\r
+  {"SSL_get_error",  NULL},\r
+  {"SSL_set_fd",   NULL},\r
+  {"SSL_new",   NULL},\r
+  {"SSL_CTX_new",   NULL},\r
+  {"SSLv23_server_method", NULL},\r
+  {"SSL_library_init",  NULL},\r
+  {"SSL_CTX_use_PrivateKey_file", NULL},\r
+  {"SSL_CTX_use_certificate_file",NULL},\r
+  {"SSL_CTX_set_default_passwd_cb",NULL},\r
+  {"SSL_CTX_free",  NULL},\r
+  {"SSL_load_error_strings", NULL},\r
+  {"SSL_CTX_use_certificate_chain_file", NULL},\r
+  {NULL,    NULL}\r
+};\r
+\r
+// Similar array as ssl_sw. These functions could be located in different lib.\r
+static struct ssl_func crypto_sw[] = {\r
+  {"CRYPTO_num_locks",  NULL},\r
+  {"CRYPTO_set_locking_callback", NULL},\r
+  {"CRYPTO_set_id_callback", NULL},\r
+  {"ERR_get_error",  NULL},\r
+  {"ERR_error_string", NULL},\r
+  {NULL,    NULL}\r
+};\r
+#endif // NO_SSL_DL\r
+\r
+\r
+#ifndef INT64_MAX\r
+#define INT64_MAX  9223372036854775807\r
+#endif\r
+\r
+static const char *month_names[] = {\r
+  "Jan", "Feb", "Mar", "Apr", "May", "Jun",\r
+  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"\r
+};\r
+\r
+// Unified socket address. For IPv6 support, add IPv6 address structure\r
+// in the union u.\r
+struct usa {\r
+  socklen_t len;\r
+  union {\r
+    struct sockaddr sa;\r
+    struct sockaddr_in sin;\r
+  } u;\r
+};\r
+\r
+// Describes a string (chunk of memory).\r
+struct vec {\r
+  const char *ptr;\r
+  size_t len;\r
+};\r
+\r
+// Structure used by mg_stat() function. Uses 64 bit file length.\r
+struct mgstat {\r
+  int is_directory;  // Directory marker\r
+  int64_t size;      // File size\r
+  time_t mtime;      // Modification time\r
+};\r
+\r
+// Describes listening socket, or socket which was accept()-ed by the master\r
+// thread and queued for future handling by the worker thread.\r
+struct socket {\r
+  struct socket *next;  // Linkage\r
+  SOCKET sock;          // Listening socket\r
+  struct usa lsa;       // Local socket address\r
+  struct usa rsa;       // Remote socket address\r
+  int is_ssl;           // Is socket SSL-ed\r
+  int is_proxy;\r
+};\r
+\r
+enum {\r
+  CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER,\r
+  PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, ACCESS_LOG_FILE,\r
+  SSL_CHAIN_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,\r
+  GLOBAL_PASSWORDS_FILE, INDEX_FILES,\r
+  ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST, MAX_REQUEST_SIZE,\r
+  EXTRA_MIME_TYPES, LISTENING_PORTS,\r
+  DOCUMENT_ROOT, SSL_CERTIFICATE, NUM_THREADS, RUN_AS_USER,\r
+  NUM_OPTIONS\r
+};\r
+\r
+static const char *config_options[] = {\r
+  "C", "cgi_extensions", ".cgi,.pl,.php",\r
+  "E", "cgi_environment", NULL,\r
+  "G", "put_delete_passwords_file", NULL,\r
+  "I", "cgi_interpreter", NULL,\r
+  "P", "protect_uri", NULL,\r
+  "R", "authentication_domain", "mydomain.com",\r
+  "S", "ssi_extensions", ".shtml,.shtm",\r
+  "a", "access_log_file", NULL,\r
+  "c", "ssl_chain_file", NULL,\r
+  "d", "enable_directory_listing", "yes",\r
+  "e", "error_log_file", NULL,\r
+  "g", "global_passwords_file", NULL,\r
+  "i", "index_files", "index.html,index.htm,index.cgi",\r
+  "k", "enable_keep_alive", "no",\r
+  "l", "access_control_list", NULL,\r
+  "M", "max_request_size", "16384",\r
+  "m", "extra_mime_types", NULL,\r
+  "p", "listening_ports", "8080",\r
+  "r", "document_root",  ".",\r
+  "s", "ssl_certificate", NULL,\r
+  "t", "num_threads", "10",\r
+  "u", "run_as_user", NULL,\r
+  NULL\r
+};\r
+#define ENTRIES_PER_CONFIG_OPTION 3\r
+\r
+struct mg_context {\r
+  volatile int stop_flag;       // Should we stop event loop\r
+  SSL_CTX *ssl_ctx;             // SSL context\r
+  char *config[NUM_OPTIONS];    // Mongoose configuration parameters\r
+  mg_callback_t user_callback;  // User-defined callback function\r
+  void *user_data;              // User-defined data\r
+\r
+  struct socket *listening_sockets;\r
+\r
+  volatile int num_threads;  // Number of threads\r
+  pthread_mutex_t mutex;     // Protects (max|num)_threads\r
+  pthread_cond_t  cond;      // Condvar for tracking workers terminations\r
+\r
+  struct socket queue[20];   // Accepted sockets\r
+  volatile int sq_head;      // Head of the socket queue\r
+  volatile int sq_tail;      // Tail of the socket queue\r
+  pthread_cond_t sq_full;    // Singaled when socket is produced\r
+  pthread_cond_t sq_empty;   // Signaled when socket is consumed\r
+};\r
+\r
+struct mg_connection {\r
+  struct mg_connection *peer; // Remote target in proxy mode\r
+  struct mg_request_info request_info;\r
+  struct mg_context *ctx;\r
+  SSL *ssl;                   // SSL descriptor\r
+  struct socket client;       // Connected client\r
+  time_t birth_time;          // Time connection was accepted\r
+  int64_t num_bytes_sent;     // Total bytes sent to client\r
+  int64_t content_len;        // Content-Length header value\r
+  int64_t consumed_content;   // How many bytes of content is already read\r
+  char *buf;                  // Buffer for received data\r
+  int buf_size;               // Buffer size\r
+  int request_len;            // Size of the request + headers in a buffer\r
+  int data_len;               // Total size of data in a buffer\r
+};\r
+\r
+const char **mg_get_valid_option_names(void) {\r
+  return config_options;\r
+}\r
+\r
+static void *call_user(struct mg_connection *conn, enum mg_event event) {\r
+  conn->request_info.user_data = conn->ctx->user_data;\r
+  return conn->ctx->user_callback == NULL ? NULL :\r
+    conn->ctx->user_callback(event, conn, &conn->request_info);\r
+}\r
+\r
+static int get_option_index(const char *name) {\r
+  int i;\r
+\r
+  for (i = 0; config_options[i] != NULL; i += ENTRIES_PER_CONFIG_OPTION) {\r
+    if (strcmp(config_options[i], name) == 0 ||\r
+        strcmp(config_options[i + 1], name) == 0) {\r
+      return i / ENTRIES_PER_CONFIG_OPTION;\r
+    }\r
+  }\r
+  return -1;\r
+}\r
+\r
+const char *mg_get_option(const struct mg_context *ctx, const char *name) {\r
+  int i;\r
+  if ((i = get_option_index(name)) == -1) {\r
+    return NULL;\r
+  } else if (ctx->config[i] == NULL) {\r
+    return "";\r
+  } else {\r
+    return ctx->config[i];\r
+  }\r
+}\r
+\r
+// Print error message to the opened error log stream.\r
+static void cry(struct mg_connection *conn, const char *fmt, ...) {\r
+  char buf[BUFSIZ];\r
+  va_list ap;\r
+  FILE *fp;\r
+  time_t timestamp;\r
+\r
+  va_start(ap, fmt);\r
+  (void) vsnprintf(buf, sizeof(buf), fmt, ap);\r
+  va_end(ap);\r
+\r
+  // Do not lock when getting the callback value, here and below.\r
+  // I suppose this is fine, since function cannot disappear in the\r
+  // same way string option can.\r
+  conn->request_info.log_message = buf;\r
+  if (call_user(conn, MG_EVENT_LOG) == NULL) {\r
+    fp = conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :\r
+      mg_fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");\r
+\r
+    if (fp != NULL) {\r
+      flockfile(fp);\r
+      timestamp = time(NULL);\r
+\r
+      (void) fprintf(fp,\r
+          "[%010lu] [error] [client %s] ",\r
+          (unsigned long) timestamp,\r
+          inet_ntoa(conn->client.rsa.u.sin.sin_addr));\r
+\r
+      if (conn->request_info.request_method != NULL) {\r
+        (void) fprintf(fp, "%s %s: ",\r
+            conn->request_info.request_method,\r
+            conn->request_info.uri);\r
+      }\r
+\r
+      (void) fprintf(fp, "%s", buf);\r
+      fputc('\n', fp);\r
+      funlockfile(fp);\r
+      if (fp != stderr) {\r
+        fclose(fp);\r
+      }\r
+    }\r
+  }\r
+  conn->request_info.log_message = NULL;\r
+}\r
+\r
+// Return OpenSSL error message\r
+static const char *ssl_error(void) {\r
+  unsigned long err;\r
+  err = ERR_get_error();\r
+  return err == 0 ? "" : ERR_error_string(err, NULL);\r
+}\r
+\r
+// Return fake connection structure. Used for logging, if connection\r
+// is not applicable at the moment of logging.\r
+static struct mg_connection *fc(struct mg_context *ctx) {\r
+  static struct mg_connection fake_connection;\r
+  fake_connection.ctx = ctx;\r
+  return &fake_connection;\r
+}\r
+\r
+const char *mg_version(void) {\r
+  return MONGOOSE_VERSION;\r
+}\r
+\r
+static void mg_strlcpy(register char *dst, register const char *src, size_t n) {\r
+  for (; *src != '\0' && n > 1; n--) {\r
+    *dst++ = *src++;\r
+  }\r
+  *dst = '\0';\r
+}\r
+\r
+static int lowercase(const char *s) {\r
+  return tolower(* (const unsigned char *) s);\r
+}\r
+\r
+static int mg_strncasecmp(const char *s1, const char *s2, size_t len) {\r
+  int diff = 0;\r
+\r
+  if (len > 0)\r
+    do {\r
+      diff = lowercase(s1++) - lowercase(s2++);\r
+    } while (diff == 0 && s1[-1] != '\0' && --len > 0);\r
+\r
+  return diff;\r
+}\r
+\r
+static int mg_strcasecmp(const char *s1, const char *s2) {\r
+  int diff;\r
+\r
+  do {\r
+    diff = lowercase(s1++) - lowercase(s2++);\r
+  } while (diff == 0 && s1[-1] != '\0');\r
+\r
+  return diff;\r
+}\r
+\r
+static char * mg_strndup(const char *ptr, size_t len) {\r
+  char *p;\r
+\r
+  if ((p = (char *) malloc(len + 1)) != NULL) {\r
+    mg_strlcpy(p, ptr, len + 1);\r
+  }\r
+\r
+  return p;\r
+}\r
+\r
+static char * mg_strdup(const char *str) {\r
+  return mg_strndup(str, strlen(str));\r
+}\r
+\r
+// Like snprintf(), but never returns negative value, or the value\r
+// that is larger than a supplied buffer.\r
+// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability\r
+// in his audit report.\r
+static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen,\r
+                        const char *fmt, va_list ap) {\r
+  int n;\r
+\r
+  if (buflen == 0)\r
+    return 0;\r
+\r
+  n = vsnprintf(buf, buflen, fmt, ap);\r
+\r
+  if (n < 0) {\r
+    cry(conn, "vsnprintf error");\r
+    n = 0;\r
+  } else if (n >= (int) buflen) {\r
+    cry(conn, "truncating vsnprintf buffer: [%.*s]",\r
+        n > 200 ? 200 : n, buf);\r
+    n = (int) buflen - 1;\r
+  }\r
+  buf[n] = '\0';\r
+\r
+  return n;\r
+}\r
+\r
+static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen,\r
+                       const char *fmt, ...) {\r
+  va_list ap;\r
+  int n;\r
+\r
+  va_start(ap, fmt);\r
+  n = mg_vsnprintf(conn, buf, buflen, fmt, ap);\r
+  va_end(ap);\r
+\r
+  return n;\r
+}\r
+\r
+// Skip the characters until one of the delimiters characters found.\r
+// 0-terminate resulting word. Skip the delimiter and following whitespaces if any.\r
+// Advance pointer to buffer to the next word. Return found 0-terminated word.\r
+// Delimiters can be quoted with quotechar.\r
+static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace, char quotechar) {\r
+  char *p, *begin_word, *end_word, *end_whitespace;\r
+\r
+  begin_word = *buf;\r
+  end_word = begin_word + strcspn(begin_word, delimiters);\r
+\r
+  /* Check for quotechar */\r
+  if (end_word > begin_word) {\r
+    p = end_word - 1;\r
+    while (*p == quotechar) {\r
+      /* If there is anything beyond end_word, copy it */\r
+      if (*end_word == '\0') {\r
+        *p = '\0';\r
+        break;\r
+      } else {\r
+        size_t end_off = strcspn(end_word + 1, delimiters);\r
+        memmove (p, end_word, end_off + 1);\r
+        p += end_off; /* p must correspond to end_word - 1 */\r
+        end_word += end_off + 1;\r
+      }\r
+    }\r
+    for (p++; p < end_word; p++) {\r
+      *p = '\0';\r
+    }\r
+  }\r
+\r
+  if (*end_word == '\0') {\r
+    *buf = end_word;\r
+  } else {\r
+    end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace);\r
+\r
+    for (p = end_word; p < end_whitespace; p++) {\r
+      *p = '\0';\r
+    }\r
+\r
+    *buf = end_whitespace;\r
+  }\r
+\r
+  return begin_word;\r
+}\r
+\r
+// Simplified version of skip_quoted without quote char\r
+// and whitespace == delimiters\r
+static char *skip(char **buf, const char *delimiters) {\r
+  return skip_quoted(buf, delimiters, delimiters, 0);\r
+}\r
+\r
+\r
+// Return HTTP header value, or NULL if not found.\r
+static const char *get_header(const struct mg_request_info *ri,\r
+                              const char *name) {\r
+  int i;\r
+\r
+  for (i = 0; i < ri->num_headers; i++)\r
+    if (!mg_strcasecmp(name, ri->http_headers[i].name))\r
+      return ri->http_headers[i].value;\r
+\r
+  return NULL;\r
+}\r
+\r
+const char *mg_get_header(const struct mg_connection *conn, const char *name) {\r
+  return get_header(&conn->request_info, name);\r
+}\r
+\r
+// A helper function for traversing comma separated list of values.\r
+// It returns a list pointer shifted to the next value, of NULL if the end\r
+// of the list found.\r
+// Value is stored in val vector. If value has form "x=y", then eq_val\r
+// vector is initialized to point to the "y" part, and val vector length\r
+// is adjusted to point only to "x".\r
+static const char *next_option(const char *list, struct vec *val,\r
+                               struct vec *eq_val) {\r
+  if (list == NULL || *list == '\0') {\r
+    /* End of the list */\r
+    list = NULL;\r
+  } else {\r
+    val->ptr = list;\r
+    if ((list = strchr(val->ptr, ',')) != NULL) {\r
+      /* Comma found. Store length and shift the list ptr */\r
+      val->len = list - val->ptr;\r
+      list++;\r
+    } else {\r
+      /* This value is the last one */\r
+      list = val->ptr + strlen(val->ptr);\r
+      val->len = list - val->ptr;\r
+    }\r
+\r
+    if (eq_val != NULL) {\r
+      /*\r
+       * Value has form "x=y", adjust pointers and lengths\r
+       * so that val points to "x", and eq_val points to "y".\r
+       */\r
+      eq_val->len = 0;\r
+      eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len);\r
+      if (eq_val->ptr != NULL) {\r
+        eq_val->ptr++;  /* Skip over '=' character */\r
+        eq_val->len = val->ptr + val->len - eq_val->ptr;\r
+        val->len = (eq_val->ptr - val->ptr) - 1;\r
+      }\r
+    }\r
+  }\r
+\r
+  return list;\r
+}\r
+\r
+#if !defined(NO_CGI)\r
+static int match_extension(const char *path, const char *ext_list) {\r
+  struct vec ext_vec;\r
+  size_t path_len;\r
+\r
+  path_len = strlen(path);\r
+\r
+  while ((ext_list = next_option(ext_list, &ext_vec, NULL)) != NULL)\r
+    if (ext_vec.len < path_len &&\r
+        mg_strncasecmp(path + path_len - ext_vec.len,\r
+          ext_vec.ptr, ext_vec.len) == 0)\r
+      return 1;\r
+\r
+  return 0;\r
+}\r
+#endif // !NO_CGI\r
+\r
+// HTTP 1.1 assumes keep alive if "Connection:" header is not set\r
+// This function must tolerate situations when connection info is not\r
+// set up, for example if request parsing failed.\r
+static int should_keep_alive(const struct mg_connection *conn) {\r
+  const char *http_version = conn->request_info.http_version;\r
+  const char *header = mg_get_header(conn, "Connection");\r
+  return (header == NULL && http_version && !strcmp(http_version, "1.1")) ||\r
+      (header != NULL && !mg_strcasecmp(header, "keep-alive"));\r
+}\r
+\r
+static const char *suggest_connection_header(const struct mg_connection *conn) {\r
+  return should_keep_alive(conn) ? "keep-alive" : "close";\r
+}\r
+\r
+static void send_http_error(struct mg_connection *conn, int status,\r
+                            const char *reason, const char *fmt, ...) {\r
+  char buf[BUFSIZ];\r
+  va_list ap;\r
+  int len;\r
+\r
+  conn->request_info.status_code = status;\r
+\r
+  if (call_user(conn, MG_HTTP_ERROR) == NULL) {\r
+    buf[0] = '\0';\r
+    len = 0;\r
+\r
+    /* Errors 1xx, 204 and 304 MUST NOT send a body */\r
+    if (status > 199 && status != 204 && status != 304) {\r
+      len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason);\r
+      cry(conn, "%s", buf);\r
+      buf[len++] = '\n';\r
+\r
+      va_start(ap, fmt);\r
+      len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap);\r
+      va_end(ap);\r
+    }\r
+    DEBUG_TRACE(("[%s]", buf));\r
+\r
+    mg_printf(conn, "HTTP/1.1 %d %s\r\n"\r
+              "Content-Type: text/plain\r\n"\r
+              "Content-Length: %d\r\n"\r
+              "Connection: %s\r\n\r\n", status, reason, len,\r
+              suggest_connection_header(conn));\r
+    conn->num_bytes_sent += mg_printf(conn, "%s", buf);\r
+  }\r
+}\r
+\r
+#if defined(_WIN32) && !defined(__SYMBIAN32__)\r
+static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) {\r
+  unused = NULL;\r
+  *mutex = CreateMutex(NULL, FALSE, NULL);\r
+  return *mutex == NULL ? -1 : 0;\r
+}\r
+\r
+static int pthread_mutex_destroy(pthread_mutex_t *mutex) {\r
+  return CloseHandle(*mutex) == 0 ? -1 : 0;\r
+}\r
+\r
+static int pthread_mutex_lock(pthread_mutex_t *mutex) {\r
+  return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;\r
+}\r
+\r
+static int pthread_mutex_unlock(pthread_mutex_t *mutex) {\r
+  return ReleaseMutex(*mutex) == 0 ? -1 : 0;\r
+}\r
+\r
+static int pthread_cond_init(pthread_cond_t *cv, const void *unused) {\r
+  unused = NULL;\r
+  cv->signal = CreateEvent(NULL, FALSE, FALSE, NULL);\r
+  cv->broadcast = CreateEvent(NULL, TRUE, FALSE, NULL);\r
+  return cv->signal != NULL && cv->broadcast != NULL ? 0 : -1;\r
+}\r
+\r
+static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) {\r
+  HANDLE handles[] = {cv->signal, cv->broadcast};\r
+  ReleaseMutex(*mutex);\r
+  WaitForMultipleObjects(2, handles, FALSE, INFINITE);\r
+  return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;\r
+}\r
+\r
+static int pthread_cond_signal(pthread_cond_t *cv) {\r
+  return SetEvent(cv->signal) == 0 ? -1 : 0;\r
+}\r
+\r
+static int pthread_cond_broadcast(pthread_cond_t *cv) {\r
+  // Implementation with PulseEvent() has race condition, see\r
+  // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html\r
+  return PulseEvent(cv->broadcast) == 0 ? -1 : 0;\r
+}\r
+\r
+static int pthread_cond_destroy(pthread_cond_t *cv) {\r
+  return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1;\r
+}\r
+\r
+static pthread_t pthread_self(void) {\r
+  return GetCurrentThreadId();\r
+}\r
+\r
+// For Windows, change all slashes to backslashes in path names.\r
+static void change_slashes_to_backslashes(char *path) {\r
+  int i;\r
+\r
+  for (i = 0; path[i] != '\0'; i++) {\r
+    if (path[i] == '/')\r
+      path[i] = '\\';\r
+    // i > 0 check is to preserve UNC paths, like \\server\file.txt\r
+    if (path[i] == '\\' && i > 0)\r
+      while (path[i + 1] == '\\' || path[i + 1] == '/')\r
+        (void) memmove(path + i + 1,\r
+            path + i + 2, strlen(path + i + 1));\r
+  }\r
+}\r
+\r
+// Encode 'path' which is assumed UTF-8 string, into UNICODE string.\r
+// wbuf and wbuf_len is a target buffer and its length.\r
+static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {\r
+  char buf[PATH_MAX], *p;\r
+\r
+  mg_strlcpy(buf, path, sizeof(buf));\r
+  change_slashes_to_backslashes(buf);\r
+\r
+  // Point p to the end of the file name\r
+  p = buf + strlen(buf) - 1;\r
+\r
+  // Trim trailing backslash character\r
+  while (p > buf && *p == '\\' && p[-1] != ':') {\r
+    *p-- = '\0';\r
+  }\r
+\r
+   // Protect from CGI code disclosure.\r
+   // This is very nasty hole. Windows happily opens files with\r
+   // some garbage in the end of file name. So fopen("a.cgi    ", "r")\r
+   // actually opens "a.cgi", and does not return an error!\r
+  if (*p == 0x20 ||               // No space at the end\r
+      (*p == 0x2e && p > buf) ||  // No '.' but allow '.' as full path\r
+      *p == 0x2b ||               // No '+'\r
+      (*p & ~0x7f)) {             // And generally no non-ascii chars\r
+    (void) fprintf(stderr, "Rejecting suspicious path: [%s]", buf);\r
+    buf[0] = '\0';\r
+  }\r
+\r
+  (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);\r
+}\r
+\r
+#if defined(_WIN32_WCE)\r
+static time_t time(time_t *ptime) {\r
+  time_t t;\r
+  SYSTEMTIME st;\r
+  FILETIME ft;\r
+\r
+  GetSystemTime(&st);\r
+  SystemTimeToFileTime(&st, &ft);\r
+  t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);\r
+\r
+  if (ptime != NULL) {\r
+    *ptime = t;\r
+  }\r
+\r
+  return t;\r
+}\r
+\r
+static time_t mktime(struct tm *ptm) {\r
+  SYSTEMTIME st;\r
+  FILETIME ft, lft;\r
+\r
+  st.wYear = ptm->tm_year + 1900;\r
+  st.wMonth = ptm->tm_mon + 1;\r
+  st.wDay = ptm->tm_mday;\r
+  st.wHour = ptm->tm_hour;\r
+  st.wMinute = ptm->tm_min;\r
+  st.wSecond = ptm->tm_sec;\r
+  st.wMilliseconds = 0;\r
+\r
+  SystemTimeToFileTime(&st, &ft);\r
+  LocalFileTimeToFileTime(&ft, &lft);\r
+  return (time_t) ((MAKEUQUAD(lft.dwLowDateTime, lft.dwHighDateTime) -\r
+                    EPOCH_DIFF) / RATE_DIFF);\r
+}\r
+\r
+static struct tm *localtime(const time_t *ptime, struct tm *ptm) {\r
+  int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;\r
+  FILETIME ft, lft;\r
+  SYSTEMTIME st;\r
+  TIME_ZONE_INFORMATION tzinfo;\r
+\r
+  if (ptm == NULL) {\r
+    return NULL;\r
+  }\r
+\r
+  * (int64_t *) &ft = t;\r
+  FileTimeToLocalFileTime(&ft, &lft);\r
+  FileTimeToSystemTime(&lft, &st);\r
+  ptm->tm_year = st.wYear - 1900;\r
+  ptm->tm_mon = st.wMonth - 1;\r
+  ptm->tm_wday = st.wDayOfWeek;\r
+  ptm->tm_mday = st.wDay;\r
+  ptm->tm_hour = st.wHour;\r
+  ptm->tm_min = st.wMinute;\r
+  ptm->tm_sec = st.wSecond;\r
+  ptm->tm_yday = 0; // hope nobody uses this\r
+  ptm->tm_isdst =\r
+    GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;\r
+\r
+  return ptm;\r
+}\r
+\r
+static size_t strftime(char *dst, size_t dst_size, const char *fmt,\r
+                       const struct tm *tm) {\r
+  (void) snprintf(dst, dst_size, "implement strftime() for WinCE");\r
+  return 0;\r
+}\r
+#endif\r
+\r
+static int mg_rename(const char* oldname, const char* newname) {\r
+  wchar_t woldbuf[PATH_MAX];\r
+  wchar_t wnewbuf[PATH_MAX];\r
+\r
+  to_unicode(oldname, woldbuf, ARRAY_SIZE(woldbuf));\r
+  to_unicode(newname, wnewbuf, ARRAY_SIZE(wnewbuf));\r
+\r
+  return MoveFileW(woldbuf, wnewbuf) ? 0 : -1;\r
+}\r
+\r
+\r
+static FILE *mg_fopen(const char *path, const char *mode) {\r
+  wchar_t wbuf[PATH_MAX], wmode[20];\r
+\r
+  to_unicode(path, wbuf, ARRAY_SIZE(wbuf));\r
+  MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));\r
+\r
+  return _wfopen(wbuf, wmode);\r
+}\r
+\r
+static int mg_stat(const char *path, struct mgstat *stp) {\r
+  int ok = -1; // Error\r
+  wchar_t wbuf[PATH_MAX];\r
+  WIN32_FILE_ATTRIBUTE_DATA info;\r
+\r
+  to_unicode(path, wbuf, ARRAY_SIZE(wbuf));\r
+\r
+  if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {\r
+    stp->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);\r
+    stp->mtime = SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime,\r
+                               info.ftLastWriteTime.dwHighDateTime);\r
+    stp->is_directory =\r
+      info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;\r
+    ok = 0;  // Success\r
+  }\r
+\r
+  return ok;\r
+}\r
+\r
+static int mg_remove(const char *path) {\r
+  wchar_t wbuf[PATH_MAX];\r
+  to_unicode(path, wbuf, ARRAY_SIZE(wbuf));\r
+  return DeleteFileW(wbuf) ? 0 : -1;\r
+}\r
+\r
+static int mg_mkdir(const char *path, int mode) {\r
+  char buf[PATH_MAX];\r
+  wchar_t wbuf[PATH_MAX];\r
+\r
+  mode = 0; // Unused\r
+  mg_strlcpy(buf, path, sizeof(buf));\r
+  change_slashes_to_backslashes(buf);\r
+\r
+  (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));\r
+\r
+  return CreateDirectoryW(wbuf, NULL) ? 0 : -1;\r
+}\r
+\r
+// Implementation of POSIX opendir/closedir/readdir for Windows.\r
+static DIR * opendir(const char *name) {\r
+  DIR *dir = NULL;\r
+  wchar_t wpath[PATH_MAX];\r
+  DWORD attrs;\r
+\r
+  if (name == NULL) {\r
+    SetLastError(ERROR_BAD_ARGUMENTS);\r
+  } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {\r
+    SetLastError(ERROR_NOT_ENOUGH_MEMORY);\r
+  } else {\r
+    to_unicode(name, wpath, ARRAY_SIZE(wpath));\r
+    attrs = GetFileAttributesW(wpath);\r
+    if (attrs != 0xFFFFFFFF &&\r
+        ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {\r
+      (void) wcscat(wpath, L"\\*");\r
+      dir->handle = FindFirstFileW(wpath, &dir->info);\r
+      dir->result.d_name[0] = '\0';\r
+    } else {\r
+      free(dir);\r
+      dir = NULL;\r
+    }\r
+  }\r
+\r
+  return dir;\r
+}\r
+\r
+static int closedir(DIR *dir) {\r
+  int result = 0;\r
+\r
+  if (dir != NULL) {\r
+    if (dir->handle != INVALID_HANDLE_VALUE)\r
+      result = FindClose(dir->handle) ? 0 : -1;\r
+\r
+    free(dir);\r
+  } else {\r
+    result = -1;\r
+    SetLastError(ERROR_BAD_ARGUMENTS);\r
+  }\r
+\r
+  return result;\r
+}\r
+\r
+struct dirent * readdir(DIR *dir) {\r
+  struct dirent *result = 0;\r
+\r
+  if (dir) {\r
+    if (dir->handle != INVALID_HANDLE_VALUE) {\r
+      result = &dir->result;\r
+      (void) WideCharToMultiByte(CP_UTF8, 0,\r
+          dir->info.cFileName, -1, result->d_name,\r
+          sizeof(result->d_name), NULL, NULL);\r
+\r
+      if (!FindNextFileW(dir->handle, &dir->info)) {\r
+        (void) FindClose(dir->handle);\r
+        dir->handle = INVALID_HANDLE_VALUE;\r
+      }\r
+\r
+    } else {\r
+      SetLastError(ERROR_FILE_NOT_FOUND);\r
+    }\r
+  } else {\r
+    SetLastError(ERROR_BAD_ARGUMENTS);\r
+  }\r
+\r
+  return result;\r
+}\r
+\r
+#define set_close_on_exec(fd) // No FD_CLOEXEC on Windows\r
+\r
+static int start_thread(struct mg_context *ctx, mg_thread_func_t func,\r
+                        void *param) {\r
+  HANDLE hThread;\r
+  ctx = NULL; // Unused\r
+\r
+  hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) func, param, 0,\r
+                         NULL);\r
+  if (hThread != NULL) {\r
+    (void) CloseHandle(hThread);\r
+  }\r
+\r
+  return hThread == NULL ? -1 : 0;\r
+}\r
+\r
+static HANDLE dlopen(const char *dll_name, int flags) {\r
+  wchar_t wbuf[PATH_MAX];\r
+  flags = 0; // Unused\r
+  to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));\r
+  return LoadLibraryW(wbuf);\r
+}\r
+\r
+#if !defined(NO_CGI)\r
+#define SIGKILL 0\r
+static int kill(pid_t pid, int sig_num) {\r
+  (void) TerminateProcess(pid, sig_num);\r
+  (void) CloseHandle(pid);\r
+  return 0;\r
+}\r
+\r
+static pid_t spawn_process(struct mg_connection *conn, const char *prog,\r
+                           char *envblk, char *envp[], int fd_stdin,\r
+                           int fd_stdout, const char *dir) {\r
+  HANDLE me;\r
+  char *p, *interp, cmdline[PATH_MAX], buf[PATH_MAX];\r
+  FILE *fp;\r
+  STARTUPINFOA si;\r
+  PROCESS_INFORMATION pi;\r
+\r
+  envp = NULL; // Unused\r
+\r
+  (void) memset(&si, 0, sizeof(si));\r
+  (void) memset(&pi, 0, sizeof(pi));\r
+\r
+  // TODO(lsm): redirect CGI errors to the error log file\r
+  si.cb  = sizeof(si);\r
+  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;\r
+  si.wShowWindow = SW_HIDE;\r
+\r
+  me = GetCurrentProcess();\r
+  (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me,\r
+      &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);\r
+  (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me,\r
+      &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);\r
+\r
+  // If CGI file is a script, try to read the interpreter line\r
+  interp = conn->ctx->config[CGI_INTERPRETER];\r
+  if (interp == NULL) {\r
+    buf[2] = '\0';\r
+    if ((fp = fopen(cmdline, "r")) != NULL) {\r
+      (void) fgets(buf, sizeof(buf), fp);\r
+      if (buf[0] != '#' || buf[1] != '!') {\r
+        // First line does not start with "#!". Do not set interpreter.\r
+        buf[2] = '\0';\r
+      } else {\r
+        // Trim whitespaces in interpreter name\r
+        for (p = &buf[strlen(buf) - 1]; p > buf && isspace(*p); p--) {\r
+          *p = '\0';\r
+        }\r
+      }\r
+      (void) fclose(fp);\r
+    }\r
+    interp = buf + 2;\r
+  }\r
+\r
+  (void) mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s%s%c%s",\r
+                     interp, interp[0] == '\0' ? "" : " ", dir, DIRSEP, prog);\r
+\r
+  DEBUG_TRACE(("Running [%s]", cmdline));\r
+  if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,\r
+        CREATE_NEW_PROCESS_GROUP, envblk, dir, &si, &pi) == 0) {\r
+    cry(conn, "%s: CreateProcess(%s): %d",\r
+        __func__, cmdline, ERRNO);\r
+    pi.hProcess = (pid_t) -1;\r
+  } else {\r
+    (void) close(fd_stdin);\r
+    (void) close(fd_stdout);\r
+  }\r
+\r
+  (void) CloseHandle(si.hStdOutput);\r
+  (void) CloseHandle(si.hStdInput);\r
+  (void) CloseHandle(pi.hThread);\r
+\r
+  return (pid_t) pi.hProcess;\r
+}\r
+#endif /* !NO_CGI */\r
+\r
+static int set_non_blocking_mode(SOCKET sock) {\r
+  unsigned long on = 1;\r
+  return ioctlsocket(sock, FIONBIO, &on);\r
+}\r
+\r
+#else\r
+static int mg_stat(const char *path, struct mgstat *stp) {\r
+  struct stat st;\r
+  int ok;\r
+\r
+  if (stat(path, &st) == 0) {\r
+    ok = 0;\r
+    stp->size = st.st_size;\r
+    stp->mtime = st.st_mtime;\r
+    stp->is_directory = S_ISDIR(st.st_mode);\r
+  } else {\r
+    ok = -1;\r
+  }\r
+\r
+  return ok;\r
+}\r
+\r
+static void set_close_on_exec(int fd) {\r
+  (void) fcntl(fd, F_SETFD, FD_CLOEXEC);\r
+}\r
+\r
+static int start_thread(struct mg_context *ctx, mg_thread_func_t func,\r
+                        void *param) {\r
+  pthread_t thread_id;\r
+  pthread_attr_t attr;\r
+  int retval;\r
+\r
+  (void) pthread_attr_init(&attr);\r
+  (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);\r
+  // TODO(lsm): figure out why mongoose dies on Linux if next line is enabled\r
+  // (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5);\r
+\r
+  if ((retval = pthread_create(&thread_id, &attr, func, param)) != 0) {\r
+    cry(fc(ctx), "%s: %s", __func__, strerror(retval));\r
+  }\r
+\r
+  return retval;\r
+}\r
+\r
+#ifndef NO_CGI\r
+static pid_t spawn_process(struct mg_connection *conn, const char *prog,\r
+                           char *envblk, char *envp[], int fd_stdin,\r
+                           int fd_stdout, const char *dir) {\r
+  pid_t pid;\r
+  const char *interp;\r
+\r
+  envblk = NULL; // Unused\r
+\r
+  if ((pid = fork()) == -1) {\r
+    // Parent\r
+    send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));\r
+  } else if (pid == 0) {\r
+    // Child\r
+    if (chdir(dir) != 0) {\r
+      cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));\r
+    } else if (dup2(fd_stdin, 0) == -1) {\r
+      cry(conn, "%s: dup2(%d, 0): %s", __func__, fd_stdin, strerror(ERRNO));\r
+    } else if (dup2(fd_stdout, 1) == -1) {\r
+      cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, strerror(ERRNO));\r
+    } else {\r
+      (void) dup2(fd_stdout, 2);\r
+      (void) close(fd_stdin);\r
+      (void) close(fd_stdout);\r
+\r
+      // Execute CGI program. No need to lock: new process\r
+      interp = conn->ctx->config[CGI_INTERPRETER];\r
+      if (interp == NULL) {\r
+        (void) execle(prog, prog, NULL, envp);\r
+        cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));\r
+      } else {\r
+        (void) execle(interp, interp, prog, NULL, envp);\r
+        cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,\r
+            strerror(ERRNO));\r
+      }\r
+    }\r
+    exit(EXIT_FAILURE);\r
+  } else {\r
+    // Parent. Close stdio descriptors\r
+    (void) close(fd_stdin);\r
+    (void) close(fd_stdout);\r
+  }\r
+\r
+  return pid;\r
+}\r
+#endif // !NO_CGI\r
+\r
+static int set_non_blocking_mode(SOCKET sock) {\r
+  int flags;\r
+\r
+  flags = fcntl(sock, F_GETFL, 0);\r
+  (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);\r
+\r
+  return 0;\r
+}\r
+#endif // _WIN32\r
+\r
+// Write data to the IO channel - opened file descriptor, socket or SSL\r
+// descriptor. Return number of bytes written.\r
+static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,\r
+                    int64_t len) {\r
+  int64_t sent;\r
+  int n, k;\r
+\r
+  sent = 0;\r
+  while (sent < len) {\r
+\r
+    /* How many bytes we send in this iteration */\r
+    k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);\r
+\r
+    if (ssl != NULL) {\r
+      n = SSL_write(ssl, buf + sent, k);\r
+    } else if (fp != NULL) {\r
+      n = fwrite(buf + sent, 1, (size_t)k, fp);\r
+      if (ferror(fp))\r
+        n = -1;\r
+    } else {\r
+      n = send(sock, buf + sent, (size_t)k, 0);\r
+    }\r
+\r
+    if (n < 0)\r
+      break;\r
+\r
+    sent += n;\r
+  }\r
+\r
+  return sent;\r
+}\r
+\r
+// Read from IO channel - opened file descriptor, socket, or SSL descriptor.\r
+// Return number of bytes read.\r
+static int pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len) {\r
+  int nread;\r
+\r
+  if (ssl != NULL) {\r
+    nread = SSL_read(ssl, buf, len);\r
+  } else if (fp != NULL) {\r
+    // Use read() instead of fread(), because if we're reading from the CGI\r
+    // pipe, fread() may block until IO buffer is filled up. We cannot afford\r
+    // to block and must pass all read bytes immediately to the client.\r
+    nread = read(fileno(fp), buf, (size_t) len);\r
+    if (ferror(fp))\r
+      nread = -1;\r
+  } else {\r
+    nread = recv(sock, buf, (size_t) len, 0);\r
+  }\r
+\r
+  return nread;\r
+}\r
+\r
+int mg_read(struct mg_connection *conn, void *buf, size_t len) {\r
+  int n, buffered_len, nread;\r
+  const char *buffered;\r
+\r
+  assert((conn->content_len == -1 && conn->consumed_content == 0) ||\r
+         conn->consumed_content <= conn->content_len);\r
+  DEBUG_TRACE(("%p %zu %lld %lld", buf, len,\r
+               conn->content_len, conn->consumed_content));\r
+  nread = 0;\r
+  if (conn->consumed_content < conn->content_len) {\r
+\r
+    // Adjust number of bytes to read.\r
+    int64_t to_read = conn->content_len - conn->consumed_content;\r
+    if (to_read < (int64_t) len) {\r
+      len = (int) to_read;\r
+    }\r
+\r
+    // How many bytes of data we have buffered in the request buffer?\r
+    buffered = conn->buf + conn->request_len + conn->consumed_content;\r
+    buffered_len = conn->data_len - conn->request_len;\r
+    assert(buffered_len >= 0);\r
+\r
+    // Return buffered data back if we haven't done that yet.\r
+    if (conn->consumed_content < (int64_t) buffered_len) {\r
+      buffered_len -= (int) conn->consumed_content;\r
+      if (len < (size_t) buffered_len) {\r
+        buffered_len = len;\r
+      }\r
+      memcpy(buf, buffered, (size_t)buffered_len);\r
+      len -= buffered_len;\r
+      buf = (char *) buf + buffered_len;\r
+      conn->consumed_content += buffered_len;\r
+      nread = buffered_len;\r
+    }\r
+\r
+    // We have returned all buffered data. Read new data from the remote socket.\r
+    while (len > 0) {\r
+      n = pull(NULL, conn->client.sock, conn->ssl, (char *) buf, (int) len);\r
+      if (n <= 0) {\r
+        break;\r
+      }\r
+      buf = (char *) buf + n;\r
+      conn->consumed_content += n;\r
+      nread += n;\r
+      len -= n;\r
+    }\r
+  }\r
+  return nread;\r
+}\r
+\r
+int mg_write(struct mg_connection *conn, const void *buf, size_t len) {\r
+  return (int) push(NULL, conn->client.sock, conn->ssl,\r
+      (const char *) buf, (int64_t) len);\r
+}\r
+\r
+int mg_printf(struct mg_connection *conn, const char *fmt, ...) {\r
+  char buf[BUFSIZ];\r
+  int len;\r
+  va_list ap;\r
+\r
+  va_start(ap, fmt);\r
+  len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap);\r
+  va_end(ap);\r
+\r
+  return mg_write(conn, buf, (size_t)len);\r
+}\r
+\r
+// URL-decode input buffer into destination buffer.\r
+// 0-terminate the destination buffer. Return the length of decoded data.\r
+// form-url-encoded data differs from URI encoding in a way that it\r
+// uses '+' as character for space, see RFC 1866 section 8.2.1\r
+// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt\r
+static size_t url_decode(const char *src, size_t src_len, char *dst,\r
+                         size_t dst_len, int is_form_url_encoded) {\r
+  size_t i, j;\r
+  int a, b;\r
+#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')\r
+\r
+  for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {\r
+    if (src[i] == '%' &&\r
+        isxdigit(* (const unsigned char *) (src + i + 1)) &&\r
+        isxdigit(* (const unsigned char *) (src + i + 2))) {\r
+      a = tolower(* (const unsigned char *) (src + i + 1));\r
+      b = tolower(* (const unsigned char *) (src + i + 2));\r
+      dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));\r
+      i += 2;\r
+    } else if (is_form_url_encoded && src[i] == '+') {\r
+      dst[j] = ' ';\r
+    } else {\r
+      dst[j] = src[i];\r
+    }\r
+  }\r
+\r
+  dst[j] = '\0'; /* Null-terminate the destination */\r
+\r
+  return j;\r
+}\r
+\r
+// Scan given buffer and fetch the value of the given variable.\r
+// It can be specified in query string, or in the POST data.\r
+// Return NULL if the variable not found, or allocated 0-terminated value.\r
+// It is caller's responsibility to free the returned value.\r
+int mg_get_var(const char *buf, size_t buf_len, const char *name,\r
+               char *dst, size_t dst_len) {\r
+  const char *p, *e, *s;\r
+  size_t name_len, len;\r
+\r
+  name_len = strlen(name);\r
+  e = buf + buf_len;\r
+  len = -1;\r
+  dst[0] = '\0';\r
+\r
+  // buf is "var1=val1&var2=val2...". Find variable first\r
+  for (p = buf; p != NULL && p + name_len < e; p++) {\r
+    if ((p == buf || p[-1] == '&') && p[name_len] == '=' &&\r
+        !mg_strncasecmp(name, p, name_len)) {\r
+\r
+      // Point p to variable value\r
+      p += name_len + 1;\r
+\r
+      // Point s to the end of the value\r
+      s = (const char *) memchr(p, '&', (size_t)(e - p));\r
+      if (s == NULL) {\r
+        s = e;\r
+      }\r
+      assert(s >= p);\r
+\r
+      // Decode variable into destination buffer\r
+      if ((size_t) (s - p) < dst_len) {\r
+        len = url_decode(p, (size_t)(s - p), dst, dst_len, 1);\r
+      }\r
+      break;\r
+    }\r
+  }\r
+\r
+  return len;\r
+}\r
+\r
+int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name,\r
+                  char *dst, size_t dst_size) {\r
+  const char *s, *p, *end;\r
+  int name_len, len = -1;\r
+\r
+  dst[0] = '\0';\r
+  if ((s = mg_get_header(conn, "Cookie")) == NULL) {\r
+    return 0;\r
+  }\r
+\r
+  name_len = strlen(cookie_name);\r
+  end = s + strlen(s);\r
+\r
+  for (; (s = strstr(s, cookie_name)) != NULL; s += name_len)\r
+    if (s[name_len] == '=') {\r
+      s += name_len + 1;\r
+      if ((p = strchr(s, ' ')) == NULL)\r
+        p = end;\r
+      if (p[-1] == ';')\r
+        p--;\r
+      if (*s == '"' && p[-1] == '"' && p > s + 1) {\r
+        s++;\r
+        p--;\r
+      }\r
+      if ((size_t) (p - s) < dst_size) {\r
+        len = (p - s) + 1;\r
+        mg_strlcpy(dst, s, (size_t)len);\r
+      }\r
+      break;\r
+    }\r
+\r
+  return len;\r
+}\r
+\r
+// Mongoose allows to specify multiple directories to serve,\r
+// like /var/www,/~bob=/home/bob. That means that root directory depends on URI.\r
+// This function returns root dir for given URI.\r
+static int get_document_root(const struct mg_connection *conn,\r
+                             struct vec *document_root) {\r
+  const char *root, *uri;\r
+  int len_of_matched_uri;\r
+  struct vec uri_vec, path_vec;\r
+\r
+  uri = conn->request_info.uri;\r
+  len_of_matched_uri = 0;\r
+  root = next_option(conn->ctx->config[DOCUMENT_ROOT], document_root, NULL);\r
+\r
+  while ((root = next_option(root, &uri_vec, &path_vec)) != NULL) {\r
+    if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) {\r
+      *document_root = path_vec;\r
+      len_of_matched_uri = uri_vec.len;\r
+      break;\r
+    }\r
+  }\r
+\r
+  return len_of_matched_uri;\r
+}\r
+\r
+static void convert_uri_to_file_name(struct mg_connection *conn,\r
+                                     const char *uri, char *buf,\r
+                                     size_t buf_len) {\r
+  struct vec vec;\r
+  int match_len;\r
+\r
+  match_len = get_document_root(conn, &vec);\r
+  mg_snprintf(conn, buf, buf_len, "%.*s%s", vec.len, vec.ptr, uri + match_len);\r
+\r
+#if defined(_WIN32) && !defined(__SYMBIAN32__)\r
+  change_slashes_to_backslashes(buf);\r
+#endif /* _WIN32 */\r
+\r
+  DEBUG_TRACE(("[%s] -> [%s], [%.*s]", uri, buf, (int) vec.len, vec.ptr));\r
+}\r
+\r
+static int sslize(struct mg_connection *conn, int (*func)(SSL *)) {\r
+  return (conn->ssl = SSL_new(conn->ctx->ssl_ctx)) != NULL &&\r
+    SSL_set_fd(conn->ssl, conn->client.sock) == 1 &&\r
+    func(conn->ssl) == 1;\r
+}\r
+\r
+static struct mg_connection *mg_connect(struct mg_connection *conn,\r
+                                 const char *host, int port, int use_ssl) {\r
+  struct mg_connection *newconn = NULL;\r
+  struct sockaddr_in sin;\r
+  struct hostent *he;\r
+  int sock;\r
+\r
+  if (conn->ctx->ssl_ctx == NULL && use_ssl) {\r
+    cry(conn, "%s: SSL is not initialized", __func__);\r
+  } else if ((he = gethostbyname(host)) == NULL) {\r
+    cry(conn, "%s: gethostbyname(%s): %s", __func__, host, strerror(ERRNO));\r
+  } else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {\r
+    cry(conn, "%s: socket: %s", __func__, strerror(ERRNO));\r
+  } else {\r
+    sin.sin_family = AF_INET;\r
+    sin.sin_port = htons((uint16_t) port);\r
+    sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];\r
+    if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {\r
+      cry(conn, "%s: connect(%s:%d): %s", __func__, host, port,\r
+          strerror(ERRNO));\r
+      closesocket(sock);\r
+    } else if ((newconn = (struct mg_connection *)\r
+                calloc(1, sizeof(*newconn))) == NULL) {\r
+      cry(conn, "%s: calloc: %s", __func__, strerror(ERRNO));\r
+      closesocket(sock);\r
+    } else {\r
+      newconn->client.sock = sock;\r
+      newconn->client.rsa.u.sin = sin;\r
+      if (use_ssl) {\r
+        sslize(newconn, SSL_connect);\r
+      }\r
+    }\r
+  }\r
+\r
+  return newconn;\r
+}\r
+\r
+// Check whether full request is buffered. Return:\r
+//   -1  if request is malformed\r
+//    0  if request is not yet fully buffered\r
+//   >0  actual request length, including last \r\n\r\n\r
+static int get_request_len(const char *buf, int buflen) {\r
+  const char *s, *e;\r
+  int len = 0;\r
+\r
+  DEBUG_TRACE(("buf: %p, len: %d", buf, buflen));\r
+  for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)\r
+    // Control characters are not allowed but >=128 is.\r
+    if (!isprint(* (const unsigned char *) s) && *s != '\r' &&\r
+        *s != '\n' && * (const unsigned char *) s < 128) {\r
+      len = -1;\r
+    } else if (s[0] == '\n' && s[1] == '\n') {\r
+      len = (int) (s - buf) + 2;\r
+    } else if (s[0] == '\n' && &s[1] < e &&\r
+        s[1] == '\r' && s[2] == '\n') {\r
+      len = (int) (s - buf) + 3;\r
+    }\r
+\r
+  return len;\r
+}\r
+\r
+// Convert month to the month number. Return -1 on error, or month number\r
+static int month_number_to_month_name(const char *s) {\r
+  size_t i;\r
+\r
+  for (i = 0; i < ARRAY_SIZE(month_names); i++)\r
+    if (!strcmp(s, month_names[i]))\r
+      return (int) i;\r
+\r
+  return -1;\r
+}\r
+\r
+// Parse date-time string, and return the corresponding time_t value\r
+static time_t parse_date_string(const char *s) {\r
+  time_t current_time;\r
+  struct tm tm, *tmp;\r
+  char mon[32];\r
+  int sec, min, hour, mday, month, year;\r
+\r
+  (void) memset(&tm, 0, sizeof(tm));\r
+  sec = min = hour = mday = month = year = 0;\r
+\r
+  if (((sscanf(s, "%d/%3s/%d %d:%d:%d",\r
+            &mday, mon, &year, &hour, &min, &sec) == 6) ||\r
+        (sscanf(s, "%d %3s %d %d:%d:%d",\r
+                &mday, mon, &year, &hour, &min, &sec) == 6) ||\r
+        (sscanf(s, "%*3s, %d %3s %d %d:%d:%d",\r
+                &mday, mon, &year, &hour, &min, &sec) == 6) ||\r
+        (sscanf(s, "%d-%3s-%d %d:%d:%d",\r
+                &mday, mon, &year, &hour, &min, &sec) == 6)) &&\r
+      (month = month_number_to_month_name(mon)) != -1) {\r
+    tm.tm_mday = mday;\r
+    tm.tm_mon = month;\r
+    tm.tm_year = year;\r
+    tm.tm_hour = hour;\r
+    tm.tm_min = min;\r
+    tm.tm_sec = sec;\r
+  }\r
+\r
+  if (tm.tm_year > 1900) {\r
+    tm.tm_year -= 1900;\r
+  } else if (tm.tm_year < 70) {\r
+    tm.tm_year += 100;\r
+  }\r
+\r
+  // Set Daylight Saving Time field\r
+  current_time = time(NULL);\r
+  tmp = localtime(&current_time);\r
+  tm.tm_isdst = tmp->tm_isdst;\r
+\r
+  return mktime(&tm);\r
+}\r
+\r
+// Protect against directory disclosure attack by removing '..',\r
+// excessive '/' and '\' characters\r
+static void remove_double_dots_and_double_slashes(char *s) {\r
+  char *p = s;\r
+\r
+  while (*s != '\0') {\r
+    *p++ = *s++;\r
+    if (s[-1] == '/' || s[-1] == '\\') {\r
+      // Skip all following slashes and backslashes\r
+      while (*s == '/' || *s == '\\') {\r
+        s++;\r
+      }\r
+\r
+      // Skip all double-dots\r
+      while (*s == '.' && s[1] == '.') {\r
+        s += 2;\r
+      }\r
+    }\r
+  }\r
+  *p = '\0';\r
+}\r
+\r
+static const struct {\r
+  const char *extension;\r
+  size_t ext_len;\r
+  const char *mime_type;\r
+  size_t mime_type_len;\r
+} builtin_mime_types[] = {\r
+  {".html", 5, "text/html",   9},\r
+  {".htm", 4, "text/html",   9},\r
+  {".shtm", 5, "text/html",   9},\r
+  {".shtml", 6, "text/html",   9},\r
+  {".css", 4, "text/css",   8},\r
+  {".js",  3, "application/x-javascript", 24},\r
+  {".ico", 4, "image/x-icon",   12},\r
+  {".gif", 4, "image/gif",   9},\r
+  {".jpg", 4, "image/jpeg",   10},\r
+  {".jpeg", 5, "image/jpeg",   10},\r
+  {".png", 4, "image/png",   9},\r
+  {".svg", 4, "image/svg+xml",  13},\r
+  {".torrent", 8, "application/x-bittorrent", 24},\r
+  {".wav", 4, "audio/x-wav",   11},\r
+  {".mp3", 4, "audio/x-mp3",   11},\r
+  {".mid", 4, "audio/mid",   9},\r
+  {".m3u", 4, "audio/x-mpegurl",  15},\r
+  {".ram", 4, "audio/x-pn-realaudio",  20},\r
+  {".xml", 4, "text/xml",   8},\r
+  {".xslt", 5, "application/xml",  15},\r
+  {".ra",  3, "audio/x-pn-realaudio",  20},\r
+  {".doc", 4, "application/msword",  19},\r
+  {".exe", 4, "application/octet-stream", 24},\r
+  {".zip", 4, "application/x-zip-compressed", 28},\r
+  {".xls", 4, "application/excel",  17},\r
+  {".tgz", 4, "application/x-tar-gz",  20},\r
+  {".tar", 4, "application/x-tar",  17},\r
+  {".gz",  3, "application/x-gunzip",  20},\r
+  {".arj", 4, "application/x-arj-compressed", 28},\r
+  {".rar", 4, "application/x-arj-compressed", 28},\r
+  {".rtf", 4, "application/rtf",  15},\r
+  {".pdf", 4, "application/pdf",  15},\r
+  {".swf", 4, "application/x-shockwave-flash",29},\r
+  {".mpg", 4, "video/mpeg",   10},\r
+  {".mpeg", 5, "video/mpeg",   10},\r
+  {".asf", 4, "video/x-ms-asf",  14},\r
+  {".avi", 4, "video/x-msvideo",  15},\r
+  {".bmp", 4, "image/bmp",   9},\r
+  {NULL,  0, NULL,    0}\r
+};\r
+\r
+// Look at the "path" extension and figure what mime type it has.\r
+// Store mime type in the vector.\r
+static void get_mime_type(struct mg_context *ctx, const char *path,\r
+                          struct vec *vec) {\r
+  struct vec ext_vec, mime_vec;\r
+  const char *list, *ext;\r
+  size_t i, path_len;\r
+\r
+  path_len = strlen(path);\r
+\r
+  // Scan user-defined mime types first, in case user wants to\r
+  // override default mime types.\r
+  list = ctx->config[EXTRA_MIME_TYPES];\r
+  while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {\r
+    // ext now points to the path suffix\r
+    ext = path + path_len - ext_vec.len;\r
+    if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {\r
+      *vec = mime_vec;\r
+      return;\r
+    }\r
+  }\r
+\r
+  // Now scan built-in mime types\r
+  for (i = 0; builtin_mime_types[i].extension != NULL; i++) {\r
+    ext = path + (path_len - builtin_mime_types[i].ext_len);\r
+    if (path_len > builtin_mime_types[i].ext_len &&\r
+        mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {\r
+      vec->ptr = builtin_mime_types[i].mime_type;\r
+      vec->len = builtin_mime_types[i].mime_type_len;\r
+      return;\r
+    }\r
+  }\r
+\r
+  // Nothing found. Fall back to "text/plain"\r
+  vec->ptr = "text/plain";\r
+  vec->len = 10;\r
+}\r
+\r
+#ifndef HAVE_MD5\r
+typedef struct MD5Context {\r
+  uint32_t buf[4];\r
+  uint32_t bits[2];\r
+  unsigned char in[64];\r
+} MD5_CTX;\r
+\r
+#if defined(__BYTE_ORDER) && (__BYTE_ORDER == 1234)\r
+#define byteReverse(buf, len) // Do nothing\r
+#else\r
+static void byteReverse(unsigned char *buf, unsigned longs) {\r
+  uint32_t t;\r
+  do {\r
+    t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |\r
+      ((unsigned) buf[1] << 8 | buf[0]);\r
+    *(uint32_t *) buf = t;\r
+    buf += 4;\r
+  } while (--longs);\r
+}\r
+#endif\r
+\r
+#define F1(x, y, z) (z ^ (x & (y ^ z)))\r
+#define F2(x, y, z) F1(z, x, y)\r
+#define F3(x, y, z) (x ^ y ^ z)\r
+#define F4(x, y, z) (y ^ (x | ~z))\r
+\r
+#define MD5STEP(f, w, x, y, z, data, s) \\r
+  ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )\r
+\r
+// Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious\r
+// initialization constants.\r
+static void MD5Init(MD5_CTX *ctx) {\r
+  ctx->buf[0] = 0x67452301;\r
+  ctx->buf[1] = 0xefcdab89;\r
+  ctx->buf[2] = 0x98badcfe;\r
+  ctx->buf[3] = 0x10325476;\r
+\r
+  ctx->bits[0] = 0;\r
+  ctx->bits[1] = 0;\r
+}\r
+\r
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {\r
+  register uint32_t a, b, c, d;\r
+\r
+  a = buf[0];\r
+  b = buf[1];\r
+  c = buf[2];\r
+  d = buf[3];\r
+\r
+  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);\r
+  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);\r
+  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);\r
+  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);\r
+  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);\r
+  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);\r
+  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);\r
+  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);\r
+  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);\r
+  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);\r
+  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);\r
+  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);\r
+  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);\r
+  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);\r
+  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);\r
+  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);\r
+\r
+  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);\r
+  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);\r
+  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);\r
+  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);\r
+  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);\r
+  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);\r
+  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);\r
+  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);\r
+  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);\r
+  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);\r
+  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);\r
+  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);\r
+  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);\r
+  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);\r
+  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);\r
+  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);\r
+\r
+  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);\r
+  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);\r
+  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);\r
+  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);\r
+  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);\r
+  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);\r
+  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);\r
+  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);\r
+  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);\r
+  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);\r
+  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);\r
+  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);\r
+  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);\r
+  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);\r
+  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);\r
+  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);\r
+\r
+  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);\r
+  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);\r
+  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);\r
+  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);\r
+  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);\r
+  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);\r
+  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);\r
+  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);\r
+  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);\r
+  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);\r
+  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);\r
+  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);\r
+  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);\r
+  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);\r
+  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);\r
+  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);\r
+\r
+  buf[0] += a;\r
+  buf[1] += b;\r
+  buf[2] += c;\r
+  buf[3] += d;\r
+}\r
+\r
+static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {\r
+  uint32_t t;\r
+\r
+  t = ctx->bits[0];\r
+  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)\r
+    ctx->bits[1]++;\r
+  ctx->bits[1] += len >> 29;\r
+\r
+  t = (t >> 3) & 0x3f;\r
+\r
+  if (t) {\r
+    unsigned char *p = (unsigned char *) ctx->in + t;\r
+\r
+    t = 64 - t;\r
+    if (len < t) {\r
+      memcpy(p, buf, len);\r
+      return;\r
+    }\r
+    memcpy(p, buf, t);\r
+    byteReverse(ctx->in, 16);\r
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);\r
+    buf += t;\r
+    len -= t;\r
+  }\r
+\r
+  while (len >= 64) {\r
+    memcpy(ctx->in, buf, 64);\r
+    byteReverse(ctx->in, 16);\r
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);\r
+    buf += 64;\r
+    len -= 64;\r
+  }\r
+\r
+  memcpy(ctx->in, buf, len);\r
+}\r
+\r
+static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {\r
+  unsigned count;\r
+  unsigned char *p;\r
+\r
+  count = (ctx->bits[0] >> 3) & 0x3F;\r
+\r
+  p = ctx->in + count;\r
+  *p++ = 0x80;\r
+  count = 64 - 1 - count;\r
+  if (count < 8) {\r
+    memset(p, 0, count);\r
+    byteReverse(ctx->in, 16);\r
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);\r
+    memset(ctx->in, 0, 56);\r
+  } else {\r
+    memset(p, 0, count - 8);\r
+  }\r
+  byteReverse(ctx->in, 14);\r
+\r
+  ((uint32_t *) ctx->in)[14] = ctx->bits[0];\r
+  ((uint32_t *) ctx->in)[15] = ctx->bits[1];\r
+\r
+  MD5Transform(ctx->buf, (uint32_t *) ctx->in);\r
+  byteReverse((unsigned char *) ctx->buf, 4);\r
+  memcpy(digest, ctx->buf, 16);\r
+  memset((char *) ctx, 0, sizeof(*ctx));\r
+}\r
+#endif // !HAVE_MD5\r
+\r
+// Stringify binary data. Output buffer must be twice as big as input,\r
+// because each byte takes 2 bytes in string representation\r
+static void bin2str(char *to, const unsigned char *p, size_t len) {\r
+  static const char *hex = "0123456789abcdef";\r
+\r
+  for (; len--; p++) {\r
+    *to++ = hex[p[0] >> 4];\r
+    *to++ = hex[p[0] & 0x0f];\r
+  }\r
+  *to = '\0';\r
+}\r
+\r
+// Return stringified MD5 hash for list of vectors. Buffer must be 33 bytes.\r
+void mg_md5(char *buf, ...) {\r
+  unsigned char hash[16];\r
+  const char *p;\r
+  va_list ap;\r
+  MD5_CTX ctx;\r
+\r
+  MD5Init(&ctx);\r
+\r
+  va_start(ap, buf);\r
+  while ((p = va_arg(ap, const char *)) != NULL) {\r
+    MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));\r
+  }\r
+  va_end(ap);\r
+\r
+  MD5Final(hash, &ctx);\r
+  bin2str(buf, hash, sizeof(hash));\r
+}\r
+\r
+// Check the user's password, return 1 if OK\r
+static int check_password(const char *method, const char *ha1, const char *uri,\r
+                          const char *nonce, const char *nc, const char *cnonce,\r
+                          const char *qop, const char *response) {\r
+  char ha2[32 + 1], expected_response[32 + 1];\r
+\r
+  // Some of the parameters may be NULL\r
+  if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||\r
+      qop == NULL || response == NULL) {\r
+    return 0;\r
+  }\r
+\r
+  // NOTE(lsm): due to a bug in MSIE, we do not compare the URI\r
+  // TODO(lsm): check for authentication timeout\r
+  if (// strcmp(dig->uri, c->ouri) != 0 ||\r
+      strlen(response) != 32\r
+      // || now - strtoul(dig->nonce, NULL, 10) > 3600\r
+      ) {\r
+    return 0;\r
+  }\r
+\r
+  mg_md5(ha2, method, ":", uri, NULL);\r
+  mg_md5(expected_response, ha1, ":", nonce, ":", nc,\r
+      ":", cnonce, ":", qop, ":", ha2, NULL);\r
+\r
+  return mg_strcasecmp(response, expected_response) == 0;\r
+}\r
+\r
+// Use the global passwords file, if specified by auth_gpass option,\r
+// or search for .htpasswd in the requested directory.\r
+static FILE *open_auth_file(struct mg_connection *conn, const char *path) {\r
+  struct mg_context *ctx = conn->ctx;\r
+  char name[PATH_MAX];\r
+  const char *p, *e;\r
+  struct mgstat st;\r
+  FILE *fp;\r
+\r
+  if (ctx->config[GLOBAL_PASSWORDS_FILE] != NULL) {\r
+    // Use global passwords file\r
+    fp =  mg_fopen(ctx->config[GLOBAL_PASSWORDS_FILE], "r");\r
+    if (fp == NULL)\r
+      cry(fc(ctx), "fopen(%s): %s",\r
+          ctx->config[GLOBAL_PASSWORDS_FILE], strerror(ERRNO));\r
+  } else if (!mg_stat(path, &st) && st.is_directory) {\r
+    (void) mg_snprintf(conn, name, sizeof(name), "%s%c%s",\r
+        path, DIRSEP, PASSWORDS_FILE_NAME);\r
+    fp = mg_fopen(name, "r");\r
+  } else {\r
+     // Try to find .htpasswd in requested directory.\r
+    for (p = path, e = p + strlen(p) - 1; e > p; e--)\r
+      if (IS_DIRSEP_CHAR(*e))\r
+        break;\r
+    (void) mg_snprintf(conn, name, sizeof(name), "%.*s%c%s",\r
+        (int) (e - p), p, DIRSEP, PASSWORDS_FILE_NAME);\r
+    fp = mg_fopen(name, "r");\r
+  }\r
+\r
+  return fp;\r
+}\r
+\r
+// Parsed Authorization header\r
+struct ah {\r
+  char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;\r
+};\r
+\r
+static int parse_auth_header(struct mg_connection *conn, char *buf,\r
+                             size_t buf_size, struct ah *ah) {\r
+  char *name, *value, *s;\r
+  const char *auth_header;\r
+\r
+  if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||\r
+      mg_strncasecmp(auth_header, "Digest ", 7) != 0) {\r
+    return 0;\r
+  }\r
+\r
+  // Make modifiable copy of the auth header\r
+  (void) mg_strlcpy(buf, auth_header + 7, buf_size);\r
+\r
+  s = buf;\r
+  (void) memset(ah, 0, sizeof(*ah));\r
+\r
+  // Parse authorization header\r
+  for (;;) {\r
+    // Gobble initial spaces\r
+    while (isspace(* (unsigned char *) s)) {\r
+      s++;\r
+    }\r
+    name = skip_quoted(&s, "=", " ", 0);\r
+    /* Value is either quote-delimited, or ends at first comma or space. */\r
+    if (s[0] == '\"') {\r
+      s++;\r
+      value = skip_quoted(&s, "\"", " ", '\\');\r
+      if (s[0] == ',') {\r
+        s++;\r
+      }\r
+    } else {\r
+      value = skip_quoted(&s, ", ", " ", 0);  // IE uses commas, FF uses spaces\r
+    }\r
+    if (*name == '\0') {\r
+      break;\r
+    }\r
+\r
+    if (!strcmp(name, "username")) {\r
+      ah->user = value;\r
+    } else if (!strcmp(name, "cnonce")) {\r
+      ah->cnonce = value;\r
+    } else if (!strcmp(name, "response")) {\r
+      ah->response = value;\r
+    } else if (!strcmp(name, "uri")) {\r
+      ah->uri = value;\r
+    } else if (!strcmp(name, "qop")) {\r
+      ah->qop = value;\r
+    } else if (!strcmp(name, "nc")) {\r
+      ah->nc = value;\r
+    } else if (!strcmp(name, "nonce")) {\r
+      ah->nonce = value;\r
+    }\r
+  }\r
+\r
+  // CGI needs it as REMOTE_USER\r
+  if (ah->user != NULL) {\r
+    conn->request_info.remote_user = mg_strdup(ah->user);\r
+  } else {\r
+    return 0;\r
+  }\r
+\r
+  return 1;\r
+}\r
+\r
+// Authorize against the opened passwords file. Return 1 if authorized.\r
+static int authorize(struct mg_connection *conn, FILE *fp) {\r
+  struct ah ah;\r
+  char line[256], f_user[256], ha1[256], f_domain[256], buf[BUFSIZ];\r
+\r
+  if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {\r
+    return 0;\r
+  }\r
+\r
+  // Loop over passwords file\r
+  while (fgets(line, sizeof(line), fp) != NULL) {\r
+    if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {\r
+      continue;\r
+    }\r
+\r
+    if (!strcmp(ah.user, f_user) &&\r
+        !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))\r
+      return check_password(\r
+            conn->request_info.request_method,\r
+            ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop,\r
+            ah.response);\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
+// Return 1 if request is authorised, 0 otherwise.\r
+static int check_authorization(struct mg_connection *conn, const char *path) {\r
+  FILE *fp;\r
+  char fname[PATH_MAX];\r
+  struct vec uri_vec, filename_vec;\r
+  const char *list;\r
+  int authorized;\r
+\r
+  fp = NULL;\r
+  authorized = 1;\r
+\r
+  list = conn->ctx->config[PROTECT_URI];\r
+  while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {\r
+    if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {\r
+      (void) mg_snprintf(conn, fname, sizeof(fname), "%.*s",\r
+          filename_vec.len, filename_vec.ptr);\r
+      if ((fp = mg_fopen(fname, "r")) == NULL) {\r
+        cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno));\r
+      }\r
+      break;\r
+    }\r
+  }\r
+\r
+  if (fp == NULL) {\r
+    fp = open_auth_file(conn, path);\r
+  }\r
+\r
+  if (fp != NULL) {\r
+    authorized = authorize(conn, fp);\r
+    (void) fclose(fp);\r
+  }\r
+\r
+  return authorized;\r
+}\r
+\r
+static void send_authorization_request(struct mg_connection *conn) {\r
+  conn->request_info.status_code = 401;\r
+  (void) mg_printf(conn,\r
+      "HTTP/1.1 401 Unauthorized\r\n"\r
+      "WWW-Authenticate: Digest qop=\"auth\", "\r
+      "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",\r
+      conn->ctx->config[AUTHENTICATION_DOMAIN],\r
+      (unsigned long) time(NULL));\r
+}\r
+\r
+static int is_authorized_for_put(struct mg_connection *conn) {\r
+  FILE *fp;\r
+  int ret = 0;\r
+\r
+  fp = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ? NULL :\r
+    mg_fopen(conn->ctx->config[PUT_DELETE_PASSWORDS_FILE], "r");\r
+\r
+  if (fp != NULL) {\r
+    ret = authorize(conn, fp);\r
+    (void) fclose(fp);\r
+  }\r
+\r
+  return ret;\r
+}\r
+\r
+int mg_modify_passwords_file(const char *fname, const char *domain,\r
+                             const char *user, const char *pass) {\r
+  int found;\r
+  char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];\r
+  FILE *fp, *fp2;\r
+\r
+  found = 0;\r
+  fp = fp2 = NULL;\r
+\r
+  // Regard empty password as no password - remove user record.\r
+  if (pass[0] == '\0') {\r
+    pass = NULL;\r
+  }\r
+\r
+  (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);\r
+\r
+  // Create the file if does not exist\r
+  if ((fp = mg_fopen(fname, "a+")) != NULL) {\r
+    (void) fclose(fp);\r
+  }\r
+\r
+  // Open the given file and temporary file\r
+  if ((fp = mg_fopen(fname, "r")) == NULL) {\r
+    return 0;\r
+  } else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) {\r
+    fclose(fp);\r
+    return 0;\r
+  }\r
+\r
+  // Copy the stuff to temporary file\r
+  while (fgets(line, sizeof(line), fp) != NULL) {\r
+    if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {\r
+      continue;\r
+    }\r
+\r
+    if (!strcmp(u, user) && !strcmp(d, domain)) {\r
+      found++;\r
+      if (pass != NULL) {\r
+        mg_md5(ha1, user, ":", domain, ":", pass, NULL);\r
+        fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);\r
+      }\r
+    } else {\r
+      (void) fprintf(fp2, "%s", line);\r
+    }\r
+  }\r
+\r
+  // If new user, just add it\r
+  if (!found && pass != NULL) {\r
+    mg_md5(ha1, user, ":", domain, ":", pass, NULL);\r
+    (void) fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);\r
+  }\r
+\r
+  // Close files\r
+  (void) fclose(fp);\r
+  (void) fclose(fp2);\r
+\r
+  // Put the temp file in place of real file\r
+  (void) mg_remove(fname);\r
+  (void) mg_rename(tmp, fname);\r
+\r
+  return 1;\r
+}\r
+\r
+struct de {\r
+  struct mg_connection *conn;\r
+  char *file_name;\r
+  struct mgstat st;\r
+};\r
+\r
+static void url_encode(const char *src, char *dst, size_t dst_len) {\r
+  static const char *dont_escape = "._-$,;~()";\r
+  static const char *hex = "0123456789abcdef";\r
+  const char *end = dst + dst_len - 1;\r
+\r
+  for (; *src != '\0' && dst < end; src++, dst++) {\r
+    if (isalnum(*(const unsigned char *) src) ||\r
+        strchr(dont_escape, * (const unsigned char *) src) != NULL) {\r
+      *dst = *src;\r
+    } else if (dst + 2 < end) {\r
+      dst[0] = '%';\r
+      dst[1] = hex[(* (const unsigned char *) src) >> 4];\r
+      dst[2] = hex[(* (const unsigned char *) src) & 0xf];\r
+      dst += 2;\r
+    }\r
+  }\r
+\r
+  *dst = '\0';\r
+}\r
+\r
+static void print_dir_entry(struct de *de) {\r
+  char size[64], mod[64], href[PATH_MAX];\r
+\r
+  if (de->st.is_directory) {\r
+    (void) mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]");\r
+  } else {\r
+     // We use (signed) cast below because MSVC 6 compiler cannot\r
+     // convert unsigned __int64 to double. Sigh.\r
+    if (de->st.size < 1024) {\r
+      (void) mg_snprintf(de->conn, size, sizeof(size),\r
+          "%lu", (unsigned long) de->st.size);\r
+    } else if (de->st.size < 1024 * 1024) {\r
+      (void) mg_snprintf(de->conn, size, sizeof(size),\r
+          "%.1fk", (double) de->st.size / 1024.0);\r
+    } else if (de->st.size < 1024 * 1024 * 1024) {\r
+      (void) mg_snprintf(de->conn, size, sizeof(size),\r
+          "%.1fM", (double) de->st.size / 1048576);\r
+    } else {\r
+      (void) mg_snprintf(de->conn, size, sizeof(size),\r
+          "%.1fG", (double) de->st.size / 1073741824);\r
+    }\r
+  }\r
+  (void) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.mtime));\r
+  url_encode(de->file_name, href, sizeof(href));\r
+  de->conn->num_bytes_sent += mg_printf(de->conn,\r
+      "<tr><td><a href=\"%s%s%s\">%s%s</a></td>"\r
+      "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",\r
+      de->conn->request_info.uri, href, de->st.is_directory ? "/" : "",\r
+      de->file_name, de->st.is_directory ? "/" : "", mod, size);\r
+}\r
+\r
+// This function is called from send_directory() and used for\r
+// sorting directory entries by size, or name, or modification time.\r
+// On windows, __cdecl specification is needed in case if project is built\r
+// with __stdcall convention. qsort always requires __cdels callback.\r
+static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {\r
+  const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;\r
+  const char *query_string = a->conn->request_info.query_string;\r
+  int cmp_result = 0;\r
+\r
+  if (query_string == NULL) {\r
+    query_string = "na";\r
+  }\r
+\r
+  if (a->st.is_directory && !b->st.is_directory) {\r
+    return -1;  // Always put directories on top\r
+  } else if (!a->st.is_directory && b->st.is_directory) {\r
+    return 1;   // Always put directories on top\r
+  } else if (*query_string == 'n') {\r
+    cmp_result = strcmp(a->file_name, b->file_name);\r
+  } else if (*query_string == 's') {\r
+    cmp_result = a->st.size == b->st.size ? 0 :\r
+      a->st.size > b->st.size ? 1 : -1;\r
+  } else if (*query_string == 'd') {\r
+    cmp_result = a->st.mtime == b->st.mtime ? 0 :\r
+      a->st.mtime > b->st.mtime ? 1 : -1;\r
+  }\r
+\r
+  return query_string[1] == 'd' ? -cmp_result : cmp_result;\r
+}\r
+\r
+static void handle_directory_request(struct mg_connection *conn,\r
+                                     const char *dir) {\r
+  struct dirent *dp;\r
+  DIR *dirp;\r
+  struct de *entries = NULL;\r
+  char path[PATH_MAX];\r
+  int i, sort_direction, num_entries = 0, arr_size = 128;\r
+\r
+  if ((dirp = opendir(dir)) == NULL) {\r
+    send_http_error(conn, 500, "Cannot open directory",\r
+        "Error: opendir(%s): %s", path, strerror(ERRNO));\r
+    return;\r
+  }\r
+\r
+  (void) mg_printf(conn, "%s",\r
+      "HTTP/1.1 200 OK\r\n"\r
+      "Connection: close\r\n"\r
+      "Content-Type: text/html; charset=utf-8\r\n\r\n");\r
+\r
+  sort_direction = conn->request_info.query_string != NULL &&\r
+    conn->request_info.query_string[1] == 'd' ? 'a' : 'd';\r
+\r
+  while ((dp = readdir(dirp)) != NULL) {\r
+\r
+    // Do not show current dir and passwords file\r
+    if (!strcmp(dp->d_name, ".") ||\r
+        !strcmp(dp->d_name, "..") ||\r
+        !strcmp(dp->d_name, PASSWORDS_FILE_NAME))\r
+      continue;\r
+\r
+    if (entries == NULL || num_entries >= arr_size) {\r
+      arr_size *= 2;\r
+      entries = (struct de *) realloc(entries,\r
+          arr_size * sizeof(entries[0]));\r
+    }\r
+\r
+    if (entries == NULL) {\r
+      closedir(dirp);\r
+      send_http_error(conn, 500, "Cannot open directory",\r
+          "%s", "Error: cannot allocate memory");\r
+      return;\r
+    }\r
+\r
+    mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, DIRSEP, dp->d_name);\r
+\r
+    // If we don't memset stat structure to zero, mtime will have\r
+    // garbage and strftime() will segfault later on in\r
+    // print_dir_entry(). memset is required only if mg_stat()\r
+    // fails. For more details, see\r
+    // http://code.google.com/p/mongoose/issues/detail?id=79\r
+    if (mg_stat(path, &entries[num_entries].st) != 0) {\r
+      memset(&entries[num_entries].st, 0, sizeof(entries[num_entries].st));\r
+    }\r
+\r
+    entries[num_entries].conn = conn;\r
+    entries[num_entries].file_name = mg_strdup(dp->d_name);\r
+    num_entries++;\r
+  }\r
+  (void) closedir(dirp);\r
+\r
+  conn->num_bytes_sent += mg_printf(conn,\r
+      "<html><head><title>Index of %s</title>"\r
+      "<style>th {text-align: left;}</style></head>"\r
+      "<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"\r
+      "<tr><th><a href=\"?n%c\">Name</a></th>"\r
+      "<th><a href=\"?d%c\">Modified</a></th>"\r
+      "<th><a href=\"?s%c\">Size</a></th></tr>"\r
+      "<tr><td colspan=\"3\"><hr></td></tr>",\r
+      conn->request_info.uri, conn->request_info.uri,\r
+      sort_direction, sort_direction, sort_direction);\r
+\r
+  // Print first entry - link to a parent directory\r
+  conn->num_bytes_sent += mg_printf(conn,\r
+      "<tr><td><a href=\"%s%s\">%s</a></td>"\r
+      "<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",\r
+      conn->request_info.uri, "..", "Parent directory", "-", "-");\r
+\r
+  // Sort and print directory entries\r
+  qsort(entries, (size_t)num_entries, sizeof(entries[0]), compare_dir_entries);\r
+  for (i = 0; i < num_entries; i++) {\r
+    print_dir_entry(&entries[i]);\r
+    free(entries[i].file_name);\r
+  }\r
+  free(entries);\r
+\r
+  conn->num_bytes_sent += mg_printf(conn, "%s", "</table></body></html>");\r
+  conn->request_info.status_code = 200;\r
+}\r
+\r
+// Send len bytes from the opened file to the client.\r
+static void send_file_data(struct mg_connection *conn, FILE *fp, int64_t len) {\r
+  char buf[BUFSIZ];\r
+  int to_read, num_read, num_written;\r
+\r
+  while (len > 0) {\r
+    // Calculate how much to read from the file in the buffer\r
+    to_read = sizeof(buf);\r
+    if ((int64_t) to_read > len)\r
+      to_read = (int) len;\r
+\r
+    // Read from file, exit the loop on error\r
+    if ((num_read = fread(buf, 1, (size_t)to_read, fp)) == 0)\r
+      break;\r
+\r
+    // Send read bytes to the client, exit the loop on error\r
+    if ((num_written = mg_write(conn, buf, (size_t)num_read)) != num_read)\r
+      break;\r
+\r
+    // Both read and were successful, adjust counters\r
+    conn->num_bytes_sent += num_written;\r
+    len -= num_written;\r
+  }\r
+}\r
+\r
+static int parse_range_header(const char *header, int64_t *a, int64_t *b) {\r
+  return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);\r
+}\r
+\r
+static void handle_file_request(struct mg_connection *conn, const char *path,\r
+                                struct mgstat *stp) {\r
+  char date[64], lm[64], etag[64], range[64];\r
+  const char *fmt = "%a, %d %b %Y %H:%M:%S %Z", *msg = "OK", *hdr;\r
+  time_t curtime = time(NULL);\r
+  int64_t cl, r1, r2;\r
+  struct vec mime_vec;\r
+  FILE *fp;\r
+  int n;\r
+\r
+  get_mime_type(conn->ctx, path, &mime_vec);\r
+  cl = stp->size;\r
+  conn->request_info.status_code = 200;\r
+  range[0] = '\0';\r
+\r
+  if ((fp = mg_fopen(path, "rb")) == NULL) {\r
+    send_http_error(conn, 500, http_500_error,\r
+        "fopen(%s): %s", path, strerror(ERRNO));\r
+    return;\r
+  }\r
+  set_close_on_exec(fileno(fp));\r
+\r
+  // If Range: header specified, act accordingly\r
+  r1 = r2 = 0;\r
+  hdr = mg_get_header(conn, "Range");\r
+  if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0) {\r
+    conn->request_info.status_code = 206;\r
+    (void) fseeko(fp, (off_t) r1, SEEK_SET);\r
+    cl = n == 2 ? r2 - r1 + 1: cl - r1;\r
+    (void) mg_snprintf(conn, range, sizeof(range),\r
+        "Content-Range: bytes "\r
+        "%" INT64_FMT "-%"\r
+        INT64_FMT "/%" INT64_FMT "\r\n",\r
+        r1, r1 + cl - 1, stp->size);\r
+    msg = "Partial Content";\r
+  }\r
+\r
+  // Prepare Etag, Date, Last-Modified headers\r
+  (void) strftime(date, sizeof(date), fmt, localtime(&curtime));\r
+  (void) strftime(lm, sizeof(lm), fmt, localtime(&stp->mtime));\r
+  (void) mg_snprintf(conn, etag, sizeof(etag), "%lx.%lx",\r
+      (unsigned long) stp->mtime, (unsigned long) stp->size);\r
+\r
+  (void) mg_printf(conn,\r
+      "HTTP/1.1 %d %s\r\n"\r
+      "Date: %s\r\n"\r
+      "Last-Modified: %s\r\n"\r
+      "Etag: \"%s\"\r\n"\r
+      "Content-Type: %.*s\r\n"\r
+      "Content-Length: %" INT64_FMT "\r\n"\r
+      "Connection: %s\r\n"\r
+      "Accept-Ranges: bytes\r\n"\r
+      "%s\r\n",\r
+      conn->request_info.status_code, msg, date, lm, etag,\r
+      mime_vec.len, mime_vec.ptr, cl, suggest_connection_header(conn), range);\r
+\r
+  if (strcmp(conn->request_info.request_method, "HEAD") != 0) {\r
+    send_file_data(conn, fp, cl);\r
+  }\r
+  (void) fclose(fp);\r
+}\r
+\r
+// Parse HTTP headers from the given buffer, advance buffer to the point\r
+// where parsing stopped.\r
+static void parse_http_headers(char **buf, struct mg_request_info *ri) {\r
+  int i;\r
+\r
+  for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {\r
+    ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);\r
+    ri->http_headers[i].value = skip(buf, "\r\n");\r
+    if (ri->http_headers[i].name[0] == '\0')\r
+      break;\r
+    ri->num_headers = i + 1;\r
+  }\r
+}\r
+\r
+static int is_valid_http_method(const char *method) {\r
+  return !strcmp(method, "GET") || !strcmp(method, "POST") ||\r
+    !strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||\r
+    !strcmp(method, "PUT") || !strcmp(method, "DELETE");\r
+}\r
+\r
+// Parse HTTP request, fill in mg_request_info structure.\r
+static int parse_http_request(char *buf, struct mg_request_info *ri) {\r
+  int status = 0;\r
+\r
+  // RFC says that all initial whitespaces should be ingored\r
+  while (*buf != '\0' && isspace(* (unsigned char *) buf)) {\r
+    buf++;\r
+  }\r
+\r
+  ri->request_method = skip(&buf, " ");\r
+  ri->uri = skip(&buf, " ");\r
+  ri->http_version = skip(&buf, "\r\n");\r
+\r
+  if (is_valid_http_method(ri->request_method) &&\r
+      strncmp(ri->http_version, "HTTP/", 5) == 0) {\r
+    ri->http_version += 5;   /* Skip "HTTP/" */\r
+    parse_http_headers(&buf, ri);\r
+    status = 1;\r
+  }\r
+\r
+  return status;\r
+}\r
+\r
+// Keep reading the input (either opened file descriptor fd, or socket sock,\r
+// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the\r
+// buffer (which marks the end of HTTP request). Buffer buf may already\r
+// have some data. The length of the data is stored in nread.\r
+// Upon every read operation, increase nread by the number of bytes read.\r
+static int read_request(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz,\r
+                        int *nread) {\r
+  int n, request_len;\r
+\r
+  request_len = 0;\r
+  while (*nread < bufsiz && request_len == 0) {\r
+    n = pull(fp, sock, ssl, buf + *nread, bufsiz - *nread);\r
+    if (n <= 0) {\r
+      break;\r
+    } else {\r
+      *nread += n;\r
+      request_len = get_request_len(buf, *nread);\r
+    }\r
+  }\r
+\r
+  return request_len;\r
+}\r
+\r
+// For given directory path, substitute it to valid index file.\r
+// Return 0 if index file has been found, -1 if not found.\r
+// If the file is found, it's stats is returned in stp.\r
+static int substitute_index_file(struct mg_connection *conn, char *path,\r
+                                 size_t path_len, struct mgstat *stp) {\r
+  const char *list = conn->ctx->config[INDEX_FILES];\r
+  struct mgstat st;\r
+  struct vec filename_vec;\r
+  size_t n = strlen(path);\r
+  int found = 0;\r
+\r
+  // The 'path' given to us points to the directory. Remove all trailing\r
+  // directory separator characters from the end of the path, and\r
+  // then append single directory separator character.\r
+  while (n > 0 && IS_DIRSEP_CHAR(path[n - 1])) {\r
+    n--;\r
+  }\r
+  path[n] = DIRSEP;\r
+\r
+  // Traverse index files list. For each entry, append it to the given\r
+  // path and see if the file exists. If it exists, break the loop\r
+  while ((list = next_option(list, &filename_vec, NULL)) != NULL) {\r
+\r
+    // Ignore too long entries that may overflow path buffer\r
+    if (filename_vec.len > path_len - n)\r
+      continue;\r
+\r
+    // Prepare full path to the index file\r
+    (void) mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1);\r
+\r
+    // Does it exist?\r
+    if (mg_stat(path, &st) == 0) {\r
+      // Yes it does, break the loop\r
+      *stp = st;\r
+      found = 1;\r
+      break;\r
+    }\r
+  }\r
+\r
+  // If no index file exists, restore directory path\r
+  if (!found) {\r
+    path[n] = '\0';\r
+  }\r
+\r
+  return found;\r
+}\r
+\r
+// Return True if we should reply 304 Not Modified.\r
+static int is_not_modified(const struct mg_connection *conn,\r
+                           const struct mgstat *stp) {\r
+  const char *ims = mg_get_header(conn, "If-Modified-Since");\r
+  return ims != NULL && stp->mtime <= parse_date_string(ims);\r
+}\r
+\r
+static int forward_body_data(struct mg_connection *conn, FILE *fp,\r
+                             SOCKET sock, SSL *ssl) {\r
+  const char *expect, *buffered;\r
+  char buf[BUFSIZ];\r
+  int to_read, nread, buffered_len, success = 0;\r
+\r
+  expect = mg_get_header(conn, "Expect");\r
+  assert(fp != NULL);\r
+\r
+  if (conn->content_len == -1) {\r
+    send_http_error(conn, 411, "Length Required", "");\r
+  } else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {\r
+    send_http_error(conn, 417, "Expectation Failed", "");\r
+  } else {\r
+    if (expect != NULL) {\r
+      (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n");\r
+    }\r
+\r
+    buffered = conn->buf + conn->request_len;\r
+    buffered_len = conn->data_len - conn->request_len;\r
+    assert(buffered_len >= 0);\r
+    assert(conn->consumed_content == 0);\r
+\r
+    if (buffered_len > 0) {\r
+      if ((int64_t) buffered_len > conn->content_len) {\r
+        buffered_len = (int) conn->content_len;\r
+      }\r
+      push(fp, sock, ssl, buffered, (int64_t) buffered_len);\r
+      conn->consumed_content += buffered_len;\r
+    }\r
+\r
+    while (conn->consumed_content < conn->content_len) {\r
+      to_read = sizeof(buf);\r
+      if ((int64_t) to_read > conn->content_len - conn->consumed_content) {\r
+        to_read = (int) (conn->content_len - conn->consumed_content);\r
+      }\r
+      nread = pull(NULL, conn->client.sock, conn->ssl, buf, to_read);\r
+      if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {\r
+        break;\r
+      }\r
+      conn->consumed_content += nread;\r
+    }\r
+\r
+    if (conn->consumed_content == conn->content_len) {\r
+      success = 1;\r
+    }\r
+\r
+    // Each error code path in this function must send an error\r
+    if (!success) {\r
+      send_http_error(conn, 577, http_500_error, "");\r
+    }\r
+  }\r
+\r
+  return success;\r
+}\r
+\r
+#if !defined(NO_CGI)\r
+// This structure helps to create an environment for the spawned CGI program.\r
+// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,\r
+// last element must be NULL.\r
+// However, on Windows there is a requirement that all these VARIABLE=VALUE\0\r
+// strings must reside in a contiguous buffer. The end of the buffer is\r
+// marked by two '\0' characters.\r
+// We satisfy both worlds: we create an envp array (which is vars), all\r
+// entries are actually pointers inside buf.\r
+struct cgi_env_block {\r
+  struct mg_connection *conn;\r
+  char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer\r
+  int len; // Space taken\r
+  char *vars[MAX_CGI_ENVIR_VARS]; // char **envp\r
+  int nvars; // Number of variables\r
+};\r
+\r
+// Append VARIABLE=VALUE\0 string to the buffer, and add a respective\r
+// pointer into the vars array.\r
+static char *addenv(struct cgi_env_block *block, const char *fmt, ...) {\r
+  int n, space;\r
+  char *added;\r
+  va_list ap;\r
+\r
+  // Calculate how much space is left in the buffer\r
+  space = sizeof(block->buf) - block->len - 2;\r
+  assert(space >= 0);\r
+\r
+  // Make a pointer to the free space int the buffer\r
+  added = block->buf + block->len;\r
+\r
+  // Copy VARIABLE=VALUE\0 string into the free space\r
+  va_start(ap, fmt);\r
+  n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap);\r
+  va_end(ap);\r
+\r
+  // Make sure we do not overflow buffer and the envp array\r
+  if (n > 0 && n < space &&\r
+      block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {\r
+    // Append a pointer to the added string into the envp array\r
+    block->vars[block->nvars++] = block->buf + block->len;\r
+    // Bump up used length counter. Include \0 terminator\r
+    block->len += n + 1;\r
+  }\r
+\r
+  return added;\r
+}\r
+\r
+static void prepare_cgi_environment(struct mg_connection *conn,\r
+                                    const char *prog,\r
+                                    struct cgi_env_block *blk) {\r
+  const char *s, *slash;\r
+  struct vec var_vec, root;\r
+  char *p;\r
+  int  i;\r
+\r
+  blk->len = blk->nvars = 0;\r
+  blk->conn = conn;\r
+\r
+  get_document_root(conn, &root);\r
+\r
+  addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);\r
+  addenv(blk, "SERVER_ROOT=%.*s", root.len, root.ptr);\r
+  addenv(blk, "DOCUMENT_ROOT=%.*s", root.len, root.ptr);\r
+\r
+  // Prepare the environment block\r
+  addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");\r
+  addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");\r
+  addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP\r
+  addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.u.sin.sin_port));\r
+  addenv(blk, "REQUEST_METHOD=%s", conn->request_info.request_method);\r
+  addenv(blk, "REMOTE_ADDR=%s",\r
+      inet_ntoa(conn->client.rsa.u.sin.sin_addr));\r
+  addenv(blk, "REMOTE_PORT=%d", conn->request_info.remote_port);\r
+  addenv(blk, "REQUEST_URI=%s", conn->request_info.uri);\r
+\r
+  // SCRIPT_NAME\r
+  assert(conn->request_info.uri[0] == '/');\r
+  slash = strrchr(conn->request_info.uri, '/');\r
+  if ((s = strrchr(prog, '/')) == NULL)\r
+    s = prog;\r
+  addenv(blk, "SCRIPT_NAME=%.*s%s", slash - conn->request_info.uri,\r
+         conn->request_info.uri, s);\r
+\r
+  addenv(blk, "SCRIPT_FILENAME=%s", prog);\r
+  addenv(blk, "PATH_TRANSLATED=%s", prog);\r
+  addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on");\r
+\r
+  if ((s = mg_get_header(conn, "Content-Type")) != NULL)\r
+    addenv(blk, "CONTENT_TYPE=%s", s);\r
+\r
+  if (conn->request_info.query_string != NULL)\r
+    addenv(blk, "QUERY_STRING=%s", conn->request_info.query_string);\r
+\r
+  if ((s = mg_get_header(conn, "Content-Length")) != NULL)\r
+    addenv(blk, "CONTENT_LENGTH=%s", s);\r
+\r
+  if ((s = getenv("PATH")) != NULL)\r
+    addenv(blk, "PATH=%s", s);\r
+\r
+#if defined(_WIN32)\r
+  if ((s = getenv("COMSPEC")) != NULL)\r
+    addenv(blk, "COMSPEC=%s", s);\r
+  if ((s = getenv("SYSTEMROOT")) != NULL)\r
+    addenv(blk, "SYSTEMROOT=%s", s);\r
+#else\r
+  if ((s = getenv("LD_LIBRARY_PATH")) != NULL)\r
+    addenv(blk, "LD_LIBRARY_PATH=%s", s);\r
+#endif /* _WIN32 */\r
+\r
+  if ((s = getenv("PERLLIB")) != NULL)\r
+    addenv(blk, "PERLLIB=%s", s);\r
+\r
+  if (conn->request_info.remote_user != NULL) {\r
+    addenv(blk, "REMOTE_USER=%s", conn->request_info.remote_user);\r
+    addenv(blk, "%s", "AUTH_TYPE=Digest");\r
+  }\r
+\r
+  // Add all headers as HTTP_* variables\r
+  for (i = 0; i < conn->request_info.num_headers; i++) {\r
+    p = addenv(blk, "HTTP_%s=%s",\r
+        conn->request_info.http_headers[i].name,\r
+        conn->request_info.http_headers[i].value);\r
+\r
+    // Convert variable name into uppercase, and change - to _\r
+    for (; *p != '=' && *p != '\0'; p++) {\r
+      if (*p == '-')\r
+        *p = '_';\r
+      *p = (char) toupper(* (unsigned char *) p);\r
+    }\r
+  }\r
+\r
+  // Add user-specified variables\r
+  s = conn->ctx->config[CGI_ENVIRONMENT];\r
+  while ((s = next_option(s, &var_vec, NULL)) != NULL) {\r
+    addenv(blk, "%.*s", var_vec.len, var_vec.ptr);\r
+  }\r
+\r
+  blk->vars[blk->nvars++] = NULL;\r
+  blk->buf[blk->len++] = '\0';\r
+\r
+  assert(blk->nvars < (int) ARRAY_SIZE(blk->vars));\r
+  assert(blk->len > 0);\r
+  assert(blk->len < (int) sizeof(blk->buf));\r
+}\r
+\r
+static void handle_cgi_request(struct mg_connection *conn, const char *prog) {\r
+  int headers_len, data_len, i, fd_stdin[2], fd_stdout[2];\r
+  const char *status;\r
+  char buf[BUFSIZ], *pbuf, dir[PATH_MAX], *p;\r
+  struct mg_request_info ri;\r
+  struct cgi_env_block blk;\r
+  FILE *in, *out;\r
+  pid_t pid;\r
+\r
+  prepare_cgi_environment(conn, prog, &blk);\r
+\r
+  // CGI must be executed in its own directory. 'dir' must point to the\r
+  // directory containing executable program, 'p' must point to the\r
+  // executable program name relative to 'dir'.\r
+  (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog);\r
+  if ((p = strrchr(dir, DIRSEP)) != NULL) {\r
+    *p++ = '\0';\r
+  } else {\r
+    dir[0] = '.', dir[1] = '\0';\r
+    p = (char *) prog;\r
+  }\r
+\r
+  pid = (pid_t) -1;\r
+  fd_stdin[0] = fd_stdin[1] = fd_stdout[0] = fd_stdout[1] = -1;\r
+  in = out = NULL;\r
+\r
+  if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) {\r
+    send_http_error(conn, 500, http_500_error,\r
+        "Cannot create CGI pipe: %s", strerror(ERRNO));\r
+    goto done;\r
+  } else if ((pid = spawn_process(conn, p, blk.buf, blk.vars,\r
+          fd_stdin[0], fd_stdout[1], dir)) == (pid_t) -1) {\r
+    goto done;\r
+  } else if ((in = fdopen(fd_stdin[1], "wb")) == NULL ||\r
+      (out = fdopen(fd_stdout[0], "rb")) == NULL) {\r
+    send_http_error(conn, 500, http_500_error,\r
+        "fopen: %s", strerror(ERRNO));\r
+    goto done;\r
+  }\r
+\r
+  setbuf(in, NULL);\r
+  setbuf(out, NULL);\r
+\r
+  // spawn_process() must close those!\r
+  // If we don't mark them as closed, close() attempt before\r
+  // return from this function throws an exception on Windows.\r
+  // Windows does not like when closed descriptor is closed again.\r
+  fd_stdin[0] = fd_stdout[1] = -1;\r
+\r
+  // Send POST data to the CGI process if needed\r
+  if (!strcmp(conn->request_info.request_method, "POST") &&\r
+      !forward_body_data(conn, in, INVALID_SOCKET, NULL)) {\r
+    goto done;\r
+  }\r
+\r
+  // Now read CGI reply into a buffer. We need to set correct\r
+  // status code, thus we need to see all HTTP headers first.\r
+  // Do not send anything back to client, until we buffer in all\r
+  // HTTP headers.\r
+  data_len = 0;\r
+  headers_len = read_request(out, INVALID_SOCKET, NULL,\r
+      buf, sizeof(buf), &data_len);\r
+  if (headers_len <= 0) {\r
+    send_http_error(conn, 500, http_500_error,\r
+                    "CGI program sent malformed HTTP headers: [%.*s]",\r
+                    data_len, buf);\r
+    goto done;\r
+  }\r
+  pbuf = buf;\r
+  buf[headers_len - 1] = '\0';\r
+  parse_http_headers(&pbuf, &ri);\r
+\r
+  // Make up and send the status line\r
+  status = get_header(&ri, "Status");\r
+  conn->request_info.status_code = status == NULL ? 200 : atoi(status);\r
+  (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n", conn->request_info.status_code);\r
+\r
+  // Send headers\r
+  for (i = 0; i < ri.num_headers; i++) {\r
+    mg_printf(conn, "%s: %s\r\n",\r
+              ri.http_headers[i].name, ri.http_headers[i].value);\r
+  }\r
+  (void) mg_write(conn, "\r\n", 2);\r
+\r
+  // Send chunk of data that may be read after the headers\r
+  conn->num_bytes_sent += mg_write(conn, buf + headers_len,\r
+                                   (size_t)(data_len - headers_len));\r
+\r
+  // Read the rest of CGI output and send to the client\r
+  send_file_data(conn, out, INT64_MAX);\r
+\r
+done:\r
+  if (pid != (pid_t) -1) {\r
+    kill(pid, SIGKILL);\r
+#if !defined(_WIN32)\r
+    do {} while (waitpid(-1, &i, WNOHANG) > 0);\r
+#endif\r
+  }\r
+  if (fd_stdin[0] != -1) {\r
+    (void) close(fd_stdin[0]);\r
+  }\r
+  if (fd_stdout[1] != -1) {\r
+    (void) close(fd_stdout[1]);\r
+  }\r
+\r
+  if (in != NULL) {\r
+    (void) fclose(in);\r
+  } else if (fd_stdin[1] != -1) {\r
+    (void) close(fd_stdin[1]);\r
+  }\r
+\r
+  if (out != NULL) {\r
+    (void) fclose(out);\r
+  } else if (fd_stdout[0] != -1) {\r
+    (void) close(fd_stdout[0]);\r
+  }\r
+}\r
+#endif // !NO_CGI\r
+\r
+// For a given PUT path, create all intermediate subdirectories\r
+// for given path. Return 0 if the path itself is a directory,\r
+// or -1 on error, 1 if OK.\r
+static int put_dir(const char *path) {\r
+  char buf[PATH_MAX];\r
+  const char *s, *p;\r
+  struct mgstat st;\r
+  size_t len;\r
+\r
+  for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {\r
+    len = p - path;\r
+    assert(len < sizeof(buf));\r
+    (void) memcpy(buf, path, len);\r
+    buf[len] = '\0';\r
+\r
+    // Try to create intermediate directory\r
+    if (mg_stat(buf, &st) == -1 && mg_mkdir(buf, 0755) != 0) {\r
+      return -1;\r
+    }\r
+\r
+    // Is path itself a directory?\r
+    if (p[1] == '\0') {\r
+      return 0;\r
+    }\r
+  }\r
+\r
+  return 1;\r
+}\r
+\r
+static void put_file(struct mg_connection *conn, const char *path) {\r
+  struct mgstat st;\r
+  const char *range;\r
+  int64_t r1, r2;\r
+  FILE *fp;\r
+  int rc;\r
+\r
+  conn->request_info.status_code = mg_stat(path, &st) == 0 ? 200 : 201;\r
+\r
+  if ((rc = put_dir(path)) == 0) {\r
+    mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->request_info.status_code);\r
+  } else if (rc == -1) {\r
+    send_http_error(conn, 500, http_500_error,\r
+        "put_dir(%s): %s", path, strerror(ERRNO));\r
+  } else if ((fp = mg_fopen(path, "wb+")) == NULL) {\r
+    send_http_error(conn, 500, http_500_error,\r
+        "fopen(%s): %s", path, strerror(ERRNO));\r
+  } else {\r
+    set_close_on_exec(fileno(fp));\r
+    range = mg_get_header(conn, "Content-Range");\r
+    r1 = r2 = 0;\r
+    if (range != NULL && parse_range_header(range, &r1, &r2) > 0) {\r
+      conn->request_info.status_code = 206;\r
+      // TODO(lsm): handle seek error\r
+      (void) fseeko(fp, (off_t) r1, SEEK_SET);\r
+    }\r
+    if (forward_body_data(conn, fp, INVALID_SOCKET, NULL))\r
+      (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n",\r
+          conn->request_info.status_code);\r
+    (void) fclose(fp);\r
+  }\r
+}\r
+\r
+static void send_ssi_file(struct mg_connection *, const char *, FILE *, int);\r
+\r
+static void do_ssi_include(struct mg_connection *conn, const char *ssi,\r
+                           char *tag, int include_level) {\r
+  char file_name[BUFSIZ], path[PATH_MAX], *p;\r
+  struct vec root;\r
+  int is_ssi;\r
+  FILE *fp;\r
+\r
+  get_document_root(conn, &root);\r
+\r
+  // sscanf() is safe here, since send_ssi_file() also uses buffer\r
+  // of size BUFSIZ to get the tag. So strlen(tag) is always < BUFSIZ.\r
+  if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {\r
+    // File name is relative to the webserver root\r
+    (void) mg_snprintf(conn, path, sizeof(path), "%.*s%c%s",\r
+        root.len, root.ptr, DIRSEP, file_name);\r
+  } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1) {\r
+    // File name is relative to the webserver working directory\r
+    // or it is absolute system path\r
+    (void) mg_snprintf(conn, path, sizeof(path), "%s", file_name);\r
+  } else if (sscanf(tag, " \"%[^\"]\"", file_name) == 1) {\r
+    // File name is relative to the currect document\r
+    (void) mg_snprintf(conn, path, sizeof(path), "%s", ssi);\r
+    if ((p = strrchr(path, DIRSEP)) != NULL) {\r
+      p[1] = '\0';\r
+    }\r
+    (void) mg_snprintf(conn, path + strlen(path),\r
+        sizeof(path) - strlen(path), "%s", file_name);\r
+  } else {\r
+    cry(conn, "Bad SSI #include: [%s]", tag);\r
+    return;\r
+  }\r
+\r
+  if ((fp = mg_fopen(path, "rb")) == NULL) {\r
+    cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",\r
+        tag, path, strerror(ERRNO));\r
+  } else {\r
+    set_close_on_exec(fileno(fp));\r
+    is_ssi = match_extension(path, conn->ctx->config[SSI_EXTENSIONS]);\r
+    if (is_ssi) {\r
+      send_ssi_file(conn, path, fp, include_level + 1);\r
+    } else {\r
+      send_file_data(conn, fp, INT64_MAX);\r
+    }\r
+    (void) fclose(fp);\r
+  }\r
+}\r
+\r
+#if !defined(NO_POPEN)\r
+static void do_ssi_exec(struct mg_connection *conn, char *tag) {\r
+  char cmd[BUFSIZ];\r
+  FILE *fp;\r
+\r
+  if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {\r
+    cry(conn, "Bad SSI #exec: [%s]", tag);\r
+  } else if ((fp = popen(cmd, "r")) == NULL) {\r
+    cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO));\r
+  } else {\r
+    send_file_data(conn, fp, INT64_MAX);\r
+    (void) pclose(fp);\r
+  }\r
+}\r
+#endif // !NO_POPEN\r
+\r
+static void send_ssi_file(struct mg_connection *conn, const char *path,\r
+                          FILE *fp, int include_level) {\r
+  char buf[BUFSIZ];\r
+  int ch, len, in_ssi_tag;\r
+\r
+  if (include_level > 10) {\r
+    cry(conn, "SSI #include level is too deep (%s)", path);\r
+    return;\r
+  }\r
+\r
+  in_ssi_tag = 0;\r
+  len = 0;\r
+\r
+  while ((ch = fgetc(fp)) != EOF) {\r
+    if (in_ssi_tag && ch == '>') {\r
+      in_ssi_tag = 0;\r
+      buf[len++] = (char) ch;\r
+      buf[len] = '\0';\r
+      assert(len <= (int) sizeof(buf));\r
+      if (len < 6 || memcmp(buf, "<!--#", 5) != 0) {\r
+        // Not an SSI tag, pass it\r
+        (void) mg_write(conn, buf, (size_t)len);\r
+      } else {\r
+        if (!memcmp(buf + 5, "include", 7)) {\r
+          do_ssi_include(conn, path, buf + 12, include_level);\r
+#if !defined(NO_POPEN)\r
+        } else if (!memcmp(buf + 5, "exec", 4)) {\r
+          do_ssi_exec(conn, buf + 9);\r
+#endif // !NO_POPEN\r
+        } else {\r
+          cry(conn, "%s: unknown SSI " "command: \"%s\"", path, buf);\r
+        }\r
+      }\r
+      len = 0;\r
+    } else if (in_ssi_tag) {\r
+      if (len == 5 && memcmp(buf, "<!--#", 5) != 0) {\r
+        // Not an SSI tag\r
+        in_ssi_tag = 0;\r
+      } else if (len == (int) sizeof(buf) - 2) {\r
+        cry(conn, "%s: SSI tag is too large", path);\r
+        len = 0;\r
+      }\r
+      buf[len++] = ch & 0xff;\r
+    } else if (ch == '<') {\r
+      in_ssi_tag = 1;\r
+      if (len > 0) {\r
+        (void) mg_write(conn, buf, (size_t)len);\r
+      }\r
+      len = 0;\r
+      buf[len++] = ch & 0xff;\r
+    } else {\r
+      buf[len++] = ch & 0xff;\r
+      if (len == (int) sizeof(buf)) {\r
+        (void) mg_write(conn, buf, (size_t)len);\r
+        len = 0;\r
+      }\r
+    }\r
+  }\r
+\r
+  // Send the rest of buffered data\r
+  if (len > 0) {\r
+    (void) mg_write(conn, buf, (size_t)len);\r
+  }\r
+}\r
+\r
+static void handle_ssi_file_request(struct mg_connection *conn,\r
+                                    const char *path) {\r
+  FILE *fp;\r
+\r
+  if ((fp = mg_fopen(path, "rb")) == NULL) {\r
+    send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path,\r
+                    strerror(ERRNO));\r
+  } else {\r
+    set_close_on_exec(fileno(fp));\r
+    mg_printf(conn, "HTTP/1.1 200 OK\r\n"\r
+              "Content-Type: text/html\r\nConnection: %s\r\n\r\n",\r
+              suggest_connection_header(conn));\r
+    send_ssi_file(conn, path, fp, 0);\r
+    (void) fclose(fp);\r
+  }\r
+}\r
+\r
+// This is the heart of the Mongoose's logic.\r
+// This function is called when the request is read, parsed and validated,\r
+// and Mongoose must decide what action to take: serve a file, or\r
+// a directory, or call embedded function, etcetera.\r
+static void handle_request(struct mg_connection *conn) {\r
+  struct mg_request_info *ri = &conn->request_info;\r
+  char path[PATH_MAX];\r
+  int uri_len;\r
+  struct mgstat st;\r
+\r
+  if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {\r
+    * conn->request_info.query_string++ = '\0';\r
+  }\r
+  uri_len = strlen(ri->uri);\r
+  (void) url_decode(ri->uri, (size_t)uri_len, ri->uri, (size_t)(uri_len + 1), 0);\r
+  remove_double_dots_and_double_slashes(ri->uri);\r
+  convert_uri_to_file_name(conn, ri->uri, path, sizeof(path));\r
+\r
+  DEBUG_TRACE(("%s", ri->uri));\r
+  if (!check_authorization(conn, path)) {\r
+    send_authorization_request(conn);\r
+  } else if (call_user(conn, MG_NEW_REQUEST) != NULL) {\r
+    // Do nothing, callback has served the request\r
+  } else if (strstr(path, PASSWORDS_FILE_NAME)) {\r
+    // Do not allow to view passwords files\r
+    send_http_error(conn, 403, "Forbidden", "Access Forbidden");\r
+  } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {\r
+    send_http_error(conn, 404, "Not Found", "Not Found");\r
+  } else if ((!strcmp(ri->request_method, "PUT") ||\r
+        !strcmp(ri->request_method, "DELETE")) &&\r
+      (conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ||\r
+       !is_authorized_for_put(conn))) {\r
+    send_authorization_request(conn);\r
+  } else if (!strcmp(ri->request_method, "PUT")) {\r
+    put_file(conn, path);\r
+  } else if (!strcmp(ri->request_method, "DELETE")) {\r
+    if (mg_remove(path) == 0) {\r
+      send_http_error(conn, 200, "OK", "");\r
+    } else {\r
+      send_http_error(conn, 500, http_500_error, "remove(%s): %s", path,\r
+                      strerror(ERRNO));\r
+    }\r
+  } else if (mg_stat(path, &st) != 0) {\r
+    send_http_error(conn, 404, "Not Found", "%s", "File not found");\r
+  } else if (st.is_directory && ri->uri[uri_len - 1] != '/') {\r
+    (void) mg_printf(conn,\r
+        "HTTP/1.1 301 Moved Permanently\r\n"\r
+        "Location: %s/\r\n\r\n", ri->uri);\r
+  } else if (st.is_directory &&\r
+             !substitute_index_file(conn, path, sizeof(path), &st)) {\r
+    if (!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes")) {\r
+      handle_directory_request(conn, path);\r
+    } else {\r
+      send_http_error(conn, 403, "Directory Listing Denied",\r
+          "Directory listing denied");\r
+    }\r
+  } else if (match_extension(path, conn->ctx->config[CGI_EXTENSIONS])) {\r
+    if (strcmp(ri->request_method, "POST") &&\r
+        strcmp(ri->request_method, "GET")) {\r
+      send_http_error(conn, 501, "Not Implemented",\r
+          "Method %s is not implemented", ri->request_method);\r
+    } else {\r
+      handle_cgi_request(conn, path);\r
+    }\r
+  } else if (match_extension(path, conn->ctx->config[SSI_EXTENSIONS])) {\r
+    handle_ssi_file_request(conn, path);\r
+  } else if (is_not_modified(conn, &st)) {\r
+    send_http_error(conn, 304, "Not Modified", "");\r
+  } else {\r
+    handle_file_request(conn, path, &st);\r
+  }\r
+}\r
+\r
+static void close_all_listening_sockets(struct mg_context *ctx) {\r
+  struct socket *sp, *tmp;\r
+  for (sp = ctx->listening_sockets; sp != NULL; sp = tmp) {\r
+    tmp = sp->next;\r
+    (void) closesocket(sp->sock);\r
+    free(sp);\r
+  }\r
+}\r
+\r
+// Valid listening port specification is: [ip_address:]port[s|p]\r
+// Examples: 80, 443s, 127.0.0.1:3128p, 1.2.3.4:8080sp\r
+static int parse_port_string(const struct vec *vec, struct socket *so) {\r
+  struct usa *usa = &so->lsa;\r
+  int a, b, c, d, port, len;\r
+\r
+  // MacOS needs that. If we do not zero it, subsequent bind() will fail.\r
+  memset(so, 0, sizeof(*so));\r
+\r
+  if (sscanf(vec->ptr, "%d.%d.%d.%d:%d%n", &a, &b, &c, &d, &port, &len) == 5) {\r
+    // IP address to bind to is specified\r
+    usa->u.sin.sin_addr.s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d);\r
+  } else if (sscanf(vec->ptr, "%d%n", &port, &len) == 1) {\r
+    // Only port number is specified. Bind to all addresses\r
+    usa->u.sin.sin_addr.s_addr = htonl(INADDR_ANY);\r
+  } else {\r
+    return 0;\r
+  }\r
+  assert(len > 0 && len <= (int) vec->len);\r
+\r
+  if (strchr("sp,", vec->ptr[len]) == NULL) {\r
+    return 0;\r
+  }\r
+\r
+  so->is_ssl = vec->ptr[len] == 's';\r
+  so->is_proxy = vec->ptr[len] == 'p';\r
+  usa->len = sizeof(usa->u.sin);\r
+  usa->u.sin.sin_family = AF_INET;\r
+  usa->u.sin.sin_port = htons((uint16_t) port);\r
+\r
+  return 1;\r
+}\r
+\r
+static int set_ports_option(struct mg_context *ctx) {\r
+  const char *list = ctx->config[LISTENING_PORTS];\r
+  int reuseaddr = 1, success = 1;\r
+  SOCKET sock;\r
+  struct vec vec;\r
+  struct socket so, *listener;\r
+\r
+  while (success && (list = next_option(list, &vec, NULL)) != NULL) {\r
+    if (!parse_port_string(&vec, &so)) {\r
+      cry(fc(ctx), "%s: %.*s: invalid port spec. Expecting list of: %s",\r
+          __func__, vec.len, vec.ptr, "[IP_ADDRESS:]PORT[s|p]");\r
+      success = 0;\r
+    } else if (so.is_ssl && ctx->ssl_ctx == NULL) {\r
+      cry(fc(ctx), "Cannot add SSL socket, is -ssl_cert option set?");\r
+      success = 0;\r
+    } else if ((sock = socket(PF_INET, SOCK_STREAM, 6)) == INVALID_SOCKET ||\r
+#if !defined(_WIN32)\r
+               // On Windows, SO_REUSEADDR is recommended only for\r
+               // broadcast UDP sockets\r
+               setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,\r
+                          sizeof(reuseaddr)) != 0 ||\r
+#endif // !_WIN32\r
+               bind(sock, &so.lsa.u.sa, so.lsa.len) != 0 ||\r
+               listen(sock, 20) != 0) {\r
+      closesocket(sock);\r
+      cry(fc(ctx), "%s: cannot bind to %.*s: %s", __func__,\r
+          vec.len, vec.ptr, strerror(ERRNO));\r
+      success = 0;\r
+    } else if ((listener = (struct socket *)\r
+                calloc(1, sizeof(*listener))) == NULL) {\r
+      closesocket(sock);\r
+      cry(fc(ctx), "%s: %s", __func__, strerror(ERRNO));\r
+      success = 0;\r
+    } else {\r
+      *listener = so;\r
+      listener->sock = sock;\r
+      set_close_on_exec(listener->sock);\r
+      listener->next = ctx->listening_sockets;\r
+      ctx->listening_sockets = listener;\r
+    }\r
+  }\r
+\r
+  if (!success) {\r
+    close_all_listening_sockets(ctx);\r
+  }\r
+\r
+  return success;\r
+}\r
+\r
+static void log_header(const struct mg_connection *conn, const char *header,\r
+                       FILE *fp) {\r
+  const char *header_value;\r
+\r
+  if ((header_value = mg_get_header(conn, header)) == NULL) {\r
+    (void) fprintf(fp, "%s", " -");\r
+  } else {\r
+    (void) fprintf(fp, " \"%s\"", header_value);\r
+  }\r
+}\r
+\r
+static void log_access(const struct mg_connection *conn) {\r
+  const struct mg_request_info *ri;\r
+  FILE *fp;\r
+  char date[64];\r
+\r
+  fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ?  NULL :\r
+    mg_fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+");\r
+\r
+  if (fp == NULL)\r
+    return;\r
+\r
+  (void) strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z",\r
+      localtime(&conn->birth_time));\r
+\r
+  ri = &conn->request_info;\r
+\r
+  flockfile(fp);\r
+\r
+  (void) fprintf(fp,\r
+      "%s - %s [%s] \"%s %s HTTP/%s\" %d %" INT64_FMT,\r
+      inet_ntoa(conn->client.rsa.u.sin.sin_addr),\r
+      ri->remote_user == NULL ? "-" : ri->remote_user,\r
+      date,\r
+      ri->request_method ? ri->request_method : "-",\r
+      ri->uri ? ri->uri : "-",\r
+      ri->http_version,\r
+      conn->request_info.status_code, conn->num_bytes_sent);\r
+  log_header(conn, "Referer", fp);\r
+  log_header(conn, "User-Agent", fp);\r
+  (void) fputc('\n', fp);\r
+  (void) fflush(fp);\r
+\r
+  funlockfile(fp);\r
+  (void) fclose(fp);\r
+}\r
+\r
+static int isbyte(int n) {\r
+  return n >= 0 && n <= 255;\r
+}\r
+\r
+// Verify given socket address against the ACL.\r
+// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.\r
+static int check_acl(struct mg_context *ctx, const struct usa *usa) {\r
+  int a, b, c, d, n, mask, allowed;\r
+  char flag;\r
+  uint32_t acl_subnet, acl_mask, remote_ip;\r
+  struct vec vec;\r
+  const char *list = ctx->config[ACCESS_CONTROL_LIST];\r
+\r
+  if (list == NULL) {\r
+    return 1;\r
+  }\r
+\r
+  (void) memcpy(&remote_ip, &usa->u.sin.sin_addr, sizeof(remote_ip));\r
+\r
+  // If any ACL is set, deny by default\r
+  allowed = '-';\r
+\r
+  while ((list = next_option(list, &vec, NULL)) != NULL) {\r
+    mask = 32;\r
+\r
+    if (sscanf(vec.ptr, "%c%d.%d.%d.%d%n", &flag, &a, &b, &c, &d, &n) != 5) {\r
+      cry(fc(ctx), "%s: subnet must be [+|-]x.x.x.x[/x]", __func__);\r
+      return -1;\r
+    } else if (flag != '+' && flag != '-') {\r
+      cry(fc(ctx), "%s: flag must be + or -: [%s]", __func__, vec.ptr);\r
+      return -1;\r
+    } else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) {\r
+      cry(fc(ctx), "%s: bad ip address: [%s]", __func__, vec.ptr);\r
+      return -1;\r
+    } else if (sscanf(vec.ptr + n, "/%d", &mask) == 0) {\r
+      // Do nothing, no mask specified\r
+    } else if (mask < 0 || mask > 32) {\r
+      cry(fc(ctx), "%s: bad subnet mask: %d [%s]", __func__, n, vec.ptr);\r
+      return -1;\r
+    }\r
+\r
+    acl_subnet = (a << 24) | (b << 16) | (c << 8) | d;\r
+    acl_mask = mask ? 0xffffffffU << (32 - mask) : 0;\r
+\r
+    if (acl_subnet == (ntohl(remote_ip) & acl_mask)) {\r
+      allowed = flag;\r
+    }\r
+  }\r
+\r
+  return allowed == '+';\r
+}\r
+\r
+static void add_to_set(SOCKET fd, fd_set *set, int *max_fd) {\r
+  FD_SET(fd, set);\r
+  if (fd > (SOCKET) *max_fd) {\r
+    *max_fd = (int) fd;\r
+  }\r
+}\r
+\r
+#if !defined(_WIN32)\r
+static int set_uid_option(struct mg_context *ctx) {\r
+  struct passwd *pw;\r
+  const char *uid = ctx->config[RUN_AS_USER];\r
+  int success = 0;\r
+\r
+  if (uid == NULL) {\r
+    success = 1;\r
+  } else {\r
+    if ((pw = getpwnam(uid)) == NULL) {\r
+      cry(fc(ctx), "%s: unknown user [%s]", __func__, uid);\r
+    } else if (setgid(pw->pw_gid) == -1) {\r
+      cry(fc(ctx), "%s: setgid(%s): %s", __func__, uid, strerror(errno));\r
+    } else if (setuid(pw->pw_uid) == -1) {\r
+      cry(fc(ctx), "%s: setuid(%s): %s", __func__, uid, strerror(errno));\r
+    } else {\r
+      success = 1;\r
+    }\r
+  }\r
+\r
+  return success;\r
+}\r
+#endif // !_WIN32\r
+\r
+#if !defined(NO_SSL)\r
+static pthread_mutex_t *ssl_mutexes;\r
+\r
+static void ssl_locking_callback(int mode, int mutex_num, const char *file,\r
+                                 int line) {\r
+  line = 0;    // Unused\r
+  file = NULL; // Unused\r
+\r
+  if (mode & CRYPTO_LOCK) {\r
+    (void) pthread_mutex_lock(&ssl_mutexes[mutex_num]);\r
+  } else {\r
+    (void) pthread_mutex_unlock(&ssl_mutexes[mutex_num]);\r
+  }\r
+}\r
+\r
+static unsigned long ssl_id_callback(void) {\r
+  return (unsigned long) pthread_self();\r
+}\r
+\r
+#if !defined(NO_SSL_DL)\r
+static int load_dll(struct mg_context *ctx, const char *dll_name,\r
+                    struct ssl_func *sw) {\r
+  union {void *p; void (*fp)(void);} u;\r
+  void  *dll_handle;\r
+  struct ssl_func *fp;\r
+\r
+  if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) {\r
+    cry(fc(ctx), "%s: cannot load %s", __func__, dll_name);\r
+    return 0;\r
+  }\r
+\r
+  for (fp = sw; fp->name != NULL; fp++) {\r
+#ifdef _WIN32\r
+    // GetProcAddress() returns pointer to function\r
+    u.fp = (void (*)(void)) dlsym(dll_handle, fp->name);\r
+#else\r
+    // dlsym() on UNIX returns void *. ISO C forbids casts of data pointers to\r
+    // function pointers. We need to use a union to make a cast.\r
+    u.p = dlsym(dll_handle, fp->name);\r
+#endif /* _WIN32 */\r
+    if (u.fp == NULL) {\r
+      cry(fc(ctx), "%s: %s: cannot find %s", __func__, dll_name, fp->name);\r
+      return 0;\r
+    } else {\r
+      fp->ptr = u.fp;\r
+    }\r
+  }\r
+\r
+  return 1;\r
+}\r
+#endif // NO_SSL_DL\r
+\r
+// Dynamically load SSL library. Set up ctx->ssl_ctx pointer.\r
+static int set_ssl_option(struct mg_context *ctx) {\r
+  struct mg_request_info request_info;\r
+  SSL_CTX *CTX;\r
+  int i, size;\r
+  const char *pem = ctx->config[SSL_CERTIFICATE];\r
+  const char *chain = ctx->config[SSL_CHAIN_FILE];\r
+\r
+  if (pem == NULL) {\r
+    return 1;\r
+  }\r
+\r
+#if !defined(NO_SSL_DL)\r
+  if (!load_dll(ctx, SSL_LIB, ssl_sw) ||\r
+      !load_dll(ctx, CRYPTO_LIB, crypto_sw)) {\r
+    return 0;\r
+  }\r
+#endif // NO_SSL_DL\r
+\r
+  // Initialize SSL crap\r
+  SSL_library_init();\r
+  SSL_load_error_strings();\r
+\r
+  if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL) {\r
+    cry(fc(ctx), "SSL_CTX_new error: %s", ssl_error());\r
+  } else if (ctx->user_callback != NULL) {\r
+    memset(&request_info, 0, sizeof(request_info));\r
+    request_info.user_data = ctx->user_data;\r
+    ctx->user_callback(MG_INIT_SSL, (struct mg_connection *) CTX,\r
+                       &request_info);\r
+  }\r
+\r
+  if (CTX != NULL && SSL_CTX_use_certificate_file(CTX, pem,\r
+        SSL_FILETYPE_PEM) == 0) {\r
+    cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());\r
+    return 0;\r
+  } else if (CTX != NULL && SSL_CTX_use_PrivateKey_file(CTX, pem,\r
+        SSL_FILETYPE_PEM) == 0) {\r
+    cry(fc(ctx), "%s: cannot open %s: %s", NULL, pem, ssl_error());\r
+    return 0;\r
+  }\r
+\r
+  if (CTX != NULL && chain != NULL &&\r
+      SSL_CTX_use_certificate_chain_file(CTX, chain) == 0) {\r
+    cry(fc(ctx), "%s: cannot open %s: %s", NULL, chain, ssl_error());\r
+    return 0;\r
+  }\r
+\r
+  // Initialize locking callbacks, needed for thread safety.\r
+  // http://www.openssl.org/support/faq.html#PROG1\r
+  size = sizeof(pthread_mutex_t) * CRYPTO_num_locks();\r
+  if ((ssl_mutexes = (pthread_mutex_t *) malloc((size_t)size)) == NULL) {\r
+    cry(fc(ctx), "%s: cannot allocate mutexes: %s", __func__, ssl_error());\r
+    return 0;\r
+  }\r
+\r
+  for (i = 0; i < CRYPTO_num_locks(); i++) {\r
+    pthread_mutex_init(&ssl_mutexes[i], NULL);\r
+  }\r
+\r
+  CRYPTO_set_locking_callback(&ssl_locking_callback);\r
+  CRYPTO_set_id_callback(&ssl_id_callback);\r
+\r
+  // Done with everything. Save the context.\r
+  ctx->ssl_ctx = CTX;\r
+\r
+  return 1;\r
+}\r
+#endif // !NO_SSL\r
+\r
+static int set_gpass_option(struct mg_context *ctx) {\r
+  struct mgstat mgstat;\r
+  const char *path = ctx->config[GLOBAL_PASSWORDS_FILE];\r
+  return path == NULL || mg_stat(path, &mgstat) == 0;\r
+}\r
+\r
+static int set_acl_option(struct mg_context *ctx) {\r
+  struct usa fake;\r
+  return check_acl(ctx, &fake) != -1;\r
+}\r
+\r
+static void reset_per_request_attributes(struct mg_connection *conn) {\r
+  struct mg_request_info *ri = &conn->request_info;\r
+\r
+  // Reset request info attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port\r
+  if (ri->remote_user != NULL) {\r
+    free((void *) ri->remote_user);\r
+  }\r
+  ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;\r
+  ri->num_headers = 0;\r
+  ri->status_code = -1;\r
+\r
+  conn->num_bytes_sent = conn->consumed_content = 0;\r
+  conn->content_len = -1;\r
+  conn->request_len = conn->data_len = 0;\r
+}\r
+\r
+static void close_socket_gracefully(SOCKET sock) {\r
+  char buf[BUFSIZ];\r
+  int n;\r
+\r
+  // Send FIN to the client\r
+  (void) shutdown(sock, SHUT_WR);\r
+  set_non_blocking_mode(sock);\r
+\r
+  // Read and discard pending data. If we do not do that and close the\r
+  // socket, the data in the send buffer may be discarded. This\r
+  // behaviour is seen on Windows, when client keeps sending data\r
+  // when server decide to close the connection; then when client\r
+  // does recv() it gets no data back.\r
+  do {\r
+    n = pull(NULL, sock, NULL, buf, sizeof(buf));\r
+  } while (n > 0);\r
+\r
+  // Now we know that our FIN is ACK-ed, safe to close\r
+  (void) closesocket(sock);\r
+}\r
+\r
+static void close_connection(struct mg_connection *conn) {\r
+  if (conn->ssl) {\r
+    SSL_free(conn->ssl);\r
+    conn->ssl = NULL;\r
+  }\r
+\r
+  if (conn->client.sock != INVALID_SOCKET) {\r
+    close_socket_gracefully(conn->client.sock);\r
+  }\r
+}\r
+\r
+static void discard_current_request_from_buffer(struct mg_connection *conn) {\r
+  char *buffered;\r
+  int buffered_len, body_len;\r
+\r
+  buffered = conn->buf + conn->request_len;\r
+  buffered_len = conn->data_len - conn->request_len;\r
+  assert(buffered_len >= 0);\r
+\r
+  if (conn->content_len == -1) {\r
+    body_len = 0;\r
+  } else if (conn->content_len < (int64_t) buffered_len) {\r
+    body_len = (int) conn->content_len;\r
+  } else {\r
+    body_len = buffered_len;\r
+  }\r
+\r
+  conn->data_len -= conn->request_len + body_len;\r
+  memmove(conn->buf, conn->buf + conn->request_len + body_len,\r
+          (size_t) conn->data_len);\r
+}\r
+\r
+static int parse_url(const char *url, char *host, int *port) {\r
+  int len;\r
+\r
+  if (sscanf(url, "%*[htps]://%1024[^:]:%d%n", host, port, &len) == 2 ||\r
+      sscanf(url, "%1024[^:]:%d%n", host, port, &len) == 2) {\r
+  } else if (sscanf(url, "%*[htps]://%1024[^/]%n", host, &len) == 1) {\r
+    *port = 80;\r
+  } else {\r
+    sscanf(url, "%1024[^/]%n", host, &len);\r
+    *port = 80;\r
+  }\r
+  DEBUG_TRACE(("Host:%s, port:%d", host, *port));\r
+\r
+  return len;\r
+}\r
+\r
+static void handle_proxy_request(struct mg_connection *conn) {\r
+  struct mg_request_info *ri = &conn->request_info;\r
+  char host[1025], buf[BUFSIZ];\r
+  int port, is_ssl, len, i, n;\r
+\r
+  DEBUG_TRACE(("URL: %s", ri->uri));\r
+  if (conn->request_info.uri[0] == '/' ||\r
+      (ri->uri == NULL || (len = parse_url(ri->uri, host, &port))) == 0) {\r
+    return;\r
+  }\r
+\r
+  if (conn->peer == NULL) {\r
+    is_ssl = !strcmp(ri->request_method, "CONNECT");\r
+    if ((conn->peer = mg_connect(conn, host, port, is_ssl)) == NULL) {\r
+      return;\r
+    }\r
+    conn->peer->client.is_ssl = is_ssl;\r
+  }\r
+  \r
+  // Forward client's request to the target\r
+  mg_printf(conn->peer, "%s %s HTTP/%s\r\n", ri->request_method, ri->uri + len,\r
+            ri->http_version);\r
+\r
+  // And also all headers. TODO(lsm): anonymize!\r
+  for (i = 0; i < ri->num_headers; i++) {\r
+    mg_printf(conn->peer, "%s: %s\r\n", ri->http_headers[i].name,\r
+              ri->http_headers[i].value);\r
+  }\r
+  // End of headers, final newline\r
+  mg_write(conn->peer, "\r\n", 2);\r
+\r
+  // Read and forward body data if any\r
+  if (!strcmp(ri->request_method, "POST")) {\r
+    forward_body_data(conn, NULL, conn->peer->client.sock, conn->peer->ssl);\r
+  }\r
+\r
+  // Read data from the target and forward it to the client\r
+  while ((n = pull(NULL, conn->peer->client.sock, conn->peer->ssl,\r
+                   buf, sizeof(buf))) > 0) {\r
+    if (mg_write(conn, buf, (size_t)n) != n) {\r
+      break;\r
+    }\r
+  }\r
+\r
+  if (!conn->peer->client.is_ssl) {\r
+    close_connection(conn->peer);\r
+    free(conn->peer);\r
+    conn->peer = NULL;\r
+  }\r
+}\r
+\r
+static int is_valid_uri(const char *uri) {\r
+  // Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2\r
+  // URI can be an asterisk (*) or should start with slash.\r
+  return (uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0'));\r
+}\r
+\r
+static void process_new_connection(struct mg_connection *conn) {\r
+  struct mg_request_info *ri = &conn->request_info;\r
+  int keep_alive_enabled;\r
+  const char *cl;\r
+\r
+  keep_alive_enabled = !strcmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes");\r
+\r
+  do {\r
+    reset_per_request_attributes(conn);\r
+\r
+    // If next request is not pipelined, read it in\r
+    if ((conn->request_len = get_request_len(conn->buf, conn->data_len)) == 0) {\r
+      conn->request_len = read_request(NULL, conn->client.sock, conn->ssl,\r
+          conn->buf, conn->buf_size, &conn->data_len);\r
+    }\r
+    assert(conn->data_len >= conn->request_len);\r
+    if (conn->request_len == 0 && conn->data_len == conn->buf_size) {\r
+      send_http_error(conn, 413, "Request Too Large", "");\r
+      return;\r
+    } if (conn->request_len <= 0) {\r
+      return;  // Remote end closed the connection\r
+    }\r
+\r
+    // Nul-terminate the request cause parse_http_request() uses sscanf\r
+    conn->buf[conn->request_len - 1] = '\0';\r
+    if (!parse_http_request(conn->buf, ri) ||\r
+        (!conn->client.is_proxy && !is_valid_uri(ri->uri))) {\r
+      // Do not put garbage in the access log, just send it back to the client\r
+      send_http_error(conn, 400, "Bad Request",\r
+          "Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf);\r
+    } else if (strcmp(ri->http_version, "1.0") &&\r
+               strcmp(ri->http_version, "1.1")) {\r
+      // Request seems valid, but HTTP version is strange\r
+      send_http_error(conn, 505, "HTTP version not supported", "");\r
+      log_access(conn);\r
+    } else {\r
+      // Request is valid, handle it\r
+      cl = get_header(ri, "Content-Length");\r
+      conn->content_len = cl == NULL ? -1 : strtoll(cl, NULL, 10);\r
+      conn->birth_time = time(NULL);\r
+      if (conn->client.is_proxy) {\r
+        handle_proxy_request(conn);\r
+      } else {\r
+        handle_request(conn);\r
+      }\r
+      log_access(conn);\r
+      discard_current_request_from_buffer(conn);\r
+    }\r
+    // conn->peer is not NULL only for SSL-ed proxy connections\r
+  } while (conn->peer || (keep_alive_enabled && should_keep_alive(conn)));\r
+}\r
+\r
+// Worker threads take accepted socket from the queue\r
+static int consume_socket(struct mg_context *ctx, struct socket *sp) {\r
+  (void) pthread_mutex_lock(&ctx->mutex);\r
+  DEBUG_TRACE(("going idle"));\r
+\r
+  // If the queue is empty, wait. We're idle at this point.\r
+  while (ctx->sq_head == ctx->sq_tail && ctx->stop_flag == 0) {\r
+    pthread_cond_wait(&ctx->sq_full, &ctx->mutex);\r
+  }\r
+  // Master thread could wake us up without putting a socket.\r
+  // If this happens, it is time to exit.\r
+  if (ctx->stop_flag) {\r
+    (void) pthread_mutex_unlock(&ctx->mutex);\r
+    return 0;\r
+  }\r
+  assert(ctx->sq_head > ctx->sq_tail);\r
+\r
+  // Copy socket from the queue and increment tail\r
+  *sp = ctx->queue[ctx->sq_tail % ARRAY_SIZE(ctx->queue)];\r
+  ctx->sq_tail++;\r
+  DEBUG_TRACE(("grabbed socket %d, going busy", sp->sock));\r
+\r
+  // Wrap pointers if needed\r
+  while (ctx->sq_tail > (int) ARRAY_SIZE(ctx->queue)) {\r
+    ctx->sq_tail -= ARRAY_SIZE(ctx->queue);\r
+    ctx->sq_head -= ARRAY_SIZE(ctx->queue);\r
+  }\r
+\r
+  (void) pthread_cond_signal(&ctx->sq_empty);\r
+  (void) pthread_mutex_unlock(&ctx->mutex);\r
+\r
+  return 1;\r
+}\r
+\r
+static void worker_thread(struct mg_context *ctx) {\r
+  struct mg_connection *conn;\r
+  int buf_size = atoi(ctx->config[MAX_REQUEST_SIZE]);\r
+\r
+  conn = (struct mg_connection *) calloc(1, sizeof(*conn) + buf_size);\r
+  conn->buf_size = buf_size;\r
+  conn->buf = (char *) (conn + 1);\r
+  assert(conn != NULL);\r
+\r
+  while (ctx->stop_flag == 0 && consume_socket(ctx, &conn->client)) {\r
+    conn->birth_time = time(NULL);\r
+    conn->ctx = ctx;\r
+\r
+    // Fill in IP, port info early so even if SSL setup below fails,\r
+    // error handler would have the corresponding info.\r
+    // Thanks to Johannes Winkelmann for the patch.\r
+    conn->request_info.remote_port = ntohs(conn->client.rsa.u.sin.sin_port);\r
+    memcpy(&conn->request_info.remote_ip,\r
+           &conn->client.rsa.u.sin.sin_addr.s_addr, 4);\r
+    conn->request_info.remote_ip = ntohl(conn->request_info.remote_ip);\r
+    conn->request_info.is_ssl = conn->client.is_ssl;\r
+\r
+    if (!conn->client.is_ssl ||\r
+        (conn->client.is_ssl && sslize(conn, SSL_accept))) {\r
+      process_new_connection(conn);\r
+    }\r
+\r
+    close_connection(conn);\r
+  }\r
+  free(conn);\r
+\r
+  // Signal master that we're done with connection and exiting\r
+  (void) pthread_mutex_lock(&ctx->mutex);\r
+  ctx->num_threads--;\r
+  (void) pthread_cond_signal(&ctx->cond);\r
+  assert(ctx->num_threads >= 0);\r
+  (void) pthread_mutex_unlock(&ctx->mutex);\r
+\r
+  DEBUG_TRACE(("exiting"));\r
+}\r
+\r
+// Master thread adds accepted socket to a queue\r
+static void produce_socket(struct mg_context *ctx, const struct socket *sp) {\r
+  (void) pthread_mutex_lock(&ctx->mutex);\r
+\r
+  // If the queue is full, wait\r
+  while (ctx->sq_head - ctx->sq_tail >= (int) ARRAY_SIZE(ctx->queue)) {\r
+    (void) pthread_cond_wait(&ctx->sq_empty, &ctx->mutex);\r
+  }\r
+  assert(ctx->sq_head - ctx->sq_tail < (int) ARRAY_SIZE(ctx->queue));\r
+\r
+  // Copy socket to the queue and increment head\r
+  ctx->queue[ctx->sq_head % ARRAY_SIZE(ctx->queue)] = *sp;\r
+  ctx->sq_head++;\r
+  DEBUG_TRACE(("queued socket %d", sp->sock));\r
+\r
+  (void) pthread_cond_signal(&ctx->sq_full);\r
+  (void) pthread_mutex_unlock(&ctx->mutex);\r
+}\r
+\r
+static void accept_new_connection(const struct socket *listener,\r
+                                  struct mg_context *ctx) {\r
+  struct socket accepted;\r
+  int allowed;\r
+\r
+  accepted.rsa.len = sizeof(accepted.rsa.u.sin);\r
+  accepted.lsa = listener->lsa;\r
+  accepted.sock = accept(listener->sock, &accepted.rsa.u.sa, &accepted.rsa.len);\r
+  if (accepted.sock != INVALID_SOCKET) {\r
+    allowed = check_acl(ctx, &accepted.rsa);\r
+    if (allowed) {\r
+      // Put accepted socket structure into the queue\r
+      DEBUG_TRACE(("accepted socket %d", accepted.sock));\r
+      accepted.is_ssl = listener->is_ssl;\r
+      accepted.is_proxy = listener->is_proxy;\r
+      produce_socket(ctx, &accepted);\r
+    } else {\r
+      cry(fc(ctx), "%s: %s is not allowed to connect",\r
+          __func__, inet_ntoa(accepted.rsa.u.sin.sin_addr));\r
+      (void) closesocket(accepted.sock);\r
+    }\r
+  }\r
+}\r
+\r
+static void master_thread(struct mg_context *ctx) {\r
+  fd_set read_set;\r
+  struct timeval tv;\r
+  struct socket *sp;\r
+  int max_fd;\r
+\r
+  while (ctx->stop_flag == 0) {\r
+    FD_ZERO(&read_set);\r
+    max_fd = -1;\r
+\r
+    // Add listening sockets to the read set\r
+    for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) {\r
+      add_to_set(sp->sock, &read_set, &max_fd);\r
+    }\r
+\r
+    tv.tv_sec = 0;\r
+    tv.tv_usec = 200 * 1000;\r
+\r
+    if (select(max_fd + 1, &read_set, NULL, NULL, &tv) < 0) {\r
+#ifdef _WIN32\r
+      // On windows, if read_set and write_set are empty,\r
+      // select() returns "Invalid parameter" error\r
+      // (at least on my Windows XP Pro). So in this case, we sleep here.\r
+      sleep(1);\r
+#endif // _WIN32\r
+    } else {\r
+      for (sp = ctx->listening_sockets; sp != NULL; sp = sp->next) {\r
+        if (FD_ISSET(sp->sock, &read_set)) {\r
+          accept_new_connection(sp, ctx);\r
+        }\r
+      }\r
+    }\r
+  }\r
+  DEBUG_TRACE(("stopping workers"));\r
+\r
+  // Stop signal received: somebody called mg_stop. Quit.\r
+  close_all_listening_sockets(ctx);\r
+\r
+  // Wakeup workers that are waiting for connections to handle.\r
+  pthread_cond_broadcast(&ctx->sq_full);\r
+\r
+  // Wait until all threads finish\r
+  (void) pthread_mutex_lock(&ctx->mutex);\r
+  while (ctx->num_threads > 0) {\r
+    (void) pthread_cond_wait(&ctx->cond, &ctx->mutex);\r
+  }\r
+  (void) pthread_mutex_unlock(&ctx->mutex);\r
+\r
+  // All threads exited, no sync is needed. Destroy mutex and condvars\r
+  (void) pthread_mutex_destroy(&ctx->mutex);\r
+  (void) pthread_cond_destroy(&ctx->cond);\r
+  (void) pthread_cond_destroy(&ctx->sq_empty);\r
+  (void) pthread_cond_destroy(&ctx->sq_full);\r
+\r
+  // Signal mg_stop() that we're done\r
+  ctx->stop_flag = 2;\r
+\r
+  DEBUG_TRACE(("exiting"));\r
+}\r
+\r
+static void free_context(struct mg_context *ctx) {\r
+  int i;\r
+\r
+  // Deallocate config parameters\r
+  for (i = 0; i < NUM_OPTIONS; i++) {\r
+    if (ctx->config[i] != NULL)\r
+      free(ctx->config[i]);\r
+  }\r
+\r
+  // Deallocate SSL context\r
+  if (ctx->ssl_ctx != NULL) {\r
+    SSL_CTX_free(ctx->ssl_ctx);\r
+  }\r
+#ifndef NO_SSL\r
+  if (ssl_mutexes != NULL) {\r
+    free(ssl_mutexes);\r
+  }\r
+#endif // !NO_SSL\r
+\r
+  // Deallocate context itself\r
+  free(ctx);\r
+}\r
+\r
+void mg_stop(struct mg_context *ctx) {\r
+  ctx->stop_flag = 1;\r
+\r
+  // Wait until mg_fini() stops\r
+  while (ctx->stop_flag != 2) {\r
+    (void) sleep(0);\r
+  }\r
+  free_context(ctx);\r
+\r
+#if defined(_WIN32) && !defined(__SYMBIAN32__)\r
+  (void) WSACleanup();\r
+#endif // _WIN32\r
+}\r
+\r
+struct mg_context *mg_start(mg_callback_t user_callback, void *user_data,\r
+                            const char **options) {\r
+  struct mg_context *ctx;\r
+  const char *name, *value, *default_value;\r
+  int i;\r
+\r
+#if defined(_WIN32) && !defined(__SYMBIAN32__)\r
+  WSADATA data;\r
+  WSAStartup(MAKEWORD(2,2), &data);\r
+#endif // _WIN32\r
+\r
+  // Allocate context and initialize reasonable general case defaults.\r
+  // TODO(lsm): do proper error handling here.\r
+  ctx = (struct mg_context *) calloc(1, sizeof(*ctx));\r
+  ctx->user_callback = user_callback;\r
+  ctx->user_data = user_data;\r
+\r
+  while (options && (name = *options++) != NULL) {\r
+    if ((i = get_option_index(name)) == -1) {\r
+      cry(fc(ctx), "Invalid option: %s", name);\r
+      free_context(ctx);\r
+      return NULL;\r
+    } else if ((value = *options++) == NULL) {\r
+      cry(fc(ctx), "%s: option value cannot be NULL", name);\r
+      free_context(ctx);\r
+      return NULL;\r
+    }\r
+    ctx->config[i] = mg_strdup(value);\r
+    DEBUG_TRACE(("[%s] -> [%s]", name, value));\r
+  }\r
+\r
+  // Set default value if needed\r
+  for (i = 0; config_options[i * ENTRIES_PER_CONFIG_OPTION] != NULL; i++) {\r
+    default_value = config_options[i * ENTRIES_PER_CONFIG_OPTION + 2];\r
+    if (ctx->config[i] == NULL && default_value != NULL) {\r
+      ctx->config[i] = mg_strdup(default_value);\r
+      DEBUG_TRACE(("Setting default: [%s] -> [%s]",\r
+                   config_options[i * ENTRIES_PER_CONFIG_OPTION + 1],\r
+                   default_value));\r
+    }\r
+  }\r
+\r
+  // NOTE(lsm): order is important here. SSL certificates must\r
+  // be initialized before listening ports. UID must be set last.\r
+  if (!set_gpass_option(ctx) ||\r
+#if !defined(NO_SSL)\r
+      !set_ssl_option(ctx) ||\r
+#endif\r
+      !set_ports_option(ctx) ||\r
+#if !defined(_WIN32)\r
+      !set_uid_option(ctx) ||\r
+#endif\r
+      !set_acl_option(ctx)) {\r
+    free_context(ctx);\r
+    return NULL;\r
+  }\r
+\r
+#if !defined(_WIN32) && !defined(__SYMBIAN32__)\r
+  // Ignore SIGPIPE signal, so if browser cancels the request, it\r
+  // won't kill the whole process.\r
+  (void) signal(SIGPIPE, SIG_IGN);\r
+#endif // !_WIN32\r
+\r
+  (void) pthread_mutex_init(&ctx->mutex, NULL);\r
+  (void) pthread_cond_init(&ctx->cond, NULL);\r
+  (void) pthread_cond_init(&ctx->sq_empty, NULL);\r
+  (void) pthread_cond_init(&ctx->sq_full, NULL);\r
+\r
+  // Start master (listening) thread\r
+  start_thread(ctx, (mg_thread_func_t) master_thread, ctx);\r
+\r
+  // Start worker threads\r
+  for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) {\r
+    if (start_thread(ctx, (mg_thread_func_t) worker_thread, ctx) != 0) {\r
+      cry(fc(ctx), "Cannot start worker thread: %d", ERRNO);\r
+    } else {\r
+      ctx->num_threads++;\r
+    }\r
+  }\r
+\r
+  return ctx;\r
+}\r
diff --git a/MapServer/mongoose/mongoose.h b/MapServer/mongoose/mongoose.h
new file mode 100644 (file)
index 0000000..1b86bd5
--- /dev/null
@@ -0,0 +1,222 @@
+// Copyright (c) 2004-2010 Sergey Lyubka\r
+//\r
+// Permission is hereby granted, free of charge, to any person obtaining a copy\r
+// of this software and associated documentation files (the "Software"), to deal\r
+// in the Software without restriction, including without limitation the rights\r
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
+// copies of the Software, and to permit persons to whom the Software is\r
+// furnished to do so, subject to the following conditions:\r
+//\r
+// The above copyright notice and this permission notice shall be included in\r
+// all copies or substantial portions of the Software.\r
+//\r
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
+// THE SOFTWARE.\r
+\r
+#ifndef MONGOOSE_HEADER_INCLUDED\r
+#define  MONGOOSE_HEADER_INCLUDED\r
+\r
+#ifdef __cplusplus\r
+extern "C" {\r
+#endif // __cplusplus\r
+\r
+struct mg_context;     // Handle for the HTTP service itself\r
+struct mg_connection;  // Handle for the individual connection\r
+\r
+\r
+// This structure contains information about the HTTP request.\r
+struct mg_request_info {\r
+  void *user_data;       // User-defined pointer passed to mg_start()\r
+  char *request_method;  // "GET", "POST", etc\r
+  char *uri;             // URL-decoded URI\r
+  char *http_version;    // E.g. "1.0", "1.1"\r
+  char *query_string;    // \0 - terminated\r
+  char *remote_user;     // Authenticated user\r
+  char *log_message;     // Mongoose error log message\r
+  long remote_ip;        // Client's IP address\r
+  int remote_port;       // Client's port\r
+  int status_code;       // HTTP reply status code\r
+  int is_ssl;            // 1 if SSL-ed, 0 if not\r
+  int num_headers;       // Number of headers\r
+  struct mg_header {\r
+    char *name;          // HTTP header name\r
+    char *value;         // HTTP header value\r
+  } http_headers[64];    // Maximum 64 headers\r
+};\r
+\r
+// Various events on which user-defined function is called by Mongoose.\r
+enum mg_event {\r
+  MG_NEW_REQUEST,   // New HTTP request has arrived from the client\r
+  MG_HTTP_ERROR,    // HTTP error must be returned to the client\r
+  MG_EVENT_LOG,     // Mongoose logs an event, request_info.log_message\r
+  MG_INIT_SSL       // Mongoose initializes SSL. Instead of mg_connection *,\r
+                    // SSL context is passed to the callback function.\r
+};\r
+\r
+// Prototype for the user-defined function. Mongoose calls this function\r
+// on every event mentioned above.\r
+//\r
+// Parameters:\r
+//   event: which event has been triggered.\r
+//   conn: opaque connection handler. Could be used to read, write data to the\r
+//         client, etc. See functions below that accept "mg_connection *".\r
+//   request_info: Information about HTTP request.\r
+//\r
+// Return:\r
+//   If handler returns non-NULL, that means that handler has processed the\r
+//   request by sending appropriate HTTP reply to the client. Mongoose treats\r
+//   the request as served.\r
+//   If callback returns NULL, that means that callback has not processed\r
+//   the request. Handler must not send any data to the client in this case.\r
+//   Mongoose proceeds with request handling as if nothing happened.\r
+typedef void * (*mg_callback_t)(enum mg_event event,\r
+                                struct mg_connection *conn,\r
+                                const struct mg_request_info *request_info);\r
+\r
+\r
+// Start web server.\r
+//\r
+// Parameters:\r
+//   callback: user defined event handling function or NULL.\r
+//   options: NULL terminated list of option_name, option_value pairs that\r
+//            specify Mongoose configuration parameters.\r
+//\r
+// Example:\r
+//   const char *options[] = {\r
+//     "document_root", "/var/www",\r
+//     "listening_ports", "80,443s",\r
+//     NULL\r
+//   };\r
+//   struct mg_context *ctx = mg_start(&my_func, NULL, options);\r
+//\r
+// Please refer to http://code.google.com/p/mongoose/wiki/MongooseManual\r
+// for the list of valid option and their possible values.\r
+//\r
+// Return:\r
+//   web server context, or NULL on error.\r
+struct mg_context *mg_start(mg_callback_t callback, void *user_data,\r
+                            const char **options);\r
+\r
+\r
+// Stop the web server.\r
+//\r
+// Must be called last, when an application wants to stop the web server and\r
+// release all associated resources. This function blocks until all Mongoose\r
+// threads are stopped. Context pointer becomes invalid.\r
+void mg_stop(struct mg_context *);\r
+\r
+\r
+// Get the value of particular configuration parameter.\r
+// The value returned is read-only. Mongoose does not allow changing\r
+// configuration at run time.\r
+// If given parameter name is not valid, NULL is returned. For valid\r
+// names, return value is guaranteed to be non-NULL. If parameter is not\r
+// set, zero-length string is returned.\r
+const char *mg_get_option(const struct mg_context *ctx, const char *name);\r
+\r
+\r
+// Return array of strings that represent valid configuration options.\r
+// For each option, a short name, long name, and default value is returned.\r
+// Array is NULL terminated.\r
+const char **mg_get_valid_option_names(void);\r
+\r
+\r
+// Add, edit or delete the entry in the passwords file.\r
+//\r
+// This function allows an application to manipulate .htpasswd files on the\r
+// fly by adding, deleting and changing user records. This is one of the\r
+// several ways of implementing authentication on the server side. For another,\r
+// cookie-based way please refer to the examples/chat.c in the source tree.\r
+//\r
+// If password is not NULL, entry is added (or modified if already exists).\r
+// If password is NULL, entry is deleted.\r
+//\r
+// Return:\r
+//   1 on success, 0 on error.\r
+int mg_modify_passwords_file(const char *passwords_file_name,\r
+                             const char *domain,\r
+                             const char *user,\r
+                             const char *password);\r
+\r
+// Send data to the client.\r
+int mg_write(struct mg_connection *, const void *buf, size_t len);\r
+\r
+\r
+// Send data to the browser using printf() semantics.\r
+//\r
+// Works exactly like mg_write(), but allows to do message formatting.\r
+// Note that mg_printf() uses internal buffer of size IO_BUF_SIZE\r
+// (8 Kb by default) as temporary message storage for formatting. Do not\r
+// print data that is bigger than that, otherwise it will be truncated.\r
+int mg_printf(struct mg_connection *, const char *fmt, ...);\r
+\r
+\r
+// Read data from the remote end, return number of bytes read.\r
+int mg_read(struct mg_connection *, void *buf, size_t len);\r
+\r
+\r
+// Get the value of particular HTTP header.\r
+//\r
+// This is a helper function. It traverses request_info->http_headers array,\r
+// and if the header is present in the array, returns its value. If it is\r
+// not present, NULL is returned.\r
+const char *mg_get_header(const struct mg_connection *, const char *name);\r
+\r
+\r
+// Get a value of particular form variable.\r
+//\r
+// Parameters:\r
+//   data: pointer to form-uri-encoded buffer. This could be either POST data,\r
+//         or request_info.query_string.\r
+//   data_len: length of the encoded data.\r
+//   var_name: variable name to decode from the buffer\r
+//   buf: destination buffer for the decoded variable\r
+//   buf_len: length of the destination buffer\r
+//\r
+// Return:\r
+//   On success, length of the decoded variable.\r
+//   On error, -1 (variable not found, or destination buffer is too small).\r
+//\r
+// Destination buffer is guaranteed to be '\0' - terminated. In case of\r
+// failure, dst[0] == '\0'.\r
+int mg_get_var(const char *data, size_t data_len,\r
+    const char *var_name, char *buf, size_t buf_len);\r
+\r
+// Fetch value of certain cookie variable into the destination buffer.\r
+//\r
+// Destination buffer is guaranteed to be '\0' - terminated. In case of\r
+// failure, dst[0] == '\0'. Note that RFC allows many occurrences of the same\r
+// parameter. This function returns only first occurrence.\r
+//\r
+// Return:\r
+//   On success, value length.\r
+//   On error, -1 (either "Cookie:" header is not present at all, or the\r
+//   requested parameter is not found, or destination buffer is too small\r
+//   to hold the value).\r
+int mg_get_cookie(const struct mg_connection *,\r
+    const char *cookie_name, char *buf, size_t buf_len);\r
+\r
+\r
+// Return Mongoose version.\r
+const char *mg_version(void);\r
+\r
+\r
+// MD5 hash given strings.\r
+// Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of\r
+// asciiz strings. When function returns, buf will contain human-readable\r
+// MD5 hash. Example:\r
+//   char buf[33];\r
+//   mg_md5(buf, "aa", "bb", NULL);\r
+void mg_md5(char *buf, ...);\r
+\r
+\r
+#ifdef __cplusplus\r
+}\r
+#endif // __cplusplus\r
+\r
+#endif // MONGOOSE_HEADER_INCLUDED\r
diff --git a/Sites/MapServer/earth.ico b/Sites/MapServer/earth.ico
new file mode 100644 (file)
index 0000000..b688491
Binary files /dev/null and b/Sites/MapServer/earth.ico differ
diff --git a/Sites/MapServer/favicon.ico b/Sites/MapServer/favicon.ico
new file mode 100644 (file)
index 0000000..3864c5a
Binary files /dev/null and b/Sites/MapServer/favicon.ico differ
diff --git a/Sites/MapServer/index.html b/Sites/MapServer/index.html
new file mode 100644 (file)
index 0000000..d49db4e
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
+    <link href="main.css" type="text/css" rel="stylesheet">
+    <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?v=3.21&sensor=false"></script>
+    <script type="text/javascript" src="js/prototype.js"></script>
+    <script type="text/javascript" src="js/maps.js"></script>
+</head>
+
+<body onload="InitializeMap()">
+    <div id="all" class="unblock"></div>
+
+    <div id="mapCanvas" style="width:100%; height:100%"></div>
+
+    <div id="controlPanel" onmousemove="javascript:HideEntryMenu();HideContextMenu();">
+        <p>Algorithm:<br/><select id='select_algorithm' onchange="LoadParameter();Run();"></select></p>
+        <div id='parameterArea'></div>
+        <table style="padding: 5px 0pt; width: 100%;"><tbody id='infoArea'></tbody></table>
+    </div>
+
+    <canvas id="functionCanvas"></canvas>
+
+    <div id="contextMenu" class="contextMenu">
+        <p><a href="javascript:SetSource();HideContextMenu();Run();">Set Source</a></p>
+        <p><a href="javascript:SetTarget();HideContextMenu();Run();">Set Target</a></p>
+        <p><a href="javascript:HideContextMenu();Run();">Rerun last Query</a></p>
+        <p><a href="javascript:ResetQuery();">Reset Query</a></p>
+    </div>
+
+    <div id="entryMenu" class="contextMenu">
+        <p><a href="javascript:HideEntryMenu();ToggleGroup();">Toggle</a></p>
+        <p><a href="javascript:HideEntryMenu();PinGroup();">Pin</a></p>
+        <p><a href="javascript:HideEntryMenu();DeleteGroup();">Delete</a></p>
+    </div>
+
+    <div class="gmnoprint gm-style-cc" draggable="false" style="z-index: 1000001;user-select: none;height: 14px;line-height: 14px;position: absolute;right: 277px;bottom: 0px;">
+        <div style="opacity: 0.7; width: 100%; height: 100%; position: absolute;">
+            <div style="width: 1px;"></div>
+            <div style="background-color: rgb(245, 245, 245); width: auto; height: 100%; margin-left: 1px;"></div>
+        </div>
+        <div style="position: relative; padding-right: 6px; padding-left: 6px; font-family: Roboto, Arial, sans-serif; font-size: 10px; color: rgb(68, 68, 68); white-space: nowrap; direction: ltr; text-align: right; vertical-align: middle; display: inline-block;">
+            <a href="https://www.kit.edu/datenschutz.php" target="_blank" style="text-decoration: none; cursor: pointer; color: rgb(68, 68, 68);">Datenschutzerkl&aumlrung</a>
+        </div>
+    </div>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/Sites/MapServer/js/googleMapsApi.js b/Sites/MapServer/js/googleMapsApi.js
new file mode 100644 (file)
index 0000000..d395d15
--- /dev/null
@@ -0,0 +1,87 @@
+
+
+window.google = window.google || {};
+google.maps = google.maps || {};
+(function() {
+  
+  function getScript(src) {
+    document.write('<' + 'script src="' + src + '"><' + '/script>');
+  }
+  
+  var modules = google.maps.modules = {};
+  google.maps.__gjsload__ = function(name, text) {
+    modules[name] = text;
+  };
+  
+  google.maps.Load = function(apiLoad) {
+    delete google.maps.Load;
+    apiLoad([0.009999999776482582,[[["http://mt0.googleapis.com/maps/vt?lyrs=m@336000000\u0026src=api\u0026hl=de-DE\u0026","http://mt1.googleapis.com/maps/vt?lyrs=m@336000000\u0026src=api\u0026hl=de-DE\u0026"],null,null,null,null,"m@336000000",["https://mts0.google.com/maps/vt?lyrs=m@336000000\u0026src=api\u0026hl=de-DE\u0026","https://mts1.google.com/maps/vt?lyrs=m@336000000\u0026src=api\u0026hl=de-DE\u0026"]],[["http://khm0.googleapis.com/kh?v=194\u0026hl=de-DE\u0026","http://khm1.googleapis.com/kh?v=194\u0026hl=de-DE\u0026"],null,null,null,1,"194",["https://khms0.google.com/kh?v=194\u0026hl=de-DE\u0026","https://khms1.google.com/kh?v=194\u0026hl=de-DE\u0026"]],null,[["http://mt0.googleapis.com/maps/vt?lyrs=t@132,r@336000000\u0026src=api\u0026hl=de-DE\u0026","http://mt1.googleapis.com/maps/vt?lyrs=t@132,r@336000000\u0026src=api\u0026hl=de-DE\u0026"],null,null,null,null,"t@132,r@336000000",["https://mts0.google.com/maps/vt?lyrs=t@132,r@336000000\u0026src=api\u0026hl=de-DE\u0026","https://mts1.google.com/maps/vt?lyrs=t@132,r@336000000\u0026src=api\u0026hl=de-DE\u0026"]],null,null,[["http://cbk0.googleapis.com/cbk?","http://cbk1.googleapis.com/cbk?"]],[["http://khm0.googleapis.com/kh?v=95\u0026hl=de-DE\u0026","http://khm1.googleapis.com/kh?v=95\u0026hl=de-DE\u0026"],null,null,null,null,"95",["https://khms0.google.com/kh?v=95\u0026hl=de-DE\u0026","https://khms1.google.com/kh?v=95\u0026hl=de-DE\u0026"]],[["http://mt0.googleapis.com/mapslt?hl=de-DE\u0026","http://mt1.googleapis.com/mapslt?hl=de-DE\u0026"]],[["http://mt0.googleapis.com/mapslt/ft?hl=de-DE\u0026","http://mt1.googleapis.com/mapslt/ft?hl=de-DE\u0026"]],[["http://mt0.googleapis.com/maps/vt?hl=de-DE\u0026","http://mt1.googleapis.com/maps/vt?hl=de-DE\u0026"]],[["http://mt0.googleapis.com/mapslt/loom?hl=de-DE\u0026","http://mt1.googleapis.com/mapslt/loom?hl=de-DE\u0026"]],[["https://mts0.googleapis.com/mapslt?hl=de-DE\u0026","https://mts1.googleapis.com/mapslt?hl=de-DE\u0026"]],[["https://mts0.googleapis.com/mapslt/ft?hl=de-DE\u0026","https://mts1.googleapis.com/mapslt/ft?hl=de-DE\u0026"]],[["https://mts0.googleapis.com/mapslt/loom?hl=de-DE\u0026","https://mts1.googleapis.com/mapslt/loom?hl=de-DE\u0026"]]],["de-DE","US",null,0,null,null,"http://maps.gstatic.com/mapfiles/","http://csi.gstatic.com","https://maps.googleapis.com","http://maps.googleapis.com",null,"https://maps.google.com","https://gg.google.com","http://maps.gstatic.com/maps-api-v3/api/images/","https://www.google.com/maps",0,"https://www.google.com"],["http://maps.googleapis.com/maps-api-v3/api/js/21/12/intl/de_ALL","3.21.12"],[3744937528],1,null,null,null,null,null,"",null,null,0,"http://khm.googleapis.com/mz?v=194\u0026",null,"https://earthbuilder.googleapis.com","https://earthbuilder.googleapis.com",null,"http://mt.googleapis.com/maps/vt/icon",[["http://maps.googleapis.com/maps/vt"],["https://maps.googleapis.com/maps/vt"],null,null,null,null,null,null,null,null,null,null,["https://www.google.com/maps/vt"],"/maps/vt",336000000,132],2,500,[null,"http://g0.gstatic.com/landmark/tour","http://g0.gstatic.com/landmark/config",null,"http://www.google.com/maps/preview/log204","","http://static.panoramio.com.storage.googleapis.com/photos/",["http://geo0.ggpht.com/cbk","http://geo1.ggpht.com/cbk","http://geo2.ggpht.com/cbk","http://geo3.ggpht.com/cbk"],"https://maps.googleapis.com/maps/api/js/GeoPhotoService.GetMetadata","https://maps.googleapis.com/maps/api/js/GeoPhotoService.SingleImageSearch",["http://lh3.ggpht.com/","http://lh4.ggpht.com/","http://lh5.ggpht.com/","http://lh6.ggpht.com/"]],["https://www.google.com/maps/api/js/master?pb=!1m2!1u21!2s12!2sde-DE!3sUS!4s21/12/intl/de_ALL","https://www.google.com/maps/api/js/widget?pb=!1m2!1u21!2s12!2sde-DE"],null,0,null,"/maps/api/js/ApplicationService.GetEntityDetails",0,null,null,null,null,[],["21.12"]], loadScriptTime);
+  };
+  var loadScriptTime = (new Date).getTime();
+})();
+// inlined
+(function(){'use strict';var aa=encodeURIComponent,ba=navigator,ca=Error,da=parseFloat,fa=Object,k=Math,m=window,ga=parseInt,ha=isFinite,n=document,ia=Array,ja=screen,la=Infinity,ma=String;function na(a,b){return a.getAt=b}function oa(a,b){return a.releaseTile=b}function pa(a,b){return a.getLength=b}function qa(a,b){return a.getId=b}function ra(a,b){return a.tileSize=b}function sa(a,b){return a.zIndex_changed=b}function ta(a,b){return a.reset=b}function ua(a,b){return a.target=b}
+function va(a,b){return a.name=b}function wa(a,b){return a.minZoom=b}function xa(a,b){return a.map_changed=b}function ya(a,b){return a.prototype=b}function za(a,b){return a.maxZoom=b}function Aa(a,b){return a.onRemove=b}function Ba(a,b){return a.openInfoWindow=b}function Ca(a,b){return a.getZoom=b}function Da(a,b){return a.setUrl=b}function Ea(a,b){return a.getPath=b}function Ga(a,b){return a.onAdd=b}function Ha(a,b){return a.clear=b}function Ja(a,b){return a.remove=b}
+function Ka(a,b){return a.data=b}function La(a,b){return a.type=b}function Ma(a,b){return a.changed=b}function Oa(a,b){return a.freeze=b}function Pa(a,b){return a.getArray=b}function Qa(a,b){return a.search=b}function Ra(a,b){return a.getType=b}function Sa(a,b){return a.extend=b}function Ta(a,b){return a.overflow=b}function Ua(a,b){return a.width=b}function Va(a,b){return a.getBounds=b}function Wa(a,b){return a.release=b}function Xa(a,b){return a.onload=b}function Ya(a,b){return a.isEmpty=b}
+function Za(a,b){return a.center_changed=b}function $a(a,b){return a.contains=b}function ab(a,b){return a.version=b}function bb(a,b){return a.onerror=b}function cb(a,b){return a.height=b}function db(a,b){return a.visible_changed=b}function eb(a,b){return a.setPath=b}function fb(a,b){return a.setZoom=b}function gb(a,b){return a.length=b}function hb(a,b){return a.returnValue=b}function ib(a,b){return a.radius_changed=b}function jb(a,b){return a.toString=b}function kb(a,b){return a.zoom=b}
+function lb(a,b){return a.getUrl=b}function mb(a,b){return a.forEach=b}function nb(a,b){return a.__gm=b}function ob(a,b){return a.projection=b}function pb(a,b){return a.getDiv=b}function qb(a,b){return a.getTile=b}function rb(a,b){return a.size=b}
+var sb="getAt",tb="srcElement",ub="getSouthWest",vb="userAgent",wb="href",xb="getLength",yb="getId",p="bindTo",zb="intersects",Ab="getNorthEast",Bb="clearTimeout",Cb="compatMode",Db="console",Eb="tileSize",Fb="split",Gb="substr",Hb="join",Ib="sqrt",Jb="addEventListener",q="style",Kb="reset",Lb="slice",Mb="target",Nb="name",Ob="toUpperCase",Pb="getProjection",u="call",Qb="minZoom",v="prototype",Rb="atan2",Tb="maxZoom",Ub="label",Vb="exec",Wb="onRemove",Xb="appendChild",Yb="openInfoWindow",x="round",
+Zb="error",$b="atan",ac="offsetWidth",bc="event",cc="propertyIsEnumerable",dc="fromPointToLatLng",ec="getVisible",fc="indexOf",z="trigger",gc="notify",hc="defaultPrevented",ic="asin",jc="toUrlValue",kc="parentNode",lc="setVisible",mc="firstChild",nc="listener",oc="setTimeout",pc="onAdd",qc="stopPropagation",rc="addDomListener",sc="unbind",tc="replace",uc="setValues",vc="tagName",wc="setAt",xc="weight",yc="cloneNode",zc="removeChild",Ac="documentMode",Bc="fromLatLngToPoint",Cc="charAt",Dc="removeEventListener",
+Ec="remove",B="push",Fc="type",Gc="location",Hc="detachEvent",Ic="changed",Jc="removeAt",Kc="context",Lc="splice",Nc="preventDefault",Oc="random",Pc="getArray",Rc="opacity",Sc="removeListener",Tc="getElementsByTagName",Uc="extend",C="width",Vc="startTime",Wc="features",Xc="offsetHeight",Yc="shift",Zc="hasOwnProperty",$c="release",ad="isEmpty",bd="fromCharCode",cd="contains",dd="version",ed="google",fd="apply",gd="clearInstanceListeners",hd="attachEvent",id="navigator",D="addListener",E="height",G=
+"forward",jd="setZoom",I="length",kd="radius",ld="toString",J="bind",md="zoom",nd="nodeType",od="addListenerOnce",pd="charCodeAt",qd="document",rd="forEach",sd="floor",td="toLowerCase",ud="gm_bindings_",vd="__gm",wd="getTime",xd="getTileUrl",yd="createElement",zd="getTile",Ad="computeHeading",Bd="setPov",Cd="nextSibling",Dd="insertAt",Ed="ERROR",Fd="INVALID_LAYER",Gd="INVALID_REQUEST",Hd="MAX_DIMENSIONS_EXCEEDED",Id="MAX_ELEMENTS_EXCEEDED",Jd="MAX_WAYPOINTS_EXCEEDED",Kd="NOT_FOUND",Ld="OK",Md="OVER_QUERY_LIMIT",
+Nd="REQUEST_DENIED",Od="UNKNOWN_ERROR",Pd="ZERO_RESULTS";function Qd(){return function(){}}function K(a){return function(){return this[a]}}function Rd(a){return function(){return a}}var N,Sd=[];function Td(a){return function(){return Sd[a][fd](this,arguments)}}var Ud={ROADMAP:"roadmap",SATELLITE:"satellite",HYBRID:"hybrid",TERRAIN:"terrain"};var Vd={TOP_LEFT:1,TOP_CENTER:2,TOP:2,TOP_RIGHT:3,LEFT_CENTER:4,LEFT_TOP:5,LEFT:5,LEFT_BOTTOM:6,RIGHT_TOP:7,RIGHT:7,RIGHT_CENTER:8,RIGHT_BOTTOM:9,BOTTOM_LEFT:10,BOTTOM_CENTER:11,BOTTOM:11,BOTTOM_RIGHT:12,CENTER:13};var Wd=this;function Xd(a){return void 0!==a}function Yd(){}function Zd(a){a.Hc=function(){return a.qb?a.qb:a.qb=new a}}
+function $d(a){var b=typeof a;if("object"==b)if(a){if(a instanceof ia)return"array";if(a instanceof fa)return b;var c=fa[v][ld][u](a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a[I]&&"undefined"!=typeof a[Lc]&&"undefined"!=typeof a[cc]&&!a[cc]("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a[u]&&"undefined"!=typeof a[cc]&&!a[cc]("call"))return"function"}else return"null";else if("function"==b&&"undefined"==typeof a[u])return"object";return b}
+function ae(a){return"string"==typeof a}function be(a){return"function"==$d(a)}function ce(a){var b=typeof a;return"object"==b&&null!=a||"function"==b}function de(a){return a[ee]||(a[ee]=++fe)}var ee="closure_uid_"+(1E9*k[Oc]()>>>0),fe=0;function ge(a,b,c){return a[u][fd](a[J],arguments)}
+function he(a,b,c){if(!a)throw ca();if(2<arguments[I]){var d=ia[v][Lb][u](arguments,2);return function(){var c=ia[v][Lb][u](arguments);ia[v].unshift[fd](c,d);return a[fd](b,c)}}return function(){return a[fd](b,arguments)}}function O(a,b,c){O=Function[v][J]&&-1!=Function[v][J][ld]()[fc]("native code")?ge:he;return O[fd](null,arguments)}function ie(){return+new Date}
+function je(a,b){function c(){}ya(c,b[v]);a.ud=b[v];ya(a,new c);a[v].constructor=a;a.Pq=function(a,c,f){for(var g=ia(arguments[I]-2),h=2;h<arguments[I];h++)g[h-2]=arguments[h];return b[v][c][fd](a,g)}};function ke(a){return a?a[I]:0}function le(a){return a}function me(a,b){ne(b,function(c){a[c]=b[c]})}function oe(a){for(var b in a)return!1;return!0}function Q(a,b){function c(){}ya(c,b[v]);ya(a,new c);a[v].constructor=a}function pe(a,b,c){null!=b&&(a=k.max(a,b));null!=c&&(a=k.min(a,c));return a}function qe(a,b,c){c=c-b;return((a-b)%c+c)%c+b}function re(a,b,c){return k.abs(a-b)<=(c||1E-9)}function se(a){return k.PI/180*a}function te(a){return a/(k.PI/180)}
+function ue(a,b){for(var c=[],d=ke(a),e=0;e<d;++e)c[B](b(a[e],e));return c}function ve(a,b){for(var c=we(void 0,ke(b)),d=we(void 0,0);d<c;++d)a[B](b[d])}function xe(a){return null==a}function ye(a){return"undefined"!=typeof a}function ze(a){return"number"==typeof a}function Ae(a){return"object"==typeof a}function Be(){}function we(a,b){return null==a?b:a}function Ce(a){return"string"==typeof a}function De(a){return a===!!a}function R(a,b){for(var c=0,d=ke(a);c<d;++c)b(a[c],c)}
+function ne(a,b){for(var c in a)b(c,a[c])}function Ee(a,b,c){var d=Fe(arguments,2);return function(){return b[fd](a,d)}}function Fe(a,b,c){return Function[v][u][fd](ia[v][Lb],arguments)}function Ge(){return(new Date)[wd]()}function He(a){return null!=a&&"object"==typeof a&&"number"==typeof a[I]}function Ie(a){return function(){var b=this,c=arguments;Je(function(){a[fd](b,c)})}}function Je(a){return m[oc](a,0)}function Ke(){return m.devicePixelRatio||ja.deviceXDPI&&ja.deviceXDPI/96||1}
+function Le(a,b){if(fa[v][Zc][u](a,b))return a[b]};function Me(a){a=a||m[bc];Ne(a);Oe(a)}function Ne(a){a.cancelBubble=!0;a[qc]&&a[qc]()}function Oe(a){a[Nc]&&ye(a[hc])?a[Nc]():hb(a,!1)}function Pe(a){a.handled=!0;ye(a.bubbles)||hb(a,"handled")};var Se=ia[v];function Te(a,b,c){c=null==c?0:0>c?k.max(0,a[I]+c):c;if(ae(a))return ae(b)&&1==b[I]?a[fc](b,c):-1;for(;c<a[I];c++)if(c in a&&a[c]===b)return c;return-1}function Ue(a,b,c){for(var d=a[I],e=ae(a)?a[Fb](""):a,f=0;f<d;f++)f in e&&b[u](c,e[f],f,a)}function Ve(a,b){var c=We(a,b);return 0>c?null:ae(a)?a[Cc](c):a[c]}function We(a,b){for(var c=a[I],d=ae(a)?a[Fb](""):a,e=0;e<c;e++)if(e in d&&b[u](void 0,d[e],e,a))return e;return-1}
+function Xe(a,b){var c=Te(a,b),d;(d=0<=c)&&Se[Lc][u](a,c,1);return d};function Ye(a,b){return function(c){return c[nc]==a&&c[Kc]==(b||null)}}function Ze(){this.j=[]}N=Ze[v];N.addListener=function(a,b){var c=Ve(this.j,Ye(a,b));c?c.Wd=la:this.j[B]({listener:a,context:b||null,Wd:la});this[pc]();return a};N.addListenerOnce=function(a,b){Ve(this.j,Ye(a,b))||this.j[B]({listener:a,context:b||null,Wd:1});this[pc]();return a};N.removeListener=function(a,b){var c=this.j,d=We(c,Ye(a,b));0<=d&&Se[Lc][u](c,d,1);this[Wb]()};Ga(N,Qd());Aa(N,Qd());
+function $e(a,b,c){var d=a.j;Ue(a.j[Lb](0),function(e){b[u](c||null,function(b){1==e.Wd&&(Xe(d,e),a[Wb]());0<e.Wd&&(e.Wd--,e[nc][u](e[Kc],b))})})};function af(){this.j=[]}je(af,Ze);af[v].G=function(a){$e(this,function(b){b(a)})};var S={},bf="undefined"!=typeof ba&&-1!=ba[vb][td]()[fc]("msie"),cf={};S.addListener=function(a,b,c){return new df(a,b,c,0)};S.hasListeners=function(a,b){var c=a.__e3_,c=c&&c[b];return!!c&&!oe(c)};S.removeListener=function(a){a&&a[Ec]()};S.clearListeners=function(a,b){ne(ef(a,b),function(a,b){b&&b[Ec]()})};S.clearInstanceListeners=function(a){ne(ef(a),function(a,c){c&&c[Ec]()})};function ff(a,b){a.__e3_||(a.__e3_={});var c=a.__e3_;c[b]||(c[b]={});return c[b]}
+function ef(a,b){var c,d=a.__e3_||{};if(b)c=d[b]||{};else{c={};for(var e in d)me(c,d[e])}return c}S.trigger=function(a,b,c){a.__e3ae_&&a.__e3ae_.G(arguments);if(S.hasListeners(a,b)){var d=Fe(arguments,2),e=ef(a,b),f;for(f in e){var g=e[f];g&&g.j[fd](g.qb,d)}}};S.addDomListener=function(a,b,c,d){if(a[Jb]){var e=d?4:1;a[Jb](b,c,d);c=new df(a,b,c,e)}else a[hd]?(c=new df(a,b,c,2),a[hd]("on"+b,gf(c))):(a["on"+b]=c,c=new df(a,b,c,3));return c};
+S.addDomListenerOnce=function(a,b,c,d){var e=S[rc](a,b,function(){e[Ec]();return c[fd](this,arguments)},d);return e};S.qa=function(a,b,c,d){return S[rc](a,b,jf(c,d))};function jf(a,b){return function(c){return b[u](a,c,this)}}S.bind=function(a,b,c,d){return S[D](a,b,O(d,c))};S.addListenerOnce=function(a,b,c){var d=S[D](a,b,function(){d[Ec]();return c[fd](this,arguments)});return d};S.forward=function(a,b,c){return S[D](a,b,kf(b,c))};S.gb=function(a,b,c,d){return S[rc](a,b,kf(b,c,!d))};
+S.Pj=function(){var a=cf,b;for(b in a)a[b][Ec]();cf={};(a=Wd.CollectGarbage)&&a()};S.qp=function(){bf&&S[rc](m,"unload",S.Pj)};function kf(a,b,c){return function(d){var e=[b,a];ve(e,arguments);S[z][fd](this,e);c&&Pe[fd](null,arguments)}}function df(a,b,c,d){this.qb=a;this.G=b;this.j=c;this.K=null;this.M=d;this.id=++lf;ff(a,b)[this.id]=this;bf&&"tagName"in a&&(cf[this.id]=this)}var lf=0;
+function gf(a){return a.K=function(b){b||(b=m[bc]);if(b&&!b[Mb])try{ua(b,b[tb])}catch(c){}var d;d=a.j[fd](a.qb,[b]);return b&&"click"==b[Fc]&&(b=b[tb])&&"A"==b[vc]&&"javascript:void(0)"==b[wb]?!1:d}}Ja(df[v],function(){if(this.qb){switch(this.M){case 1:this.qb[Dc](this.G,this.j,!1);break;case 4:this.qb[Dc](this.G,this.j,!0);break;case 2:this.qb[Hc]("on"+this.G,this.K);break;case 3:this.qb["on"+this.G]=null}delete ff(this.qb,this.G)[this.id];this.K=this.j=this.qb=null;delete cf[this.id]}});function mf(a){return""+(ce(a)?de(a):a)};function T(){}N=T[v];N.get=function(a){var b=nf(this);a=a+"";b=Le(b,a);if(ye(b)){if(b){a=b.Kb;var b=b.kd,c="get"+of(a);return b[c]?b[c]():b.get(a)}return this[a]}};N.set=function(a,b){var c=nf(this);a=a+"";var d=Le(c,a);if(d){var c=d.Kb,d=d.kd,e="set"+of(c);if(d[e])d[e](b);else d.set(c,b)}else this[a]=b,c[a]=null,pf(this,a)};N.notify=function(a){var b=nf(this);a=a+"";(b=Le(b,a))?b.kd[gc](b.Kb):pf(this,a)};
+N.setValues=function(a){for(var b in a){var c=a[b],d="set"+of(b);if(this[d])this[d](c);else this.set(b,c)}};N.setOptions=T[v][uc];Ma(N,Qd());function pf(a,b){var c=b+"_changed";if(a[c])a[c]();else a[Ic](b);var c=qf(a,b),d;for(d in c){var e=c[d];pf(e.kd,e.Kb)}S[z](a,rf(b))}var sf={};function of(a){return sf[a]||(sf[a]=a[Gb](0,1)[Ob]()+a[Gb](1))}function rf(a){return a[td]()+"_changed"}function nf(a){a.gm_accessors_||(a.gm_accessors_={});return a.gm_accessors_}
+function qf(a,b){a[ud]||(a.gm_bindings_={});a[ud][Zc](b)||(a[ud][b]={});return a[ud][b]}T[v].bindTo=function(a,b,c,d){a=a+"";c=(c||a)+"";this[sc](a);var e={kd:this,Kb:a},f={kd:b,Kb:c,Th:e};nf(this)[a]=f;qf(b,c)[mf(e)]=e;d||pf(this,a)};T[v].unbind=function(a){var b=nf(this),c=b[a];c&&(c.Th&&delete qf(c.kd,c.Kb)[mf(c.Th)],this[a]=this.get(a),b[a]=null)};T[v].unbindAll=function(){tf(this,O(this[sc],this))};T[v].addListener=function(a,b){return S[D](this,a,b)};
+function tf(a,b){var c=nf(a),d;for(d in c)b(d)};var uf={Mq:"Point",Lq:"LineString",POLYGON:"Polygon"};function vf(){};function wf(a,b,c){a-=0;b-=0;c||(a=pe(a,-90,90),180!=b&&(b=qe(b,-180,180)));this.G=a;this.K=b}jb(wf[v],function(){return"("+this.lat()+", "+this.lng()+")"});wf[v].j=function(a){return a?re(this.lat(),a.lat())&&re(this.lng(),a.lng()):!1};wf[v].equals=wf[v].j;wf[v].lat=K("G");wf[v].lng=K("K");function xf(a){return se(a.G)}function yf(a){return se(a.K)}function zf(a,b){var c=k.pow(10,b);return k[x](a*c)/c}wf[v].toUrlValue=function(a){a=ye(a)?a:6;return zf(this.lat(),a)+","+zf(this.lng(),a)};function Af(a){this.message=a;va(this,"InvalidValueError");this.stack=ca().stack}Q(Af,ca);function Bf(a,b){var c="";if(null!=b){if(!(b instanceof Af))return b;c=": "+b.message}return new Af(a+c)}function Cf(a){if(!(a instanceof Af))throw a;m[Db]&&m[Db].assert&&m[Db].assert(!1,a[Nb]+": "+a.message)};function Df(a,b){return function(c){if(!c||!Ae(c))throw Bf("not an Object");var d={},e;for(e in c)if(d[e]=c[e],!b&&!a[e])throw Bf("unknown property "+e);for(e in a)try{var f=a[e](d[e]);if(ye(f)||fa[v][Zc][u](c,e))d[e]=a[e](d[e])}catch(g){throw Bf("in property "+e,g);}return d}}function Ef(a){try{return!!a[yc]}catch(b){return!1}}
+function Ff(a,b,c){return c?function(c){if(c instanceof a)return c;try{return new a(c)}catch(e){throw Bf("when calling new "+b,e);}}:function(c){if(c instanceof a)return c;throw Bf("not an instance of "+b);}}function Gf(a){return function(b){for(var c in a)if(a[c]==b)return b;throw Bf(b);}}function Hf(a){return function(b){if(!He(b))throw Bf("not an Array");return ue(b,function(b,d){try{return a(b)}catch(e){throw Bf("at index "+d,e);}})}}
+function If(a,b){return function(c){if(a(c))return c;throw Bf(b||""+c);}}function Mf(a){var b=arguments;return function(a){for(var d=[],e=0,f=b[I];e<f;++e){var g=b[e];try{(g.lh||g)(a)}catch(h){if(!(h instanceof Af))throw h;d[B](h.message);continue}return(g.then||g)(a)}throw Bf(d[Hb]("; and "));}}function Nf(a,b){return function(c){return b(a(c))}}function Of(a){return function(b){return null==b?b:a(b)}}function Pf(a){return function(b){if(b&&null!=b[a])return b;throw Bf("no "+a+" property");}}
+var Qf=If(ze,"not a number"),Rf=If(Ce,"not a string"),Sf=Of(Qf),Tf=Of(Rf),Uf=Of(If(De,"not a boolean"));var Vf=Df({lat:Qf,lng:Qf},!0);function Wf(a){try{if(a instanceof wf)return a;a=Vf(a);return new wf(a.lat,a.lng)}catch(b){throw Bf("not a LatLng or LatLngLiteral",b);}}var Xf=Hf(Wf);function Yf(a){this.j=Wf(a)}Q(Yf,vf);Ra(Yf[v],Rd("Point"));Yf[v].get=K("j");function Zf(a){if(a instanceof vf)return a;try{return new Yf(Wf(a))}catch(b){}throw Bf("not a Geometry or LatLng or LatLngLiteral object");}var $f=Hf(Zf);function ag(a,b){if(a)return function(){--a||b()};b();return Yd}function bg(a,b,c){var d=a[Tc]("head")[0];a=a[yd]("script");La(a,"text/javascript");a.charset="UTF-8";a.src=b;c&&bb(a,c);d[Xb](a);return a}function cg(a){for(var b="",c=0,d=arguments[I];c<d;++c){var e=arguments[c];e[I]&&"/"==e[0]?b=e:(b&&"/"!=b[b[I]-1]&&(b+="/"),b+=e)}return b};function dg(a){this.G=n;this.j={};this.K=a};function eg(){this.M={};this.G={};this.J={};this.j={};this.K=new fg}Zd(eg);function gg(a,b,c){a=a.K;b=a.G=new hg(new dg(b),c);c=0;for(var d=a.j[I];c<d;++c)a.j[c](b);gb(a.j,0)}eg[v].Od=function(a,b){var c=this,d=c.J;ig(c.K,function(e){for(var f=e.Si[a]||[],g=e.Cp[a]||[],h=d[a]=ag(f[I],function(){delete d[a];e.co(f[0],b);for(var c=0,h=g[I];c<h;++c){var l=g[c];d[l]&&d[l]()}}),l=0,r=f[I];l<r;++l)c.j[f[l]]&&h()})};
+function jg(a,b){a.M[b]||(a.M[b]=!0,ig(a.K,function(c){for(var d=c.Si[b],e=d?d[I]:0,f=0;f<e;++f){var g=d[f];a.j[g]||jg(a,g)}c=c.eo;c.j[b]||bg(c.G,cg(c.K,b)+".js")}))}function hg(a,b){var c=kg;this.eo=a;this.Si=c;var d={},e;for(e in c)for(var f=c[e],g=0,h=f[I];g<h;++g){var l=f[g];d[l]||(d[l]=[]);d[l][B](e)}this.Cp=d;this.co=b}function fg(){this.j=[]}function ig(a,b){a.G?b(a.G):a.j[B](b)};function lg(a,b,c){var d=eg.Hc();a=""+a;d.j[a]?b(d.j[a]):((d.G[a]=d.G[a]||[])[B](b),c||jg(d,a))}function mg(a,b){var c=eg.Hc(),d=""+a;c.j[d]=b;for(var e=c.G[d],f=e?e[I]:0,g=0;g<f;++g)e[g](b);delete c.G[d]}function ng(a,b,c){var d=[],e=ag(a[I],function(){b[fd](null,d)});Ue(a,function(a,b){lg(a,function(a){d[b]=a;e()},c)})};function og(a){a=a||{};this.K=a.id;this.j=null;try{this.j=a.geometry?Zf(a.geometry):null}catch(b){Cf(b)}this.G=a.properties||{}}N=og[v];qa(N,K("K"));N.getGeometry=K("j");N.setGeometry=function(a){var b=this.j;try{this.j=a?Zf(a):null}catch(c){Cf(c);return}S[z](this,"setgeometry",{feature:this,newGeometry:this.j,oldGeometry:b})};N.getProperty=function(a){return Le(this.G,a)};
+N.setProperty=function(a,b){if(void 0===b)this.removeProperty(a);else{var c=this.getProperty(a);this.G[a]=b;S[z](this,"setproperty",{feature:this,name:a,newValue:b,oldValue:c})}};N.removeProperty=function(a){var b=this.getProperty(a);delete this.G[a];S[z](this,"removeproperty",{feature:this,name:a,oldValue:b})};N.forEachProperty=function(a){for(var b in this.G)a(this.getProperty(b),b)};N.toGeoJson=function(a){var b=this;lg("data",function(c){c.Vm(b,a)})};function U(a,b){this.x=a;this.y=b}var qg=new U(0,0);jb(U[v],function(){return"("+this.x+", "+this.y+")"});U[v].j=function(a){return a?a.x==this.x&&a.y==this.y:!1};U[v].equals=U[v].j;U[v].round=function(){this.x=k[x](this.x);this.y=k[x](this.y)};U[v].Ue=Td(0);function rg(a){if(a instanceof U)return a;try{Df({x:Qf,y:Qf},!0)(a)}catch(b){throw Bf("not a Point",b);}return new U(a.x,a.y)};function W(a,b,c,d){Ua(this,a);cb(this,b);this.L=c||"px";this.J=d||"px"}var sg=new W(0,0);jb(W[v],function(){return"("+this[C]+", "+this[E]+")"});W[v].j=function(a){return a?a[C]==this[C]&&a[E]==this[E]:!1};W[v].equals=W[v].j;function tg(a){if(a instanceof W)return a;try{Df({height:Qf,width:Qf},!0)(a)}catch(b){throw Bf("not a Size",b);}return new W(a[C],a[E])};var ug={CIRCLE:0,FORWARD_CLOSED_ARROW:1,FORWARD_OPEN_ARROW:2,BACKWARD_CLOSED_ARROW:3,BACKWARD_OPEN_ARROW:4};function vg(a){return function(){return this.get(a)}}function wg(a,b){return b?function(c){try{this.set(a,b(c))}catch(d){Cf(Bf("set"+of(a),d))}}:function(b){this.set(a,b)}}function xg(a,b){ne(b,function(b,d){var e=vg(b);a["get"+of(b)]=e;d&&(e=wg(b,d),a["set"+of(b)]=e)})};function yg(a){this.j=a||[];zg(this)}Q(yg,T);N=yg[v];na(N,function(a){return this.j[a]});N.indexOf=function(a){for(var b=0,c=this.j[I];b<c;++b)if(a===this.j[b])return b;return-1};mb(N,function(a){for(var b=0,c=this.j[I];b<c;++b)a(this.j[b],b)});N.setAt=function(a,b){var c=this.j[a],d=this.j[I];if(a<d)this.j[a]=b,S[z](this,"set_at",a,c),this.L&&this.L(a,c);else{for(c=d;c<a;++c)this[Dd](c,void 0);this[Dd](a,b)}};N.insertAt=function(a,b){this.j[Lc](a,0,b);zg(this);S[z](this,"insert_at",a);this.G&&this.G(a)};
+N.removeAt=function(a){var b=this.j[a];this.j[Lc](a,1);zg(this);S[z](this,"remove_at",a,b);this.J&&this.J(a,b);return b};N.push=function(a){this[Dd](this.j[I],a);return this.j[I]};N.pop=function(){return this[Jc](this.j[I]-1)};Pa(N,K("j"));function zg(a){a.set("length",a.j[I])}Ha(N,function(){for(;this.get("length");)this.pop()});xg(yg[v],{length:null});function Ag(a){this.K=a||mf;this.G={}}Ag[v].ua=function(a){var b=this.G,c=this.K(a);b[c]||(b[c]=a,S[z](this,"insert",a),this.j&&this.j(a))};Ja(Ag[v],function(a){var b=this.G,c=this.K(a);b[c]&&(delete b[c],S[z](this,"remove",a),this[Wb]&&this[Wb](a))});$a(Ag[v],function(a){return!!this.G[this.K(a)]});mb(Ag[v],function(a){var b=this.G,c;for(c in b)a[u](this,b[c])});function Bg(a,b,c){this.heading=a;this.pitch=pe(b,-90,90);kb(this,k.max(0,c))}var Cg=Df({zoom:Sf,heading:Qf,pitch:Qf});function Dg(){nb(this,new T);this.J=null}Q(Dg,T);function Eg(){this.j=[];this.N=1}je(Eg,Ze);Eg[v].J=function(){var a=++this.N;$e(this,function(b){a==this.N&&b(this.get())},this)};function Fg(){}Q(Fg,T);function Gg(a){var b=a;if(a instanceof ia)b=ia(a[I]),Hg(b,a);else if(a instanceof fa){var c=b={},d;for(d in a)a[Zc](d)&&(c[d]=Gg(a[d]))}return b}function Hg(a,b){for(var c=0;c<b[I];++c)b[Zc](c)&&(a[c]=Gg(b[c]))}function Ig(a,b){a[b]||(a[b]=[]);return a[b]}function Jg(a,b){return a[b]?a[b][I]:0};function Kg(){}var Lg=new Kg,Mg=/'/g;Kg[v].j=function(a,b){var c=[];Ng(a,b,c);return c[Hb]("&")[tc](Mg,"%27")};function Ng(a,b,c){for(var d=1;d<b.U[I];++d){var e=b.U[d],f=a[d+b.T];if(null!=f&&e)if(3==e[Ub])for(var g=0;g<f[I];++g)Og(f[g],d,e,c);else Og(f,d,e,c)}}function Og(a,b,c,d){if("m"==c[Fc]){var e=d[I];Ng(a,c.S,d);d[Lc](e,0,[b,"m",d[I]-e][Hb](""))}else"b"==c[Fc]&&(a=a?"1":"0"),d[B]([b,c[Fc],aa(a)][Hb](""))};function Pg(a,b,c){for(var d in a)b[u](c,a[d],d,a)};var Qg;a:{var Rg=Wd[id];if(Rg){var Sg=Rg[vb];if(Sg){Qg=Sg;break a}}Qg=""}function Tg(a){return-1!=Qg[fc](a)};function Ug(){return Tg("Opera")||Tg("OPR")};function Vg(){return Tg("iPhone")&&!Tg("iPod")&&!Tg("iPad")};var Wg=Ug(),Xg=Tg("Trident")||Tg("MSIE"),Yg=Tg("Edge"),Zg=Tg("Gecko")&&!(-1!=Qg[td]()[fc]("webkit")&&!Tg("Edge"))&&!(Tg("Trident")||Tg("MSIE"))&&!Tg("Edge"),$g=-1!=Qg[td]()[fc]("webkit")&&!Tg("Edge"),ah=Tg("Macintosh"),bh=Tg("Windows"),ch=Tg("Linux")||Tg("CrOS"),dh=Tg("Android"),gh=Vg(),hh=Tg("iPad");function ih(){var a=Qg;if(Zg)return/rv\:([^\);]+)(\)|;)/[Vb](a);if(Yg)return/Edge\/([\d\.]+)/[Vb](a);if(Xg)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/[Vb](a);if($g)return/WebKit\/(\S+)/[Vb](a)}
+function jh(){var a=Wd[qd];return a?a[Ac]:void 0}var kh=function(){if(Wg&&Wd.opera){var a=Wd.opera[dd];return be(a)?a():a}var a="",b=ih();b&&(a=b?b[1]:"");return Xg&&(b=jh(),b>da(a))?ma(b):a}(),lh=Wd[qd],mh=lh&&Xg?jh()||("CSS1Compat"==lh[Cb]?ga(kh,10):5):void 0;function nh(a,b){this.j=a||0;this.G=b||0}nh[v].heading=K("j");nh[v].ob=Td(1);jb(nh[v],function(){return this.j+","+this.G});var oh=new nh;function ph(){}Q(ph,T);ph[v].set=function(a,b){if(null!=b&&!(b&&ze(b[Tb])&&b[Eb]&&b[Eb][C]&&b[Eb][E]&&b[zd]&&b[zd][fd]))throw ca("Wert zur Implementierung von google.maps.MapType erwartet");return T[v].set[fd](this,arguments)};function qh(a,b){-180==a&&180!=b&&(a=180);-180==b&&180!=a&&(b=180);this.j=a;this.G=b}function rh(a){return a.j>a.G}N=qh[v];Ya(N,function(){return 360==this.j-this.G});N.intersects=function(a){var b=this.j,c=this.G;return this[ad]()||a[ad]()?!1:rh(this)?rh(a)||a.j<=this.G||a.G>=b:rh(a)?a.j<=c||a.G>=b:a.j<=c&&a.G>=b};$a(N,function(a){-180==a&&(a=180);var b=this.j,c=this.G;return rh(this)?(a>=b||a<=c)&&!this[ad]():a>=b&&a<=c});
+Sa(N,function(a){this[cd](a)||(this[ad]()?this.j=this.G=a:sh(a,this.j)<sh(this.G,a)?this.j=a:this.G=a)});function th(a,b){return 1E-9>=k.abs(b.j-a.j)%360+k.abs(uh(b)-uh(a))}function sh(a,b){var c=b-a;return 0<=c?c:b+180-(a-180)}function uh(a){return a[ad]()?0:rh(a)?360-(a.j-a.G):a.G-a.j}N.lc=function(){var a=(this.j+this.G)/2;rh(this)&&(a=qe(a+180,-180,180));return a};function vh(a,b){this.G=a;this.j=b}N=vh[v];Ya(N,function(){return this.G>this.j});
+N.intersects=function(a){var b=this.G,c=this.j;return b<=a.G?a.G<=c&&a.G<=a.j:b<=a.j&&b<=c};$a(N,function(a){return a>=this.G&&a<=this.j});Sa(N,function(a){this[ad]()?this.j=this.G=a:a<this.G?this.G=a:a>this.j&&(this.j=a)});function wh(a){return a[ad]()?0:a.j-a.G}N.lc=function(){return(this.j+this.G)/2};function xh(a,b){if(a){b=b||a;var c=pe(a.lat(),-90,90),d=pe(b.lat(),-90,90);this.Ha=new vh(c,d);c=a.lng();d=b.lng();360<=d-c?this.Aa=new qh(-180,180):(c=qe(c,-180,180),d=qe(d,-180,180),this.Aa=new qh(c,d))}else this.Ha=new vh(1,-1),this.Aa=new qh(180,-180)}xh[v].getCenter=function(){return new wf(this.Ha.lc(),this.Aa.lc())};jb(xh[v],function(){return"("+this[ub]()+", "+this[Ab]()+")"});xh[v].toUrlValue=function(a){var b=this[ub](),c=this[Ab]();return[b[jc](a),c[jc](a)][Hb]()};
+xh[v].j=function(a){if(a){var b=this.Ha,c=a.Ha;a=(b[ad]()?c[ad]():1E-9>=k.abs(c.G-b.G)+k.abs(b.j-c.j))&&th(this.Aa,a.Aa)}else a=!1;return a};xh[v].equals=xh[v].j;N=xh[v];$a(N,function(a){return this.Ha[cd](a.lat())&&this.Aa[cd](a.lng())});N.intersects=function(a){return this.Ha[zb](a.Ha)&&this.Aa[zb](a.Aa)};Sa(N,function(a){this.Ha[Uc](a.lat());this.Aa[Uc](a.lng());return this});N.union=function(a){if(a[ad]())return this;this[Uc](a[ub]());this[Uc](a[Ab]());return this};
+N.getSouthWest=function(){return new wf(this.Ha.G,this.Aa.j,!0)};N.getNorthEast=function(){return new wf(this.Ha.j,this.Aa.G,!0)};N.toSpan=function(){return new wf(wh(this.Ha),uh(this.Aa),!0)};Ya(N,function(){return this.Ha[ad]()||this.Aa[ad]()});function yh(a){nb(this,a)}Q(yh,T);var zh=[];function Ah(){this.j={};this.K={};this.G={}}N=Ah[v];$a(N,function(a){return this.j[Zc](mf(a))});N.getFeatureById=function(a){return Le(this.G,a)};N.add=function(a){a=a||{};a=a instanceof og?a:new og(a);if(!this[cd](a)){var b=a[yb]();if(b){var c=this.getFeatureById(b);c&&this[Ec](c)}c=mf(a);this.j[c]=a;b&&(this.G[b]=a);var d=S[G](a,"setgeometry",this),e=S[G](a,"setproperty",this),f=S[G](a,"removeproperty",this);this.K[c]=function(){S[Sc](d);S[Sc](e);S[Sc](f)};S[z](this,"addfeature",{feature:a})}return a};
+Ja(N,function(a){var b=mf(a),c=a[yb]();if(this.j[b]){delete this.j[b];c&&delete this.G[c];if(c=this.K[b])delete this.K[b],c();S[z](this,"removefeature",{feature:a})}});mb(N,function(a){for(var b in this.j)a(this.j[b])});function Bh(){this.j={}}Bh[v].get=function(a){return this.j[a]};Bh[v].set=function(a,b){var c=this.j;c[a]||(c[a]={});me(c[a],b);S[z](this,"changed",a)};ta(Bh[v],function(a){delete this.j[a];S[z](this,"changed",a)});mb(Bh[v],function(a){ne(this.j,a)});function Ch(a){this.j=new Bh;var b=this;S[od](a,"addfeature",function(){lg("data",function(c){c.xm(b,a,b.j)})})}Q(Ch,T);Ch[v].overrideStyle=function(a,b){this.j.set(mf(a),b)};Ch[v].revertStyle=function(a){a?this.j[Kb](mf(a)):this.j[rd](O(this.j[Kb],this.j))};function Dh(a){this.j=[];try{this.j=$f(a)}catch(b){Cf(b)}}Q(Dh,vf);Ra(Dh[v],Rd("GeometryCollection"));pa(Dh[v],function(){return this.j[I]});na(Dh[v],function(a){return this.j[a]});Pa(Dh[v],function(){return this.j[Lb]()});function Eh(a){this.j=Xf(a)}Q(Eh,vf);Ra(Eh[v],Rd("LineString"));pa(Eh[v],function(){return this.j[I]});na(Eh[v],function(a){return this.j[a]});Pa(Eh[v],function(){return this.j[Lb]()});var Fh=Hf(Ff(Eh,"google.maps.Data.LineString",!0));function Gh(a){this.j=Fh(a)}Q(Gh,vf);Ra(Gh[v],Rd("MultiLineString"));pa(Gh[v],function(){return this.j[I]});na(Gh[v],function(a){return this.j[a]});Pa(Gh[v],function(){return this.j[Lb]()});function Hh(a){this.j=Xf(a)}Q(Hh,vf);Ra(Hh[v],Rd("MultiPoint"));pa(Hh[v],function(){return this.j[I]});na(Hh[v],function(a){return this.j[a]});Pa(Hh[v],function(){return this.j[Lb]()});function Ih(a){this.j=Xf(a)}Q(Ih,vf);Ra(Ih[v],Rd("LinearRing"));pa(Ih[v],function(){return this.j[I]});na(Ih[v],function(a){return this.j[a]});Pa(Ih[v],function(){return this.j[Lb]()});var Jh=Hf(Ff(Ih,"google.maps.Data.LinearRing",!0));function Kh(a){this.j=Jh(a)}Q(Kh,vf);Ra(Kh[v],Rd("Polygon"));pa(Kh[v],function(){return this.j[I]});na(Kh[v],function(a){return this.j[a]});Pa(Kh[v],function(){return this.j[Lb]()});var Lh=Hf(Ff(Kh,"google.maps.Data.Polygon",!0));function Mh(a){this.j=Lh(a)}Q(Mh,vf);Ra(Mh[v],Rd("MultiPolygon"));pa(Mh[v],function(){return this.j[I]});na(Mh[v],function(a){return this.j[a]});Pa(Mh[v],function(){return this.j[Lb]()});var Nh=Df({source:Rf,webUrl:Tf,iosDeepLinkId:Tf});var Oh=Nf(Df({placeId:Tf,query:Tf,location:Wf}),function(a){if(a.placeId&&a.query)throw Bf("cannot set both placeId and query");if(!a.placeId&&!a.query)throw Bf("must set one of placeId or query");return a});function Ph(a){a=a||{};a.clickable=we(a.clickable,!0);a.visible=we(a.visible,!0);this[uc](a);lg("marker",Be)}Q(Ph,T);
+xg(Ph[v],{position:Of(Wf),title:Tf,icon:Of(Mf(Rf,{lh:Pf("url"),then:Df({url:Rf,scaledSize:Of(tg),size:Of(tg),origin:Of(rg),anchor:Of(rg),labelOrigin:Of(rg),path:If(xe)},!0)},{lh:Pf("path"),then:Df({path:Mf(Rf,Gf(ug)),anchor:Of(rg),labelOrigin:Of(rg),fillColor:Tf,fillOpacity:Sf,rotation:Sf,scale:Sf,strokeColor:Tf,strokeOpacity:Sf,strokeWeight:Sf,url:If(xe)},!0)})),label:Of(Mf(Rf,{lh:Pf("text"),then:Df({text:Rf,fontSize:Tf,fontWeight:Tf,fontFamily:Tf},!0)})),shadow:le,shape:le,cursor:Tf,clickable:Uf,
+animation:le,draggable:Uf,visible:Uf,flat:le,zIndex:Sf,opacity:Sf,place:Of(Oh),attribution:Of(Nh)});var kg={main:[],common:["main"],util:["common"],adsense:["main"],adsense_impl:["util"],controls:["util"],data:["util"],directions:["util","geometry"],distance_matrix:["util"],drawing:["main"],drawing_impl:["controls"],elevation:["util","geometry"],geocoder:["util"],geojson:["main"],imagery_viewer:["main"],geometry:["main"],infowindow:["util"],kml:["onion","util","map"],layers:["map"],loom:["onion"],map:["common"],marker:["util"],maxzoom:["util"],onion:["util","map"],overlay:["common"],panoramio:["main"],
+places:["main"],places_impl:["controls"],poly:["util","map","geometry"],search:["main"],search_impl:["onion"],stats:["util"],streetview:["util","geometry"],usage:["util"],visualization:["main"],visualization_impl:["onion"],weather:["main"],zombie:["main"]};var Qh={};function Rh(a){gg(eg.Hc(),a,function(a,c){Qh[a](c)})}var Sh=Wd[ed].maps,Th=eg.Hc(),Wh=O(Th.Od,Th);Sh.__gjsload__=Wh;ne(Sh.modules,Wh);delete Sh.modules;var Xh=Of(Ff(yh,"Map"));var Yh=Of(Ff(Dg,"StreetViewPanorama"));function Zh(a){nb(this,{set:null});Ph[u](this,a)}Q(Zh,Ph);xa(Zh[v],function(){this[vd].set&&this[vd].set[Ec](this);var a=this.get("map");this[vd].set=a&&a[vd].jd;this[vd].set&&this[vd].set.ua(this)});Zh.MAX_ZINDEX=1E6;xg(Zh[v],{map:Mf(Xh,Yh)});function $h(a){a=a||{};a.visible=we(a.visible,!0);return a}function ai(a){return a&&a[kd]||6378137}function bi(a){return a instanceof yg?ci(a):new yg(Xf(a))}function di(a){var b;He(a)?0==ke(a)?b=!0:(b=a instanceof yg?a[sb](0):a[0],b=He(b)):b=!1;return b?a instanceof yg?ei(ci)(a):new yg(Hf(bi)(a)):new yg([bi(a)])}function ei(a){return function(b){if(!(b instanceof yg))throw Bf("not an MVCArray");b[rd](function(b,d){try{a(b)}catch(e){throw Bf("at index "+d,e);}});return b}}var ci=ei(Ff(wf,"LatLng"));function fi(a){this.set("latLngs",new yg([new yg]));this[uc]($h(a));lg("poly",Be)}Q(fi,T);xa(fi[v],db(fi[v],function(){var a=this;lg("poly",function(b){b.em(a)})}));Ea(fi[v],function(){return this.get("latLngs")[sb](0)});eb(fi[v],function(a){try{this.get("latLngs")[wc](0,bi(a))}catch(b){Cf(b)}});xg(fi[v],{draggable:Uf,editable:Uf,map:Xh,visible:Uf});function gi(a){fi[u](this,a)}Q(gi,fi);gi[v].hb=!0;gi[v].getPaths=function(){return this.get("latLngs")};gi[v].setPaths=function(a){this.set("latLngs",di(a))};function hi(a){fi[u](this,a)}Q(hi,fi);hi[v].hb=!1;var ii="click dblclick mousedown mousemove mouseout mouseover mouseup rightclick".split(" ");function ji(a,b,c){function d(a){if(!a)throw Bf("not a Feature");if("Feature"!=a[Fc])throw Bf('type != "Feature"');var b=a.geometry;try{b=null==b?null:e(b)}catch(d){throw Bf('in property "geometry"',d);}var f=a.properties||{};if(!Ae(f))throw Bf("properties is not an Object");var g=c.idPropertyName;a=g?f[g]:a.id;if(null!=a&&!ze(a)&&!Ce(a))throw Bf((g||"id")+" is not a string or number");return{id:a,geometry:b,properties:f}}function e(a){if(null==a)throw Bf("is null");var b=(a[Fc]+"")[td](),c=a.coordinates;
+try{switch(b){case "point":return new Yf(h(c));case "multipoint":return new Hh(r(c));case "linestring":return g(c);case "multilinestring":return new Gh(t(c));case "polygon":return f(c);case "multipolygon":return new Mh(y(c))}}catch(d){throw Bf('in property "coordinates"',d);}if("geometrycollection"==b)try{return new Dh(A(a.geometries))}catch(e){throw Bf('in property "geometries"',e);}throw Bf("invalid type");}function f(a){return new Kh(w(a))}function g(a){return new Eh(r(a))}function h(a){a=l(a);
+return Wf({lat:a[1],lng:a[0]})}if(!b)return[];c=c||{};var l=Hf(Qf),r=Hf(h),t=Hf(g),w=Hf(function(a){a=r(a);if(!a[I])throw Bf("contains no elements");if(!a[0].j(a[a[I]-1]))throw Bf("first and last positions are not equal");return new Ih(a[Lb](0,-1))}),y=Hf(f),A=Hf(e),H=Hf(d);if("FeatureCollection"==b[Fc]){b=b[Wc];try{return ue(H(b),function(b){return a.add(b)})}catch(F){throw Bf('in property "features"',F);}}if("Feature"==b[Fc])return[a.add(d(b))];throw Bf("not a Feature or FeatureCollection");};function ki(a){var b=this;this[uc](a||{});this.j=new Ah;S[G](this.j,"addfeature",this);S[G](this.j,"removefeature",this);S[G](this.j,"setgeometry",this);S[G](this.j,"setproperty",this);S[G](this.j,"removeproperty",this);this.G=new Ch(this.j);this.G[p]("map",this);this.G[p]("style",this);R(ii,function(a){S[G](b.G,a,b)});this.J=!1}Q(ki,T);N=ki[v];$a(N,function(a){return this.j[cd](a)});N.getFeatureById=function(a){return this.j.getFeatureById(a)};N.add=function(a){return this.j.add(a)};Ja(N,function(a){this.j[Ec](a)});
+mb(N,function(a){this.j[rd](a)});N.addGeoJson=function(a,b){return ji(this.j,a,b)};N.loadGeoJson=function(a,b,c){var d=this.j;lg("data",function(e){e.Wm(d,a,b,c)})};N.toGeoJson=function(a){var b=this.j;lg("data",function(c){c.Um(b,a)})};N.overrideStyle=function(a,b){this.G.overrideStyle(a,b)};N.revertStyle=function(a){this.G.revertStyle(a)};N.controls_changed=function(){this.get("controls")&&li(this)};N.drawingMode_changed=function(){this.get("drawingMode")&&li(this)};
+function li(a){a.J||(a.J=!0,lg("drawing_impl",function(b){b.Gn(a)}))}xg(ki[v],{map:Xh,style:le,controls:Of(Hf(Gf(uf))),controlPosition:Of(Gf(Vd)),drawingMode:Of(Gf(uf))});function mi(a){this.H=a||[]}function ni(a){this.H=a||[]}mi[v].P=K("H");ni[v].P=K("H");var oi=new mi,pi=new mi;function qi(a){this.H=a||[]}function ri(a){this.H=a||[]}function si(a){this.H=a||[]}qi[v].P=K("H");var ti=new ri;ri[v].P=K("H");var ui=new mi,vi=new qi;si[v].P=K("H");var wi=new ni,xi=new si;var yi={METRIC:0,IMPERIAL:1},zi={DRIVING:"DRIVING",WALKING:"WALKING",BICYCLING:"BICYCLING",TRANSIT:"TRANSIT"};var Ai={BUS:"BUS",RAIL:"RAIL",SUBWAY:"SUBWAY",TRAIN:"TRAIN",TRAM:"TRAM"};var Bi={LESS_WALKING:"LESS_WALKING",FEWER_TRANSFERS:"FEWER_TRANSFERS"};var Ci=Ff(xh,"LatLngBounds");var Di=Df({routes:Hf(If(Ae))},!0);function Ei(){}Ei[v].route=function(a,b){lg("directions",function(c){c.vj(a,b,!0)})};function Fi(a){function b(){d||(d=!0,lg("infowindow",function(a){a.Vl(c)}))}m[oc](function(){lg("infowindow",Be)},100);var c=this,d=!1;S[od](this,"anchor_changed",b);S[od](this,"map_changed",b);this[uc](a)}Q(Fi,T);xg(Fi[v],{content:Mf(Tf,If(Ef)),position:Of(Wf),size:Of(tg),map:Mf(Xh,Yh),anchor:Of(Ff(T,"MVCObject")),zIndex:Sf});Fi[v].open=function(a,b){this.set("anchor",b);this.set("map",a)};Fi[v].close=function(){this.set("map",null)};function Gi(a){this[uc](a)}Q(Gi,T);Ma(Gi[v],function(a){if("map"==a||"panel"==a){var b=this;lg("directions",function(c){c.Hn(b,a)})}});xg(Gi[v],{directions:Di,map:Xh,panel:Of(If(Ef)),routeIndex:Sf});function Hi(){}Hi[v].getDistanceMatrix=function(a,b){lg("distance_matrix",function(c){c.an(a,b)})};function Ii(){}Ii[v].getElevationAlongPath=function(a,b){lg("elevation",function(c){c.bn(a,b)})};Ii[v].getElevationForLocations=function(a,b){lg("elevation",function(c){c.cn(a,b)})};var Ji,Ki;function Li(){lg("geocoder",Be)}Li[v].geocode=function(a,b){lg("geocoder",function(c){c.geocode(a,b)})};function Mi(a,b,c){this.X=null;this.set("url",a);this.set("bounds",b);this[uc](c)}Q(Mi,T);xa(Mi[v],function(){var a=this;lg("kml",function(b){b.$l(a)})});xg(Mi[v],{map:Xh,url:null,bounds:null,opacity:Sf});var Ni={UNKNOWN:"UNKNOWN",OK:Ld,INVALID_REQUEST:Gd,DOCUMENT_NOT_FOUND:"DOCUMENT_NOT_FOUND",FETCH_ERROR:"FETCH_ERROR",INVALID_DOCUMENT:"INVALID_DOCUMENT",DOCUMENT_TOO_LARGE:"DOCUMENT_TOO_LARGE",LIMITS_EXCEEDED:"LIMITS_EXECEEDED",TIMED_OUT:"TIMED_OUT"};function Oi(a,b){if(Ce(a))this.set("url",a),this[uc](b);else this[uc](a)}Q(Oi,T);Oi[v].url_changed=Oi[v].driveFileId_changed=xa(Oi[v],sa(Oi[v],function(){var a=this;lg("kml",function(b){b.bm(a)})}));xg(Oi[v],{map:Xh,defaultViewport:null,metadata:null,status:null,url:Tf,screenOverlays:Uf,zIndex:Sf});function Pi(){this.X=null;lg("layers",Be)}Q(Pi,T);xa(Pi[v],function(){var a=this;lg("layers",function(b){b.Wl(a)})});xg(Pi[v],{map:Xh});function Qi(){this.X=null;lg("layers",Be)}Q(Qi,T);xa(Qi[v],function(){var a=this;lg("layers",function(b){b.gm(a)})});xg(Qi[v],{map:Xh});function Ri(){this.X=null;lg("layers",Be)}Q(Ri,T);xa(Ri[v],function(){var a=this;lg("layers",function(b){b.hm(a)})});xg(Ri[v],{map:Xh});var Si={NEAREST:"nearest",BEST:"best"};var Ti={DEFAULT:"default",OUTDOOR:"outdoor"};function Ui(a,b){Dg[u](this);nb(this,new T);var c=this.controls=[];ne(Vd,function(a,b){c[b]=new yg});this.G=!0;this.j=a;this[Bd](new Bg(0,0,1));b&&b.bc&&!ze(b.bc[md])&&kb(b.bc,ze(b[md])?b[md]:1);this[uc](b);void 0==this[ec]()&&this[lc](!0);this[vd].jd=b&&b.jd||new Ag;var d=this;S[od](this,"pano_changed",Ie(function(){lg("marker",function(a){a.Sh(d[vd].jd,d)})}))}Q(Ui,Dg);db(Ui[v],function(){var a=this;!a.L&&a[ec]()&&(a.L=!0,lg("streetview",function(b){b.Wo(a)}))});
+xg(Ui[v],{visible:Uf,pano:Tf,position:Of(Wf),pov:Of(Cg),photographerPov:null,location:null,links:Hf(If(Ae)),status:null,zoom:Sf,enableCloseButton:Uf});Ui[v].getContainer=K("j");Ui[v].registerPanoProvider=wg("panoProvider");function Vi(){this.M=[];this.G=this.j=this.K=null}N=Vi[v];N.ve=Td(2);N.Nb=Td(3);N.xd=Td(4);N.Yd=Td(5);N.Xd=Td(6);function Wi(a,b,c){this.ra=b;this.lg=new Ag;this.Y=new yg;this.R=new Ag;this.V=new Ag;this.L=new Ag;this.jd=new Ag;this.J=[];var d=this.jd;d.j=function(){delete d.j;lg("marker",Ie(function(b){b.Sh(d,a)}))};this.G=new Ui(b,{visible:!1,enableCloseButton:!0,jd:d});this.G[p]("reportErrorControl",a);this.G.G=!1;this.j=new Vi;this.ta=c}Q(Wi,Fg);function Xi(a){this.H=a||[]}var Yi;function Zi(a){this.H=a||[]}var $i;function aj(a){this.H=a||[]}var bj;function cj(a){this.H=a||[]}var dj;function ej(a){this.H=a||[]}var fj;function kj(a){this.H=a||[]}var lj;Xi[v].P=K("H");var mj=new Zi,nj=new aj,oj=new cj,pj=new ej,qj=new kj;Zi[v].P=K("H");aj[v].P=K("H");cj[v].P=K("H");ej[v].P=K("H");kj[v].P=K("H");function rj(a){this.H=a||[]}rj[v].P=K("H");var sj=new rj,tj=new rj;function uj(a){this.H=a||[]}function vj(a){this.H=a||[]}function wj(a){this.H=a||[]}function xj(a){this.H=a||[]}function yj(a){this.H=a||[]}function zj(a){this.H=a||[]}function Aj(a){this.H=a||[]}function Bj(a){this.H=a||[]}uj[v].P=K("H");lb(uj[v],function(a){return Ig(this.H,0)[a]});Da(uj[v],function(a,b){Ig(this.H,0)[a]=b});vj[v].P=K("H");wj[v].P=K("H");var Cj=new uj,Dj=new uj,Ej=new uj,Fj=new uj,Gj=new uj,Hj=new uj,Ij=new uj,Jj=new uj,Kj=new uj,Lj=new uj,Mj=new uj,Nj=new uj;xj[v].P=K("H");
+function Oj(a){a=a.H[0];return null!=a?a:""}function Pj(a){a=a.H[1];return null!=a?a:""}function Qj(){var a=Rj(Sj).H[9];return null!=a?a:""}function Tj(a){a=a.H[7];return null!=a?a:""}function Uj(a){a=a.H[12];return null!=a?a:""}yj[v].P=K("H");function Vj(a){a=a.H[0];return null!=a?a:""}function Wj(a){a=a.H[1];return null!=a?a:""}zj[v].P=K("H");function Xj(){var a=Sj.H[4],a=(a?new zj(a):Yj).H[0];return null!=a?a:0}Aj[v].P=K("H");function Zj(){var a=Sj.H[5];return null!=a?a:1}
+function ak(){var a=Sj.H[0];return null!=a?a:1}function bk(a){a=a.H[6];return null!=a?a:""}function ck(){var a=Sj.H[11];return null!=a?a:""}function dk(){var a=Sj.H[16];return null!=a?a:""}var ek=new wj,fk=new vj,gk=new xj;function Rj(a){return(a=a.H[2])?new xj(a):gk}var hk=new yj;function ik(){var a=Sj.H[3];return a?new yj(a):hk}var Yj=new zj,jk=new Bj,kk=new Xi;function lk(){var a=Sj.H[33];return a?new Xi(a):kk}function mk(a){return Ig(Sj.H,8)[a]}Bj[v].P=K("H");var Sj,nk={};function ok(){this.j=new U(128,128);this.K=256/360;this.M=256/(2*k.PI);this.G=!0}ok[v].fromLatLngToPoint=function(a,b){var c=b||new U(0,0),d=this.j;c.x=d.x+a.lng()*this.K;var e=pe(k.sin(se(a.lat())),-(1-1E-15),1-1E-15);c.y=d.y+.5*k.log((1+e)/(1-e))*-this.M;return c};ok[v].fromPointToLatLng=function(a,b){var c=this.j;return new wf(te(2*k[$b](k.exp((a.y-c.y)/-this.M))-k.PI/2),(a.x-c.x)/this.K,b)};function pk(a){this.$=this.Z=la;this.ia=this.ja=-la;R(a,O(this[Uc],this))}function qk(a,b,c,d){var e=new pk;e.$=a;e.Z=b;e.ia=c;e.ja=d;return e}Ya(pk[v],function(){return!(this.$<this.ia&&this.Z<this.ja)});Sa(pk[v],function(a){a&&(this.$=k.min(this.$,a.x),this.ia=k.max(this.ia,a.x),this.Z=k.min(this.Z,a.y),this.ja=k.max(this.ja,a.y))});pk[v].getCenter=function(){return new U((this.$+this.ia)/2,(this.Z+this.ja)/2)};var rk=qk(-la,-la,la,la),sk=qk(0,0,0,0);function tk(a,b,c){if(a=a[Bc](b))c=k.pow(2,c),a.x*=c,a.y*=c;return a};function uk(a,b){var c=a.lat()+te(b);90<c&&(c=90);var d=a.lat()-te(b);-90>d&&(d=-90);var e=k.sin(b),f=k.cos(se(a.lat()));if(90==c||-90==d||1E-6>f)return new xh(new wf(d,-180),new wf(c,180));e=te(k[ic](e/f));return new xh(new wf(d,a.lng()-e),new wf(c,a.lng()+e))};function vk(a){this.Gq=a||0;S[J](this,"forceredraw",this,this.Qb)}Q(vk,T);vk[v].ka=function(){var a=this;a.V||(a.V=m[oc](function(){a.V=void 0;a.va()},a.Gq))};vk[v].Qb=function(){this.V&&m[Bb](this.V);this.V=void 0;this.va()};function wk(a,b){var c=a[q];Ua(c,b[C]+b.L);cb(c,b[E]+b.J)}function xk(a){return new W(a[ac],a[Xc])};function yk(a){this.H=a||[]}var zk;function Ak(a){this.H=a||[]}var Bk;yk[v].P=K("H");Ak[v].P=K("H");var Ck=new yk;function Dk(){Eg[u](this)}je(Dk,Eg);Dk[v].set=function(a){this.Bj(a);this[gc]()};Dk[v].notify=function(){this.J()};function Ek(a){Eg[u](this);this.G=a}je(Ek,Dk);Ek[v].get=K("G");Ek[v].Bj=function(a){this.G=a};function Gk(a){this.H=a||[]}var Hk;function Ik(a){this.H=a||[]}var Jk;Gk[v].P=K("H");Ik[v].P=K("H");function Kk(a){this.H=a||[]}var Lk;Kk[v].P=K("H");Ca(Kk[v],function(){var a=this.H[2];return null!=a?a:0});fb(Kk[v],function(a){this.H[2]=a});var Mk=new Gk,Nk=new Ik,Ok=new Ak,Pk=new Xi;function Qk(a,b,c,d){vk[u](this);this.N=b;this.L=new ok;this.O=c+"/maps/api/js/StaticMapService.GetMapImage";this.G=this.j=null;this.J=d;this.set("div",a);this.set("loading",!0)}Q(Qk,vk);var Rk={roadmap:0,satellite:2,hybrid:3,terrain:4},Sk={0:1,2:2,3:2,4:2};N=Qk[v];N.pi=vg("center");N.Eh=vg("zoom");function Tk(a){var b=a.get("tilt")||a.get("mapMaker")||ke(a.get("styles"));a=a.get("mapTypeId");return b?null:Rk[a]}
+Ma(N,function(){var a=this.pi(),b=this.Eh(),c=Tk(this);if(a&&!a.j(this.W)||this.R!=b||this.Y!=c)Uk(this.G),this.ka(),this.R=b,this.Y=c;this.W=a});function Uk(a){a[kc]&&a[kc][zc](a)}
+N.va=function(){var a="",b=this.pi(),c=this.Eh(),d=Tk(this),e=this.get("size");if(b&&ha(b.lat())&&ha(b.lng())&&1<c&&null!=d&&e&&e[C]&&e[E]&&this.j){wk(this.j,e);var f;(b=tk(this.L,b,c))?(f=new pk,f.$=k[x](b.x-e[C]/2),f.ia=f.$+e[C],f.Z=k[x](b.y-e[E]/2),f.ja=f.Z+e[E]):f=null;b=Sk[d];if(f){var a=new Kk,g=1<(22>c&&Ke())?2:1,h;a.H[0]=a.H[0]||[];h=new Gk(a.H[0]);h.H[0]=f.$*g;h.H[1]=f.Z*g;a.H[1]=b;a[jd](c);a.H[3]=a.H[3]||[];c=new Ik(a.H[3]);c.H[0]=(f.ia-f.$)*g;c.H[1]=(f.ja-f.Z)*g;1<g&&(c.H[2]=2);a.H[4]=
+a.H[4]||[];c=new Ak(a.H[4]);c.H[0]=d;c.H[4]=Oj(Rj(Sj));c.H[5]=Pj(Rj(Sj))[td]();c.H[9]=!0;c.H[11]=!0;d=this.O+unescape("%3F");Lk||(c=[],Lk={T:-1,U:c},Hk||(b=[],Hk={T:-1,U:b},b[1]={type:"i",label:1,I:0},b[2]={type:"i",label:1,I:0}),c[1]={type:"m",label:1,I:Mk,S:Hk},c[2]={type:"e",label:1,I:0},c[3]={type:"u",label:1,I:0},Jk||(b=[],Jk={T:-1,U:b},b[1]={type:"u",label:1,I:0},b[2]={type:"u",label:1,I:0},b[3]={type:"e",label:1,I:1}),c[4]={type:"m",label:1,I:Nk,S:Jk},Bk||(b=[],Bk={T:-1,U:b},b[1]={type:"e",
+label:1,I:0},b[2]={type:"b",label:1,I:!1},b[3]={type:"b",label:1,I:!1},b[5]={type:"s",label:1,I:""},b[6]={type:"s",label:1,I:""},zk||(f=[],zk={T:-1,U:f},f[1]={type:"e",label:3},f[2]={type:"b",label:1,I:!1}),b[9]={type:"m",label:1,I:Ck,S:zk},b[10]={type:"b",label:1,I:!1},b[11]={type:"b",label:1,I:!1},b[12]={type:"b",label:1,I:!1},b[100]={type:"b",label:1,I:!1}),c[5]={type:"m",label:1,I:Ok,S:Bk},Yi||(b=[],Yi={T:-1,U:b},$i||(f=[],$i={T:-1,U:f},f[1]={type:"b",label:1,I:!1}),b[1]={type:"m",label:1,I:mj,
+S:$i},bj||(f=[],bj={T:-1,U:f},f[1]={type:"b",label:1,I:!1},f[2]={type:"b",label:1,I:!1},f[4]={type:"b",label:1,I:!1},f[5]={type:"b",label:1,I:!1}),b[8]={type:"m",label:1,I:nj,S:bj},dj||(f=[],dj={T:-1,U:f},f[1]={type:"b",label:1,I:!1}),b[9]={type:"m",label:1,I:oj,S:dj},fj||(f=[],fj={T:-1,U:f},f[1]={type:"b",label:1,I:!1},f[3]={type:"b",label:1,I:!1},f[4]={type:"j",label:1,I:""},f[5]={type:"j",label:1,I:""}),b[11]={type:"m",label:1,I:pj,S:fj},lj||(f=[],lj={T:-1,U:f},f[1]={type:"b",label:1,I:!1},f[2]=
+{type:"b",label:1,I:!1}),b[10]={type:"m",label:1,I:qj,S:lj}),c[6]={type:"m",label:1,I:Pk,S:Yi});a=Lg.j(a.H,Lk);a=this.N(d+a)}}this.G&&e&&(wk(this.G,e),e=a,a=this.G,e!=a.src?(Uk(a),Xa(a,Ee(this,this.Fh,!0)),bb(a,Ee(this,this.Fh,!1)),a.src=e):!a[kc]&&e&&this.j[Xb](a))};N.Fh=function(a){var b=this.G;Xa(b,null);bb(b,null);a&&(b[kc]||this.j[Xb](b),wk(b,this.get("size")),S[z](this,"staticmaploaded"),this.J.set(ie()));this.set("loading",!1)};
+N.div_changed=function(){var a=this.get("div"),b=this.j;if(a)if(b)a[Xb](b);else{b=this.j=n[yd]("div");Ta(b[q],"hidden");var c=this.G=n[yd]("img");S[rc](b,"contextmenu",Oe);c.ontouchstart=c.ontouchmove=c.ontouchend=c.ontouchcancel=Me;wk(c,sg);a[Xb](b);this.va()}else b&&(Uk(b),this.j=null)};function Vk(a){this.j=[];this.G=a||Ge()}var Wk;function Xk(a,b,c){c=c||Ge()-a.G;Wk&&a.j[B]([b,c]);return c}Vk[v].getTick=function(a){for(var b=this.j,c=0,d=b[I];c<d;++c){var e=b[c];if(e[0]==a)return e[1]}};var Yk;function Zk(a,b,c,d,e){var f=nk[43]?Uj(Rj(Sj)):Tj(Rj(Sj));this.K=a;this.M=f;this.j=b;this.J=c;this.G=d;this.L=Xd(e)?e:ie()}function $k(a,b,c){c=(Xd(c)?c:ie())-a.L;var d=a.M+"/csi?v=2&s=mapsapi3&action="+a.K+"&rt="+b+"."+k[x](c);Pg(a.J,function(a,b){d+="&"+aa(b)+"="+aa(a)});a.j&&(d+="&e="+a.j);a.G[yd]("img").src=d;Wd.puppet&&(a=Wd.captureCSI)&&a(d)}
+function al(a,b){var c=b||{},d=bk(Sj),e=lk(),f=[];d&&f[B](d);Ue(e.P(),function(a,b){a&&Ue(a,function(a,c){null!=a&&f[B](b+1+"_"+(c+1)+"_"+a)})});return new Zk(a,f[Hb](","),c.$q||{},c[qd]||n,c[Vc])};function bl(){this.G=al("apiboot2",{startTime:cl});$k(this.G,"main");this.j=!1}function dl(){var a=el;a.j||(a.j=!0,$k(a.G,"firstmap"))};var fl,cl,el;function gl(a,b){var c=new hl(b);for(c.j=[a];ke(c.j);){var d=c,e=c.j[Yc]();d.G(e);for(e=e[mc];e;e=e[Cd])1==e[nd]&&d.j[B](e)}}function hl(a){this.G=a;this.j=null};var il=Wd[qd]&&Wd[qd][yd]("div");function jl(a){for(var b;b=a[mc];)kl(b),a[zc](b)}function kl(a){gl(a,function(a){S[gd](a)})};function ll(a,b){var c=ie();Yk&&Xk(Yk,"mc");el&&dl();var d=new af;yh[u](this,new Wi(this,a,d));var e=b||{};ye(e.mapTypeId)||(e.mapTypeId="roadmap");this[uc](e);this[vd].oa=e.oa;this.mapTypes=new ph;this.features=new T;zh[B](a);this[gc]("streetView");var f=xk(a);e.noClear||jl(a);var g=this[vd],h=Wd.gm_force_experiments;h&&(g.J=h);var l=null;!ml(e.useStaticMap,f)||!Sj||0<=Te(g.J,"sm-none")||(l=new Qk(a,Ji,Qj(),new Ek(null)),S[G](l,"staticmaploaded",this),S[od](l,"staticmaploaded",function(){Xk(Yk,"smv")}),
+l.set("size",f),l[p]("center",this),l[p]("zoom",this),l[p]("mapTypeId",this),l[p]("styles",this),l[p]("mapMaker",this));this.overlayMapTypes=new yg;var r=this.controls=[];ne(Vd,function(a,b){r[b]=new yg});var t=this,w=!0;lg("map",function(a){a.G(t,e,l,w,c,d)});w=!1;Ka(this,new ki({map:this}))}Q(ll,yh);N=ll[v];N.streetView_changed=function(){this.get("streetView")||this.set("streetView",this[vd].G)};pb(N,function(){return this[vd].ra});
+N.panBy=function(a,b){var c=this[vd];lg("map",function(){S[z](c,"panby",a,b)})};N.panTo=function(a){var b=this[vd];a=Wf(a);lg("map",function(){S[z](b,"panto",a)})};N.panToBounds=function(a){var b=this[vd];lg("map",function(){S[z](b,"pantolatlngbounds",a)})};N.fitBounds=function(a){var b=this;lg("map",function(c){c.fitBounds(b,a)})};function ml(a,b){if(ye(a))return!!a;var c=b[C],d=b[E];return 384E3>=c*d&&800>=c&&800>=d}
+xg(ll[v],{bounds:null,streetView:Yh,center:Of(Wf),zoom:Sf,mapTypeId:Tf,projection:null,heading:Sf,tilt:Sf});function nl(){lg("maxzoom",Be)}nl[v].getMaxZoomAtLatLng=function(a,b){lg("maxzoom",function(c){c.getMaxZoomAtLatLng(a,b)})};function ol(a,b){if(!a||Ce(a)||ze(a))this.set("tableId",a),this[uc](b);else this[uc](a)}Q(ol,T);Ma(ol[v],function(a){if("suppressInfoWindows"!=a&&"clickable"!=a){var b=this;lg("onion",function(a){a.Zl(b)})}});xg(ol[v],{map:Xh,tableId:Sf,query:Of(Mf(Rf,If(Ae,"not an Object")))});function pl(){}Q(pl,T);xa(pl[v],function(){var a=this;lg("overlay",function(b){b.dm(a)})});xg(pl[v],{panes:null,projection:null,map:Mf(Xh,Yh)});function ql(a){this[uc]($h(a));lg("poly",Be)}Q(ql,T);xa(ql[v],db(ql[v],function(){var a=this;lg("poly",function(b){b.Xl(a)})}));Za(ql[v],function(){S[z](this,"bounds_changed")});ib(ql[v],ql[v].center_changed);Va(ql[v],function(){var a=this.get("radius"),b=this.get("center");if(b&&ze(a)){var c=this.get("map"),c=c&&c[vd].get("mapType");return uk(b,a/ai(c))}return null});xg(ql[v],{center:Of(Wf),draggable:Uf,editable:Uf,map:Xh,radius:Sf,visible:Uf});function rl(a){this[uc]($h(a));lg("poly",Be)}Q(rl,T);xa(rl[v],db(rl[v],function(){var a=this;lg("poly",function(b){b.fm(a)})}));xg(rl[v],{draggable:Uf,editable:Uf,bounds:Of(Ci),map:Xh,visible:Uf});function sl(){this.j=null}Q(sl,T);xa(sl[v],function(){var a=this;lg("streetview",function(b){b.Yl(a)})});xg(sl[v],{map:Xh});function tl(){this.ib=null}tl[v].getPanorama=function(a,b){var c=this.ib;lg("streetview",function(d){d.nn(a,b,c)})};tl[v].getPanoramaByLocation=function(a,b,c){var d=this.ib;lg("streetview",function(e){e.wi(a,b,c,d)})};tl[v].getPanoramaById=function(a,b){var c=this.ib;lg("streetview",function(d){d.mn(a,b,c)})};function ul(a,b,c,d){this.Fa=a;kb(this,b);this.K=d&&d.Lo||Be;this.ra=c;this.J=!1;S[D](this.ra,"load",function(){c.j=!0;d&&d.Od&&d.Od()})}ul[v].Na=Td(7);ul[v].j=function(){return this.ra.j};Wa(ul[v],function(){this.K()});Oa(ul[v],function(){this.J=!0;S[z](this,"stop")});function vl(a,b,c,d){return new ul(a,b,c,d)};function wl(a){ra(this,a[Eb]);va(this,a[Nb]);this.alt=a.alt;wa(this,a[Qb]);za(this,a[Tb]);this.j=new Ag;this.set("opacity",a[Rc]);var b=this;lg("map",function(c){var d=b.j,e=b.j=new c.j(O(a[xd],a),null,a);e[p]("opacity",b);d[rd](function(a){e.ua(a)})})}Q(wl,T);qb(wl[v],function(a,b,c){if(!a||!c)return null;c=c[yd]("div");a=vl(a,b,c);this.j.ua(a);c.O=a;return c});oa(wl[v],function(a){this.j[Ec](a.O);a.O[$c]()});wl[v].G=Td(8);wl[v].O=!0;xg(wl[v],{opacity:Sf});function xl(a,b){this.set("styles",a);var c=b||{};this.j=c.baseMapTypeId||"roadmap";wa(this,c[Qb]);za(this,c[Tb]||20);va(this,c[Nb]);this.alt=c.alt;ob(this,null);ra(this,new W(256,256))}Q(xl,T);qb(xl[v],Be);function yl(a,b){If(Ef,"container is not a Node")(a);this[uc](b);lg("controls",O(function(b){b.um(this,a)},this))}Q(yl,T);xg(yl[v],{attribution:Of(Nh),place:Of(Oh)});var zl={Animation:{BOUNCE:1,DROP:2,G:3,j:4},Circle:ql,ControlPosition:Vd,Data:ki,GroundOverlay:Mi,ImageMapType:wl,InfoWindow:Fi,LatLng:wf,LatLngBounds:xh,MVCArray:yg,MVCObject:T,Map:ll,MapTypeControlStyle:{DEFAULT:0,HORIZONTAL_BAR:1,DROPDOWN_MENU:2,INSET:3,INSET_LARGE:4},MapTypeId:Ud,MapTypeRegistry:ph,Marker:Zh,MarkerImage:function(a,b,c,d,e){this.url=a;rb(this,b||e);this.origin=c;this.anchor=d;this.scaledSize=e;this.labelOrigin=null},NavigationControlStyle:{DEFAULT:0,SMALL:1,ANDROID:2,ZOOM_PAN:3,
+Nq:4,Nl:5},OverlayView:pl,Point:U,Polygon:gi,Polyline:hi,Rectangle:rl,ScaleControlStyle:{DEFAULT:0},Size:W,StreetViewPreference:Si,StreetViewSource:Ti,StrokePosition:{CENTER:0,INSIDE:1,OUTSIDE:2},SymbolPath:ug,ZoomControlStyle:{DEFAULT:0,SMALL:1,LARGE:2,Nl:3},event:S};
+me(zl,{BicyclingLayer:Pi,DirectionsRenderer:Gi,DirectionsService:Ei,DirectionsStatus:{OK:Ld,UNKNOWN_ERROR:Od,OVER_QUERY_LIMIT:Md,REQUEST_DENIED:Nd,INVALID_REQUEST:Gd,ZERO_RESULTS:Pd,MAX_WAYPOINTS_EXCEEDED:Jd,NOT_FOUND:Kd},DirectionsTravelMode:zi,DirectionsUnitSystem:yi,DistanceMatrixService:Hi,DistanceMatrixStatus:{OK:Ld,INVALID_REQUEST:Gd,OVER_QUERY_LIMIT:Md,REQUEST_DENIED:Nd,UNKNOWN_ERROR:Od,MAX_ELEMENTS_EXCEEDED:Id,MAX_DIMENSIONS_EXCEEDED:Hd},DistanceMatrixElementStatus:{OK:Ld,NOT_FOUND:Kd,ZERO_RESULTS:Pd},
+ElevationService:Ii,ElevationStatus:{OK:Ld,UNKNOWN_ERROR:Od,OVER_QUERY_LIMIT:Md,REQUEST_DENIED:Nd,INVALID_REQUEST:Gd,Jq:"DATA_NOT_AVAILABLE"},FusionTablesLayer:ol,Geocoder:Li,GeocoderLocationType:{ROOFTOP:"ROOFTOP",RANGE_INTERPOLATED:"RANGE_INTERPOLATED",GEOMETRIC_CENTER:"GEOMETRIC_CENTER",APPROXIMATE:"APPROXIMATE"},GeocoderStatus:{OK:Ld,UNKNOWN_ERROR:Od,OVER_QUERY_LIMIT:Md,REQUEST_DENIED:Nd,INVALID_REQUEST:Gd,ZERO_RESULTS:Pd,ERROR:Ed},KmlLayer:Oi,KmlLayerStatus:Ni,MaxZoomService:nl,MaxZoomStatus:{OK:Ld,
+ERROR:Ed},SaveWidget:yl,StreetViewCoverageLayer:sl,StreetViewPanorama:Ui,StreetViewService:tl,StreetViewStatus:{OK:Ld,UNKNOWN_ERROR:Od,ZERO_RESULTS:Pd},StyledMapType:xl,TrafficLayer:Qi,TransitLayer:Ri,TransitMode:Ai,TransitRoutePreference:Bi,TravelMode:zi,UnitSystem:yi});me(ki,{Feature:og,Geometry:vf,GeometryCollection:Dh,LineString:Eh,LinearRing:Ih,MultiLineString:Gh,MultiPoint:Hh,MultiPolygon:Mh,Point:Yf,Polygon:Kh});var Al,Bl;function Cl(a){this.j=a}function Dl(a,b,c){for(var d=ia(b[I]),e=0,f=b[I];e<f;++e)d[e]=b[pd](e);d.unshift(c);a=a.j;c=b=0;for(e=d[I];c<e;++c)b*=1729,b+=d[c],b%=a;return b};function El(){var a=Xj(),b=new Cl(131071),c=unescape("%26%74%6F%6B%65%6E%3D");return function(d){d=d[tc](Fl,"%27");var e=d+c;Gl||(Gl=/(?:https?:\/\/[^/]+)?(.*)/);d=Gl[Vb](d);return e+Dl(b,d&&d[1],a)}}var Fl=/'/g,Gl;function Hl(){var a=new Cl(2147483647);return function(b){return Dl(a,b,0)}};Qh.main=function(a){eval(a)};mg("main",{});function Il(a){return O(eval,m,"window."+a+"()")}function Jl(){for(var a in fa[v])m[Db]&&m[Db][Zb]("This site adds property <"+a+"> to Object.prototype. Extending Object.prototype breaks JavaScript for..in loops, which are used heavily in Google Maps API v3.")}function Kl(a){(a="version"in a)&&m[Db]&&m[Db][Zb]("You have included the Google Maps API multiple times on this page. This may cause unexpected errors.");return a}
+m[ed].maps.Load(function(a,b){var c=m[ed].maps;Jl();var d=Kl(c);Sj=new Aj(a);k[Oc]()<Zj()&&(Wk=!0);Yk=new Vk(b);Xk(Yk,"jl");Al=k[Oc]()<ak();Bl=k[x](1E15*k[Oc]())[ld](36);Ji=El();Ki=Hl();fl=new yg;cl=b;for(var e=0;e<Jg(Sj.H,8);++e)nk[mk(e)]=!0;e=ik();Rh(Vj(e));ne(zl,function(a,b){c[a]=b});ab(c,Wj(e));m[oc](function(){ng(["util","stats"],function(a,b){a.Cj.Og();d&&b.dc.rd({ev:"api_alreadyloaded",client:bk(Sj),key:dk()})})},5E3);S.qp();el=new bl;(e=ck())&&ng(Ig(Sj.H,12),Il(e),!0)});
+}).call(this)
diff --git a/Sites/MapServer/js/maps.js b/Sites/MapServer/js/maps.js
new file mode 100644 (file)
index 0000000..8cf0c2a
--- /dev/null
@@ -0,0 +1,610 @@
+var map = null;
+var overlayGroups = new Object();
+var defaultColor = "#FF0000";
+var algorithmParameters = [];
+var clickGroup = -1;
+var clickPos = null;
+var sourcePos = null;
+var targetPos = null;
+var groupCount = 0;
+var blockFlag = false;
+var openStreetMapTypeOptions = {
+    getTileUrl: function(coord, zoom) {
+        var normalizedCoord = getNormalizedCoord(coord, zoom);
+        if (!normalizedCoord) {
+            return null;
+        }
+        var bound = Math.pow(2, zoom);
+        return "http://tile.openstreetmap.org/" + zoom + "/" + normalizedCoord.x + "/" + (bound - normalizedCoord.y - 1) + ".png";
+    },
+    tileSize: new google.maps.Size(256, 256),
+    maxZoom: 18,
+    minZoom: 0,
+    name: "OpenStreetMap",
+    alt: "OpenStreetMap"
+};
+var openStreetMapType = new google.maps.ImageMapType(openStreetMapTypeOptions);
+
+window.onpopstate = function(event) {    
+    // Check for PermaLink and execute if neccessary
+    var tokens = document.URL.split("index.html?");
+    if (tokens.length > 1) {
+       ReRun(tokens[1]);
+    }
+};
+
+window.setTimeout(manipulateMapElements , 100);
+function manipulateMapElements() {
+       // This is a bad hack!
+       // But I realy want the Map-Button to disply 'GoogleMap'
+    var osm = getElementByXpath("//div[@title='OpenStreetMap']");
+    var gm = getElementByXpath("//div[@title='Show street map']") || getElementByXpath("//div[@title='Stadtplan anzeigen']");
+    if (osm) {
+        osm.style.minWidth = "20px";
+        gm.style.minWidth = "20px";
+        gm.innerHTML = "GoogleMap";
+    } else {
+       window.setTimeout(manipulateMapElements, 100);
+    }
+}
+
+function getElementByXpath(path) {
+    return document.evaluate(path, document, null, 9, null).singleNodeValue;
+};
+
+// Initializes the map.
+function InitializeMap() {
+       
+       // Set up right-click-menus
+    HideContextMenu();
+    HideEntryMenu();
+    HideFunctionCanvas();
+
+    // Set some default map options.
+    var kitLatLng = new google.maps.LatLng(49.013871, 8.419561);
+    var initialMapOptions = {
+        zoom: 8,
+        center: kitLatLng,
+        streetViewControl: false,
+        disableDefaultUI: true,
+        mapTypeControl: true,
+        mapTypeControlOptions: {
+            //mapTypeIds: ["OpenStreetMap", google.maps.MapTypeId.ROADMAP]
+               mapTypeIds: ["OpenStreetMap", google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.TERRAIN]
+        }
+    };
+    
+    // Load the map into the canvas.
+    map = new google.maps.Map(document.getElementById("mapCanvas"), initialMapOptions);
+    map.mapTypes.set('OpenStreetMap', openStreetMapType);
+    map.setMapTypeId('OpenStreetMap');
+    
+    // Shift to bounding box.
+    new Ajax.Request('/bounding_box', {
+        onSuccess: function(response) {
+            var data = response.responseText.evalJSON();
+            var bounds = new google.maps.LatLngBounds(new google.maps.LatLng(data.Min.Latitude, data.Min.Longitude), new google.maps.LatLng(data.Max.Latitude, data.Max.Longitude));
+            map.fitBounds(bounds);
+        }
+    });
+       
+    // Add listener for clicking onto the map (place source and target markers)
+    google.maps.event.addListener(map, 'click', function(e) {
+        clickPos = e.latLng;
+        HideContextMenu();    
+        SetSource()
+        Run();
+    });
+    google.maps.event.addListener(map, 'rightclick', function(e) {
+        ShowContextMenu(e.latLng);
+        e.stop();
+    });
+    google.maps.event.addListener(map, 'mousemove', function(e) {
+        HideContextMenu();
+        HideEntryMenu();
+    });
+    
+    // Retrieve algorithms.
+    new Ajax.Request('/algorithms_information', {
+        onSuccess: function(response) {
+            var data = response.responseText.evalJSON();
+            for (var i = 0; i < data.NumberOfAlgorithms; ++i) {
+                $('select_algorithm').options.add(new Option(data.Algorithms[i].Name, data.Algorithms[i].Key));
+                if (data.Algorithms[i].Parameter) {
+                    algorithmParameters[data.Algorithms[i].Key] = data.Algorithms[i].Parameter;
+                    algorithmParameters[data.Algorithms[i].Key].sort(function(a, b){return a.Index < b.Index});
+                }    
+            }
+            LoadParameter();
+                        
+            // Check for PermaLink and execute if neccessary
+            var tokens = document.URL.split("index.html?");
+            if (tokens.length > 1) {
+               ReRun(tokens[1]);
+            }
+        }
+    });
+
+}
+
+function SetSource() {
+    sourcePos = clickPos;
+}
+
+function SetTarget() {
+    targetPos = clickPos;
+}
+
+function ShowContextMenu(latlng) {
+    clickPos = latlng;
+    var contextMenu = $('contextMenu');
+    var pos = LatLngToPixel(latlng);
+    contextMenu.style.left = (pos.x - 10) + 'px';
+    contextMenu.style.top = (pos.y - 10) + 'px';    
+    contextMenu.style.display = '';
+}
+
+function HideContextMenu() {
+    var contextMenu = $('contextMenu');
+    contextMenu.style.display = 'none';    
+}
+
+function ShowEntryMenu(e) {
+    var entryMenu = $('entryMenu');
+    entryMenu.style.left = (e.clientX - 10) + 'px';
+    entryMenu.style.top = (e.clientY - 10) + 'px';    
+    entryMenu.style.display = '';
+    return false;
+}
+
+function HideEntryMenu() {
+    var entryMenu = $('entryMenu');
+    entryMenu.style.display = 'none';    
+}
+
+function ShowFunctionCanvas() {
+    var entryMenu = $('functionCanvas');  
+    entryMenu.style.display = '';
+}
+
+function HideFunctionCanvas() {
+    var entryMenu = $('functionCanvas');
+    entryMenu.style.display = 'none';    
+}
+
+function ResetQuery() {
+    sourcePos = null;
+    targetPos = null;
+    ClearOverlays();
+    window.history.pushState({}, "Map Server", "/");
+    HideContextMenu();
+}
+
+function Block() {
+       blockFlag = true;
+    $('all').setAttribute("class", "block");
+}
+
+function Unblock() {
+       blockFlag = false;
+    $('all').setAttribute("class", "unblock");
+}
+
+function Run() {
+    if (sourcePos != null || targetPos != null) {
+        ClearOverlays();
+        var key = $('select_algorithm').value
+        var ajaxRequest = "/" + key;
+        if (sourcePos != null) {
+            ajaxRequest = ajaxRequest + "?SourceLat=" + sourcePos.lat();
+            ajaxRequest = ajaxRequest + "&SourceLon=" + sourcePos.lng();
+        }
+        if (targetPos != null) {
+            var delimiter = ((sourcePos == null) ? "?" : "&");
+            ajaxRequest = ajaxRequest + delimiter + "TargetLat=" + targetPos.lat();
+            ajaxRequest = ajaxRequest + "&TargetLon=" + targetPos.lng();
+        }
+        if (algorithmParameters[key]) {
+            for (var i = 0; i < algorithmParameters[key].length; i++) {
+                if (algorithmParameters[key][i].Type == "bool") {
+                    ajaxRequest = ajaxRequest + "&" + algorithmParameters[key][i].Name + "=" + $(algorithmParameters[key][i].Name).checked;
+                } else if (algorithmParameters[key][i].Type == "time") {
+                    ajaxRequest = ajaxRequest + "&" + algorithmParameters[key][i].Name + "=" + TimeValueToSeconds($(algorithmParameters[key][i].Name).value);
+                } else if (algorithmParameters[key][i].Type == "string") {
+                    ajaxRequest = ajaxRequest + "&" + algorithmParameters[key][i].Name + "=" + Encode($(algorithmParameters[key][i].Name).value);
+                } else {
+                    ajaxRequest = ajaxRequest + "&" + algorithmParameters[key][i].Name + "=" + $(algorithmParameters[key][i].Name).value;
+                }
+            }
+        }
+        window.history.pushState({request: ajaxRequest}, "Map Server", "index.html?" + ajaxRequest);
+        if (!blockFlag) {
+            Block();
+            new Ajax.Request(ajaxRequest, {
+                onSuccess: function(response) {
+                    DisplayResult(response.responseText.evalJSON());
+                    Unblock();
+                },
+                onComplete: function(response) {
+                               if (response.status == 0) {
+                                       DisplayResult({"ResultSet":[{"Info":"Timeout", "Color":"FF0000", "Pathes":[], "Nodes":[]}]});
+                                       Unblock();
+                               }
+                       }
+            });
+        } 
+    }
+}
+
+function ReRun(ajaxRequest) {
+       var tokens = ajaxRequest.split("?", 2);
+       var key = tokens[0].substr(1);
+
+       $('select_algorithm').value = key;
+       LoadParameter();
+       
+       var params = tokens[1].split("&");
+       var SourceLat = 1000;
+       var SourceLon = 1000;
+       var TargetLat = 1000;
+       var TargetLon = 1000;
+       for (var i = 0; i < params.length; i++) {
+               tokens = params[i].split("=", 2);
+               if (tokens[0] == "SourceLat") {
+                       SourceLat = tokens[1];
+               } else if (tokens[0] == "SourceLon") {
+                       SourceLon = tokens[1];
+               } else if (tokens[0] == "TargetLat") {
+                       TargetLat = tokens[1];
+               } else if (tokens[0] == "TargetLon") {
+                       TargetLon = tokens[1];
+               } else {
+                   if (algorithmParameters[key]) {
+                       for (var j = 0; j < algorithmParameters[key].length; j++) {
+                               if (tokens[0] == algorithmParameters[key][j].Name) {
+                                       if (algorithmParameters[key][j].Type == "bool") {
+                                               if (tokens[1] == "true") {
+                                                       $(algorithmParameters[key][j].Name).checked = true;
+                                               } else {
+                                                       $(algorithmParameters[key][j].Name).checked = false;
+                                               }
+                                       } else if (algorithmParameters[key][j].Type == "time") {
+                                               $(algorithmParameters[key][j].Name).value = SecondsToTimeValue(tokens[1]);
+                                       } else {
+                                               $(algorithmParameters[key][j].Name).value = tokens[1];
+                                       }
+                               }
+                       }
+                   }                   
+               }
+       }
+       if (SourceLat < 1000 && SourceLon < 1000) {
+           sourcePos = new google.maps.LatLng(SourceLat, SourceLon);
+       }
+       if (TargetLat < 1000 && TargetLon < 1000) {
+           targetPos = new google.maps.LatLng(TargetLat, TargetLon);
+       }
+
+    ClearOverlays();
+    if (!blockFlag) {
+        Block();
+        new Ajax.Request(ajaxRequest, {
+            onSuccess: function(response) {
+                DisplayResult(response.responseText.evalJSON());
+                Unblock();
+            },
+            onComplete: function(response) {
+                       if (response.status == 0) {
+                               DisplayResult({"ResultSet":[{"Info":"Timeout", "Color":"FF0000", "Pathes":[], "Nodes":[]}]});
+                               Unblock();
+                       }
+               }
+        }); 
+    }
+}
+
+function Encode(str) {
+    return str.replace(/#/g, "%23");
+}
+
+function ToggleAll() {
+    var state;
+    for (var group in overlayGroups) {
+       state = !overlayGroups[group][0].getVisible();
+       break;
+    }
+    for (var group in overlayGroups) {
+        for (var i = 0; i < overlayGroups[group].length; i++) {
+            overlayGroups[group][i].setVisible(state);
+        }
+    }
+}
+
+function ToggleGroup() {
+    if (overlayGroups[clickGroup]) {
+        for (var i = 0; i < overlayGroups[clickGroup].length; i++) {
+            overlayGroups[clickGroup][i].setVisible(!overlayGroups[clickGroup][i].getVisible());
+        }
+    }
+}
+
+function PinGroup() {
+       $('tr_' + clickGroup).setAttribute('pinned', true);
+}
+
+function DeleteGroup() {
+       for (var i = 0; i < overlayGroups[clickGroup].length; i++) {
+               overlayGroups[clickGroup][i].setMap(null);
+       }
+       $('infoArea').removeChild($('tr_' + clickGroup));
+       delete overlayGroups[clickGroup];
+}
+
+function SetCurrentGroup(i) {
+       clickGroup = i;
+}
+
+function LoadParameter() {
+    viaNode = null;
+    viaRegion = null;
+    var htmlString = "";
+    var key = $('select_algorithm').value
+    if (algorithmParameters[key]) {
+        for (var i = 0; i < algorithmParameters[key].length; i++) {
+            var onChangeString = "";
+            if (algorithmParameters[key][i].RecalculateOnChange) {
+               if ((algorithmParameters[key][i].Type == "time")) {
+                    onChangeString = " onblur='Run();'";
+               } else {
+                    onChangeString = " onchange='Run();'";
+               }
+            }
+            var defaultValue = algorithmParameters[key][i].DefaultValue
+            var oldInput = $(algorithmParameters[key][i].Name);
+            if (oldInput) {
+               if (algorithmParameters[key][i].Type == "bool") {
+                       defaultValue = "" + oldInput.checked;
+                } else if (algorithmParameters[key][i].Type == "time") {
+                       defaultValue = TimeValueToSeconds(oldInput.value);
+                } else {
+                       defaultValue = oldInput.value;
+                }
+            }
+            if (algorithmParameters[key][i].Type == "bool") {
+                if (defaultValue == "true") {
+                    htmlString = htmlString + "<p><input id='" + algorithmParameters[key][i].Name + "'" + onChangeString + " type='checkbox' name='boxes' checked='checked' style='position: relative; top: 3px; left: -1px;'> " + algorithmParameters[key][i].Display + "</p>";
+                } else {
+                    htmlString = htmlString + "<p><input id='" + algorithmParameters[key][i].Name + "'" + onChangeString + " type='checkbox' name='boxes' style='position: relative; top: 3px; left: -1px;'> " + algorithmParameters[key][i].Display + "</p>";
+                }
+            } else if (algorithmParameters[key][i].Type == "string") {
+                htmlString = "<p>" + algorithmParameters[key][i].Display + ":<br/><textarea wrap='off' cols='25' rows='10' id='" + algorithmParameters[key][i].Name + "'" + onChangeString + ">" + defaultValue + "</textarea></p>" + htmlString;
+            } else if (algorithmParameters[key][i].Type == "time") {
+               var timeInput = BuildTimeInput(algorithmParameters[key][i], defaultValue, onChangeString)
+                htmlString = "<p>" + algorithmParameters[key][i].Display + ":<br/>" + timeInput + "</p>" + htmlString;
+            } else {
+                var limits = "";
+                if (algorithmParameters[key][i].MinValue) {
+                       limits = limits + " min='" + algorithmParameters[key][i].MinValue + "'";
+                }
+                if (algorithmParameters[key][i].MaxValue) {
+                       limits = limits + " max='" + algorithmParameters[key][i].MaxValue + "'";
+                }
+                if (algorithmParameters[key][i].Step) {
+                       limits = limits + " step='" + algorithmParameters[key][i].Step + "'";
+                }
+                htmlString = "<p>" + algorithmParameters[key][i].Display + ":<br/><input id='" + algorithmParameters[key][i].Name + "'" + onChangeString + " value='" + defaultValue + "' type='number'" + limits + " size='30' maxlength='30'></input></p>" + htmlString;
+            }
+        }
+    }
+    $('parameterArea').innerHTML = htmlString;
+    ClearOverlays();
+}
+
+function DisplayResult(data) {
+       var oldInfos = $('infoArea').innerHTML;
+    infoHTML = "";
+    if (!data.ResultSet) return;
+    if (data.ResultSet.length > 2) {
+        infoHTML = infoHTML + "<tr id='tr_top' onclick='ToggleAll();'><td class='ColorMarker' style='background-color: #222222;'></td>";
+        infoHTML = infoHTML + "<td class='TextMarker'>Toggle All</td></tr>";
+    }
+    for (var Group = 0; Group < data.ResultSet.length; Group++) {
+       var GroupID = Group + groupCount;
+        infoHTML = infoHTML + "<tr id='tr_" + GroupID + "' onclick='ToggleGroup();' onmouseover='SetCurrentGroup(" + GroupID + ")'><td class='ColorMarker' style='background-color: #" + data.ResultSet[Group].Color + ";'></td>";
+        infoHTML = infoHTML + "<td class='TextMarker'>" + data.ResultSet[Group].Info + "</td></tr>";
+        overlayGroups[GroupID] = [];
+        if (data.ResultSet[Group].Pathes.length > 0) {
+            DrawPathes(data.ResultSet[Group].Pathes, defaultColor, GroupID);
+        }
+        if (data.ResultSet[Group].Nodes.length > 0) {
+            DrawNodes(data.ResultSet[Group].Nodes, GroupID);
+        }
+        if (data.ResultSet[Group].Parameter) {
+               SetParameter(data.ResultSet[Group].Parameter);
+        }
+    }
+    if (data.ResultSet.length == 0) {
+        infoHTML = infoHTML + "<tr id='tr_top'><td class='ColorMarker' style='background-color: #222222;'></td>";
+        infoHTML = infoHTML + "<td class='TextMarker'>No Result</td></tr>";
+    }
+    $('infoArea').innerHTML = infoHTML + oldInfos;
+    for (var group in overlayGroups) {
+        $('tr_' + group).oncontextmenu = ShowEntryMenu;
+    }
+    groupCount += data.ResultSet.length;
+}
+
+function ClearOverlays() {
+       if ($('tr_top')) {
+       $('infoArea').removeChild($('tr_top'));
+       }
+    for (var Group in overlayGroups) {
+       var info = $('tr_' + Group);
+       if (!info.getAttribute('pinned')) {
+               for (var i = 0; i < overlayGroups[Group].length; i++) {
+                       overlayGroups[Group][i].setMap(null);
+               }
+               $('infoArea').removeChild(info);
+               delete overlayGroups[Group];
+       }
+    }
+}
+
+function ParameterType(name) {
+    var key = $('select_algorithm').value
+    if (algorithmParameters[key]) {
+        for (var i = 0; i < algorithmParameters[key].length; i++) {
+               if (algorithmParameters[key][i].Name == name) {
+                       return algorithmParameters[key][i].Type;
+               }
+        }
+    }
+    return "";
+}
+
+function SetParameter(parameter) {
+    var key = $('select_algorithm').value
+    if (algorithmParameters[key]) {
+        for (var i = 0; i < algorithmParameters[key].length; i++) {
+               if (parameter[algorithmParameters[key][i].Name]) {
+               if (algorithmParameters[key][i].Type == "bool") {
+                    if (parameter[algorithmParameters[key][i].Name] == "true") {
+                       $(algorithmParameters[key][i].Name).checked = "checked";
+                    } else {
+                       $(algorithmParameters[key][i].Name).removeAttribute("checked");
+                    }
+                } else if (algorithmParameters[key][i].Type == "time") {
+                       $(algorithmParameters[key][i].Name).value = SecondsToTimeValue(parameter[algorithmParameters[key][i].Name]);
+                } else {
+                       $(algorithmParameters[key][i].Name).value = parameter[algorithmParameters[key][i].Name];
+                }
+               }
+        }
+    }  
+}
+
+function SecondsToTimeValue(seconds) {
+       if (HasTimeInput()) {
+               seconds = Number(seconds);
+               var h = Math.floor(seconds / 3600);
+               var m = Math.floor((seconds % 3600) / 60);
+               var s = Math.floor(seconds % 60);
+               return (h < 10 ? "0" : "") + h + ":" + (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
+       } else {
+               return seconds;
+       }
+}
+
+function TimeValueToSeconds(timeValue) {
+       if (HasTimeInput()) {
+               var tokens = timeValue.split(':');
+               var seconds = 0;
+               seconds += tokens[0] ? ((+tokens[0]) * 3600) : 0;
+               seconds += tokens[1] ? ((+tokens[1]) * 60) : 0;
+               seconds += tokens[2] ? (+tokens[2]) : 0;
+               return seconds; 
+       } else {
+               return timeValue;
+       }
+}
+
+function BuildTimeInput(parameter, defaultValue, onChangeString) {
+       var step = parameter.Step ? parameter.Step : "1";
+       var timeValue = SecondsToTimeValue(defaultValue);
+       if (HasTimeInput()) {
+               return "<input id='" + parameter.Name + "'" + onChangeString + " value='" + timeValue + "' type='time' step='" + step + "' size='30' maxlength='30'></input>"
+       } else {
+               return "<input id='" + parameter.Name + "'" + onChangeString + " value='" + timeValue + "' type='number' step='" + step + "' min='0' size='30' maxlength='30'></input>"
+       }
+}
+
+function HasTimeInput() {
+    var input = document.createElement('input');
+    input.setAttribute('type','time');
+    var notADateValue = 'not-a-date';
+    input.setAttribute('value', notADateValue); 
+    return (input.value !== notADateValue);
+}
+
+function DrawNodes(nodes, Group) {
+    for (var i = 0; i < nodes.length; i++) {
+        var options = {
+            position: new google.maps.LatLng(nodes[i].Pos.Lat, nodes[i].Pos.Lon),
+            title: (nodes[i].Pos.Lat + ", " + nodes[i].Pos.Lon),
+            visible: true,
+            map: map
+        };
+        if (nodes[i].Info.length > 0) {
+               if (nodes[i].Info.indexOf('.png', nodes[i].Info.length - 4) !== -1) {
+               var image = {url: nodes[i].Info, size: new google.maps.Size(20, 32), anchor: new google.maps.Point(9, 9)};
+               options.icon = image; 
+               } else {
+                options.icon = ("http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=" + nodes[i].Info.charAt(0) + "|" + nodes[i].Color + "|000000");  
+               }
+        }
+        var marker = new google.maps.Marker(options)
+        overlayGroups[Group].push(marker);
+    }    
+}
+
+function DrawPathes(pathes, color, Group) {
+    for (var i = 0; i < pathes.length; i++) {
+        if (pathes[i].Color) {
+            DrawPath(pathes[i].Nodes, "#" + pathes[i].Color, pathes[i], Group);
+        } else {
+            DrawPath(pathes[i].Nodes, color, pathes[i], Group);
+        }
+    }
+}
+
+function DrawPath(nodes, color, path, Group) {
+    var line = new google.maps.Polyline({
+        path: PolylineToPath(nodes),
+        visible: true,
+        strokeColor: color,
+        strokeOpacity: 0.6,
+        strokeWeight: 6,
+        map: map
+    });
+    overlayGroups[Group].push(line);
+}
+
+function PolylineToPath(nodes) {
+    var path = new Array(nodes.length);    
+    for (var j = 0; j < nodes.length; j++) {
+        path[j] = new google.maps.LatLng(nodes[j].Lat, nodes[j].Lon);
+    }    
+    return path;
+}
+
+function getNormalizedCoord(coord, zoom) {
+    var y = coord.y;
+    var x = coord.x;
+
+    var tileRange = 1 << zoom;
+    y = tileRange - y - 1;
+    
+    if (y < 0 || y >= tileRange) {
+        return null;
+    }
+    if (x < 0 || x >= tileRange) {
+        x = (x % tileRange + tileRange) % tileRange;
+    }
+
+    return {x: x, y: y};
+}
+
+function LatLngToPixel(latlng) {
+    var scale = Math.pow(2, map.getZoom());
+    var nw = new google.maps.LatLng(
+        map.getBounds().getNorthEast().lat(),
+        map.getBounds().getSouthWest().lng()
+    );
+    var worldCoordinateNW = map.getProjection().fromLatLngToPoint(nw);
+    var worldCoordinate = map.getProjection().fromLatLngToPoint(latlng);
+    var caurrentLatLngOffset = new google.maps.Point(
+        Math.floor((worldCoordinate.x - worldCoordinateNW.x) * scale),
+        Math.floor((worldCoordinate.y - worldCoordinateNW.y) * scale)
+    );
+    return caurrentLatLngOffset;
+}
diff --git a/Sites/MapServer/js/prototype.js b/Sites/MapServer/js/prototype.js
new file mode 100644 (file)
index 0000000..474b223
--- /dev/null
@@ -0,0 +1,6082 @@
+/*  Prototype JavaScript framework, version 1.7
+ *  (c) 2005-2010 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+
+  Version: '1.7',
+
+  Browser: (function(){
+    var ua = navigator.userAgent;
+    var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
+    return {
+      IE:             !!window.attachEvent && !isOpera,
+      Opera:          isOpera,
+      WebKit:         ua.indexOf('AppleWebKit/') > -1,
+      Gecko:          ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
+      MobileSafari:   /Apple.*Mobile/.test(ua)
+    }
+  })(),
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+
+    SelectorsAPI: !!document.querySelector,
+
+    ElementExtensions: (function() {
+      var constructor = window.Element || window.HTMLElement;
+      return !!(constructor && constructor.prototype);
+    })(),
+    SpecificElementExtensions: (function() {
+      if (typeof window.HTMLDivElement !== 'undefined')
+        return true;
+
+      var div = document.createElement('div'),
+          form = document.createElement('form'),
+          isSupported = false;
+
+      if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
+        isSupported = true;
+      }
+
+      div = form = null;
+
+      return isSupported;
+    })()
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+var Abstract = { };
+
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+/* Based on Alex Arnell's inheritance implementation. */
+
+var Class = (function() {
+
+  var IS_DONTENUM_BUGGY = (function(){
+    for (var p in { toString: 1 }) {
+      if (p === 'toString') return false;
+    }
+    return true;
+  })();
+
+  function subclass() {};
+  function create() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0, length = properties.length; i < length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+    return klass;
+  }
+
+  function addMethods(source) {
+    var ancestor   = this.superclass && this.superclass.prototype,
+        properties = Object.keys(source);
+
+    if (IS_DONTENUM_BUGGY) {
+      if (source.toString != Object.prototype.toString)
+        properties.push("toString");
+      if (source.valueOf != Object.prototype.valueOf)
+        properties.push("valueOf");
+    }
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames()[0] == "$super") {
+        var method = value;
+        value = (function(m) {
+          return function() { return ancestor[m].apply(this, arguments); };
+        })(property).wrap(method);
+
+        value.valueOf = method.valueOf.bind(method);
+        value.toString = method.toString.bind(method);
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+
+  return {
+    create: create,
+    Methods: {
+      addMethods: addMethods
+    }
+  };
+})();
+(function() {
+
+  var _toString = Object.prototype.toString,
+      NULL_TYPE = 'Null',
+      UNDEFINED_TYPE = 'Undefined',
+      BOOLEAN_TYPE = 'Boolean',
+      NUMBER_TYPE = 'Number',
+      STRING_TYPE = 'String',
+      OBJECT_TYPE = 'Object',
+      FUNCTION_CLASS = '[object Function]',
+      BOOLEAN_CLASS = '[object Boolean]',
+      NUMBER_CLASS = '[object Number]',
+      STRING_CLASS = '[object String]',
+      ARRAY_CLASS = '[object Array]',
+      DATE_CLASS = '[object Date]',
+      NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
+        typeof JSON.stringify === 'function' &&
+        JSON.stringify(0) === '0' &&
+        typeof JSON.stringify(Prototype.K) === 'undefined';
+
+  function Type(o) {
+    switch(o) {
+      case null: return NULL_TYPE;
+      case (void 0): return UNDEFINED_TYPE;
+    }
+    var type = typeof o;
+    switch(type) {
+      case 'boolean': return BOOLEAN_TYPE;
+      case 'number':  return NUMBER_TYPE;
+      case 'string':  return STRING_TYPE;
+    }
+    return OBJECT_TYPE;
+  }
+
+  function extend(destination, source) {
+    for (var property in source)
+      destination[property] = source[property];
+    return destination;
+  }
+
+  function inspect(object) {
+    try {
+      if (isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  }
+
+  function toJSON(value) {
+    return Str('', { '': value }, []);
+  }
+
+  function Str(key, holder, stack) {
+    var value = holder[key],
+        type = typeof value;
+
+    if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') {
+      value = value.toJSON(key);
+    }
+
+    var _class = _toString.call(value);
+
+    switch (_class) {
+      case NUMBER_CLASS:
+      case BOOLEAN_CLASS:
+      case STRING_CLASS:
+        value = value.valueOf();
+    }
+
+    switch (value) {
+      case null: return 'null';
+      case true: return 'true';
+      case false: return 'false';
+    }
+
+    type = typeof value;
+    switch (type) {
+      case 'string':
+        return value.inspect(true);
+      case 'number':
+        return isFinite(value) ? String(value) : 'null';
+      case 'object':
+
+        for (var i = 0, length = stack.length; i < length; i++) {
+          if (stack[i] === value) { throw new TypeError(); }
+        }
+        stack.push(value);
+
+        var partial = [];
+        if (_class === ARRAY_CLASS) {
+          for (var i = 0, length = value.length; i < length; i++) {
+            var str = Str(i, value, stack);
+            partial.push(typeof str === 'undefined' ? 'null' : str);
+          }
+          partial = '[' + partial.join(',') + ']';
+        } else {
+          var keys = Object.keys(value);
+          for (var i = 0, length = keys.length; i < length; i++) {
+            var key = keys[i], str = Str(key, value, stack);
+            if (typeof str !== "undefined") {
+               partial.push(key.inspect(true)+ ':' + str);
+             }
+          }
+          partial = '{' + partial.join(',') + '}';
+        }
+        stack.pop();
+        return partial;
+    }
+  }
+
+  function stringify(object) {
+    return JSON.stringify(object);
+  }
+
+  function toQueryString(object) {
+    return $H(object).toQueryString();
+  }
+
+  function toHTML(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  }
+
+  function keys(object) {
+    if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
+    var results = [];
+    for (var property in object) {
+      if (object.hasOwnProperty(property)) {
+        results.push(property);
+      }
+    }
+    return results;
+  }
+
+  function values(object) {
+    var results = [];
+    for (var property in object)
+      results.push(object[property]);
+    return results;
+  }
+
+  function clone(object) {
+    return extend({ }, object);
+  }
+
+  function isElement(object) {
+    return !!(object && object.nodeType == 1);
+  }
+
+  function isArray(object) {
+    return _toString.call(object) === ARRAY_CLASS;
+  }
+
+  var hasNativeIsArray = (typeof Array.isArray == 'function')
+    && Array.isArray([]) && !Array.isArray({});
+
+  if (hasNativeIsArray) {
+    isArray = Array.isArray;
+  }
+
+  function isHash(object) {
+    return object instanceof Hash;
+  }
+
+  function isFunction(object) {
+    return _toString.call(object) === FUNCTION_CLASS;
+  }
+
+  function isString(object) {
+    return _toString.call(object) === STRING_CLASS;
+  }
+
+  function isNumber(object) {
+    return _toString.call(object) === NUMBER_CLASS;
+  }
+
+  function isDate(object) {
+    return _toString.call(object) === DATE_CLASS;
+  }
+
+  function isUndefined(object) {
+    return typeof object === "undefined";
+  }
+
+  extend(Object, {
+    extend:        extend,
+    inspect:       inspect,
+    toJSON:        NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON,
+    toQueryString: toQueryString,
+    toHTML:        toHTML,
+    keys:          Object.keys || keys,
+    values:        values,
+    clone:         clone,
+    isElement:     isElement,
+    isArray:       isArray,
+    isHash:        isHash,
+    isFunction:    isFunction,
+    isString:      isString,
+    isNumber:      isNumber,
+    isDate:        isDate,
+    isUndefined:   isUndefined
+  });
+})();
+Object.extend(Function.prototype, (function() {
+  var slice = Array.prototype.slice;
+
+  function update(array, args) {
+    var arrayLength = array.length, length = args.length;
+    while (length--) array[arrayLength + length] = args[length];
+    return array;
+  }
+
+  function merge(array, args) {
+    array = slice.call(array, 0);
+    return update(array, args);
+  }
+
+  function argumentNames() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
+      .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
+      .replace(/\s+/g, '').split(',');
+    return names.length == 1 && !names[0] ? [] : names;
+  }
+
+  function bind(context) {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = slice.call(arguments, 1);
+    return function() {
+      var a = merge(args, arguments);
+      return __method.apply(context, a);
+    }
+  }
+
+  function bindAsEventListener(context) {
+    var __method = this, args = slice.call(arguments, 1);
+    return function(event) {
+      var a = update([event || window.event], args);
+      return __method.apply(context, a);
+    }
+  }
+
+  function curry() {
+    if (!arguments.length) return this;
+    var __method = this, args = slice.call(arguments, 0);
+    return function() {
+      var a = merge(args, arguments);
+      return __method.apply(this, a);
+    }
+  }
+
+  function delay(timeout) {
+    var __method = this, args = slice.call(arguments, 1);
+    timeout = timeout * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  }
+
+  function defer() {
+    var args = update([0.01], arguments);
+    return this.delay.apply(this, args);
+  }
+
+  function wrap(wrapper) {
+    var __method = this;
+    return function() {
+      var a = update([__method.bind(this)], arguments);
+      return wrapper.apply(this, a);
+    }
+  }
+
+  function methodize() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      var a = update([this], arguments);
+      return __method.apply(null, a);
+    };
+  }
+
+  return {
+    argumentNames:       argumentNames,
+    bind:                bind,
+    bindAsEventListener: bindAsEventListener,
+    curry:               curry,
+    delay:               delay,
+    defer:               defer,
+    wrap:                wrap,
+    methodize:           methodize
+  }
+})());
+
+
+
+(function(proto) {
+
+
+  function toISOString() {
+    return this.getUTCFullYear() + '-' +
+      (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+      this.getUTCDate().toPaddedString(2) + 'T' +
+      this.getUTCHours().toPaddedString(2) + ':' +
+      this.getUTCMinutes().toPaddedString(2) + ':' +
+      this.getUTCSeconds().toPaddedString(2) + 'Z';
+  }
+
+
+  function toJSON() {
+    return this.toISOString();
+  }
+
+  if (!proto.toISOString) proto.toISOString = toISOString;
+  if (!proto.toJSON) proto.toJSON = toJSON;
+
+})(Date.prototype);
+
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+        this.currentlyExecuting = false;
+      } catch(e) {
+        this.currentlyExecuting = false;
+        throw e;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, (function() {
+  var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
+    typeof JSON.parse === 'function' &&
+    JSON.parse('{"test": true}').test;
+
+  function prepareReplacement(replacement) {
+    if (Object.isFunction(replacement)) return replacement;
+    var template = new Template(replacement);
+    return function(match) { return template.evaluate(match) };
+  }
+
+  function gsub(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = prepareReplacement(replacement);
+
+    if (Object.isString(pattern))
+      pattern = RegExp.escape(pattern);
+
+    if (!(pattern.length || pattern.source)) {
+      replacement = replacement('');
+      return replacement + source.split('').join(replacement) + replacement;
+    }
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  }
+
+  function sub(pattern, replacement, count) {
+    replacement = prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  }
+
+  function scan(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  }
+
+  function truncate(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  }
+
+  function strip() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  }
+
+  function stripTags() {
+    return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
+  }
+
+  function stripScripts() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  }
+
+  function extractScripts() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img'),
+        matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  }
+
+  function evalScripts() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  }
+
+  function escapeHTML() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  }
+
+  function unescapeHTML() {
+    return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
+  }
+
+
+  function toQueryParams(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift()),
+            value = pair.length > 1 ? pair.join('=') : pair[0];
+
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  }
+
+  function toArray() {
+    return this.split('');
+  }
+
+  function succ() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  }
+
+  function times(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  }
+
+  function camelize() {
+    return this.replace(/-+(.)?/g, function(match, chr) {
+      return chr ? chr.toUpperCase() : '';
+    });
+  }
+
+  function capitalize() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  }
+
+  function underscore() {
+    return this.replace(/::/g, '/')
+               .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
+               .replace(/([a-z\d])([A-Z])/g, '$1_$2')
+               .replace(/-/g, '_')
+               .toLowerCase();
+  }
+
+  function dasherize() {
+    return this.replace(/_/g, '-');
+  }
+
+  function inspect(useDoubleQuotes) {
+    var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
+      if (character in String.specialChar) {
+        return String.specialChar[character];
+      }
+      return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  }
+
+  function unfilterJSON(filter) {
+    return this.replace(filter || Prototype.JSONFilter, '$1');
+  }
+
+  function isJSON() {
+    var str = this;
+    if (str.blank()) return false;
+    str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@');
+    str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
+    str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+    return (/^[\],:{}\s]*$/).test(str);
+  }
+
+  function evalJSON(sanitize) {
+    var json = this.unfilterJSON(),
+        cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+    if (cx.test(json)) {
+      json = json.replace(cx, function (a) {
+        return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+      });
+    }
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  }
+
+  function parseJSON() {
+    var json = this.unfilterJSON();
+    return JSON.parse(json);
+  }
+
+  function include(pattern) {
+    return this.indexOf(pattern) > -1;
+  }
+
+  function startsWith(pattern) {
+    return this.lastIndexOf(pattern, 0) === 0;
+  }
+
+  function endsWith(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.indexOf(pattern, d) === d;
+  }
+
+  function empty() {
+    return this == '';
+  }
+
+  function blank() {
+    return /^\s*$/.test(this);
+  }
+
+  function interpolate(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+
+  return {
+    gsub:           gsub,
+    sub:            sub,
+    scan:           scan,
+    truncate:       truncate,
+    strip:          String.prototype.trim || strip,
+    stripTags:      stripTags,
+    stripScripts:   stripScripts,
+    extractScripts: extractScripts,
+    evalScripts:    evalScripts,
+    escapeHTML:     escapeHTML,
+    unescapeHTML:   unescapeHTML,
+    toQueryParams:  toQueryParams,
+    parseQuery:     toQueryParams,
+    toArray:        toArray,
+    succ:           succ,
+    times:          times,
+    camelize:       camelize,
+    capitalize:     capitalize,
+    underscore:     underscore,
+    dasherize:      dasherize,
+    inspect:        inspect,
+    unfilterJSON:   unfilterJSON,
+    isJSON:         isJSON,
+    evalJSON:       NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON,
+    include:        include,
+    startsWith:     startsWith,
+    endsWith:       endsWith,
+    empty:          empty,
+    blank:          blank,
+    interpolate:    interpolate
+  };
+})());
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (object && Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return (match[1] + '');
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3],
+          pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    });
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = (function() {
+  function each(iterator, context) {
+    var index = 0;
+    try {
+      this._each(function(value) {
+        iterator.call(context, value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  }
+
+  function eachSlice(number, iterator, context) {
+    var index = -number, slices = [], array = this.toArray();
+    if (number < 1) return array;
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  }
+
+  function all(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator.call(context, value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  }
+
+  function any(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator.call(context, value, index))
+        throw $break;
+    });
+    return result;
+  }
+
+  function collect(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator.call(context, value, index));
+    });
+    return results;
+  }
+
+  function detect(iterator, context) {
+    var result;
+    this.each(function(value, index) {
+      if (iterator.call(context, value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  }
+
+  function findAll(iterator, context) {
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator.call(context, value, index))
+        results.push(value);
+    });
+    return results;
+  }
+
+  function grep(filter, iterator, context) {
+    iterator = iterator || Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(RegExp.escape(filter));
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator.call(context, value, index));
+    });
+    return results;
+  }
+
+  function include(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  }
+
+  function inGroupsOf(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  }
+
+  function inject(memo, iterator, context) {
+    this.each(function(value, index) {
+      memo = iterator.call(context, memo, value, index);
+    });
+    return memo;
+  }
+
+  function invoke(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  }
+
+  function max(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator.call(context, value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  }
+
+  function min(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator.call(context, value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  }
+
+  function partition(iterator, context) {
+    iterator = iterator || Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator.call(context, value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  }
+
+  function pluck(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  }
+
+  function reject(iterator, context) {
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator.call(context, value, index))
+        results.push(value);
+    });
+    return results;
+  }
+
+  function sortBy(iterator, context) {
+    return this.map(function(value, index) {
+      return {
+        value: value,
+        criteria: iterator.call(context, value, index)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  }
+
+  function toArray() {
+    return this.map();
+  }
+
+  function zip() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  }
+
+  function size() {
+    return this.toArray().length;
+  }
+
+  function inspect() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+
+
+
+
+
+
+
+
+
+  return {
+    each:       each,
+    eachSlice:  eachSlice,
+    all:        all,
+    every:      all,
+    any:        any,
+    some:       any,
+    collect:    collect,
+    map:        collect,
+    detect:     detect,
+    findAll:    findAll,
+    select:     findAll,
+    filter:     findAll,
+    grep:       grep,
+    include:    include,
+    member:     include,
+    inGroupsOf: inGroupsOf,
+    inject:     inject,
+    invoke:     invoke,
+    max:        max,
+    min:        min,
+    partition:  partition,
+    pluck:      pluck,
+    reject:     reject,
+    sortBy:     sortBy,
+    toArray:    toArray,
+    entries:    toArray,
+    zip:        zip,
+    size:       size,
+    inspect:    inspect,
+    find:       detect
+  };
+})();
+
+function $A(iterable) {
+  if (!iterable) return [];
+  if ('toArray' in Object(iterable)) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+Array.from = $A;
+
+
+(function() {
+  var arrayProto = Array.prototype,
+      slice = arrayProto.slice,
+      _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available
+
+  function each(iterator, context) {
+    for (var i = 0, length = this.length >>> 0; i < length; i++) {
+      if (i in this) iterator.call(context, this[i], i, this);
+    }
+  }
+  if (!_each) _each = each;
+
+  function clear() {
+    this.length = 0;
+    return this;
+  }
+
+  function first() {
+    return this[0];
+  }
+
+  function last() {
+    return this[this.length - 1];
+  }
+
+  function compact() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  }
+
+  function flatten() {
+    return this.inject([], function(array, value) {
+      if (Object.isArray(value))
+        return array.concat(value.flatten());
+      array.push(value);
+      return array;
+    });
+  }
+
+  function without() {
+    var values = slice.call(arguments, 0);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  }
+
+  function reverse(inline) {
+    return (inline === false ? this.toArray() : this)._reverse();
+  }
+
+  function uniq(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  }
+
+  function intersect(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  }
+
+
+  function clone() {
+    return slice.call(this, 0);
+  }
+
+  function size() {
+    return this.length;
+  }
+
+  function inspect() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  }
+
+  function indexOf(item, i) {
+    i || (i = 0);
+    var length = this.length;
+    if (i < 0) i = length + i;
+    for (; i < length; i++)
+      if (this[i] === item) return i;
+    return -1;
+  }
+
+  function lastIndexOf(item, i) {
+    i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+    var n = this.slice(0, i).reverse().indexOf(item);
+    return (n < 0) ? n : i - n - 1;
+  }
+
+  function concat() {
+    var array = slice.call(this, 0), item;
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      item = arguments[i];
+      if (Object.isArray(item) && !('callee' in item)) {
+        for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
+          array.push(item[j]);
+      } else {
+        array.push(item);
+      }
+    }
+    return array;
+  }
+
+  Object.extend(arrayProto, Enumerable);
+
+  if (!arrayProto._reverse)
+    arrayProto._reverse = arrayProto.reverse;
+
+  Object.extend(arrayProto, {
+    _each:     _each,
+    clear:     clear,
+    first:     first,
+    last:      last,
+    compact:   compact,
+    flatten:   flatten,
+    without:   without,
+    reverse:   reverse,
+    uniq:      uniq,
+    intersect: intersect,
+    clone:     clone,
+    toArray:   clone,
+    size:      size,
+    inspect:   inspect
+  });
+
+  var CONCAT_ARGUMENTS_BUGGY = (function() {
+    return [].concat(arguments)[0][0] !== 1;
+  })(1,2)
+
+  if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;
+
+  if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
+  if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
+})();
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+  function initialize(object) {
+    this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+  }
+
+
+  function _each(iterator) {
+    for (var key in this._object) {
+      var value = this._object[key], pair = [key, value];
+      pair.key = key;
+      pair.value = value;
+      iterator(pair);
+    }
+  }
+
+  function set(key, value) {
+    return this._object[key] = value;
+  }
+
+  function get(key) {
+    if (this._object[key] !== Object.prototype[key])
+      return this._object[key];
+  }
+
+  function unset(key) {
+    var value = this._object[key];
+    delete this._object[key];
+    return value;
+  }
+
+  function toObject() {
+    return Object.clone(this._object);
+  }
+
+
+
+  function keys() {
+    return this.pluck('key');
+  }
+
+  function values() {
+    return this.pluck('value');
+  }
+
+  function index(value) {
+    var match = this.detect(function(pair) {
+      return pair.value === value;
+    });
+    return match && match.key;
+  }
+
+  function merge(object) {
+    return this.clone().update(object);
+  }
+
+  function update(object) {
+    return new Hash(object).inject(this, function(result, pair) {
+      result.set(pair.key, pair.value);
+      return result;
+    });
+  }
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  function toQueryString() {
+    return this.inject([], function(results, pair) {
+      var key = encodeURIComponent(pair.key), values = pair.value;
+
+      if (values && typeof values == 'object') {
+        if (Object.isArray(values)) {
+          var queryValues = [];
+          for (var i = 0, len = values.length, value; i < len; i++) {
+            value = values[i];
+            queryValues.push(toQueryPair(key, value));
+          }
+          return results.concat(queryValues);
+        }
+      } else results.push(toQueryPair(key, values));
+      return results;
+    }).join('&');
+  }
+
+  function inspect() {
+    return '#<Hash:{' + this.map(function(pair) {
+      return pair.map(Object.inspect).join(': ');
+    }).join(', ') + '}>';
+  }
+
+  function clone() {
+    return new Hash(this);
+  }
+
+  return {
+    initialize:             initialize,
+    _each:                  _each,
+    set:                    set,
+    get:                    get,
+    unset:                  unset,
+    toObject:               toObject,
+    toTemplateReplacements: toObject,
+    keys:                   keys,
+    values:                 values,
+    index:                  index,
+    merge:                  merge,
+    update:                 update,
+    toQueryString:          toQueryString,
+    inspect:                inspect,
+    toJSON:                 toObject,
+    clone:                  clone
+  };
+})());
+
+Hash.from = $H;
+Object.extend(Number.prototype, (function() {
+  function toColorPart() {
+    return this.toPaddedString(2, 16);
+  }
+
+  function succ() {
+    return this + 1;
+  }
+
+  function times(iterator, context) {
+    $R(0, this, true).each(iterator, context);
+    return this;
+  }
+
+  function toPaddedString(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  }
+
+  function abs() {
+    return Math.abs(this);
+  }
+
+  function round() {
+    return Math.round(this);
+  }
+
+  function ceil() {
+    return Math.ceil(this);
+  }
+
+  function floor() {
+    return Math.floor(this);
+  }
+
+  return {
+    toColorPart:    toColorPart,
+    succ:           succ,
+    times:          times,
+    toPaddedString: toPaddedString,
+    abs:            abs,
+    round:          round,
+    ceil:           ceil,
+    floor:          floor
+  };
+})());
+
+function $R(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+}
+
+var ObjectRange = Class.create(Enumerable, (function() {
+  function initialize(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  }
+
+  function _each(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  }
+
+  function include(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+
+  return {
+    initialize: initialize,
+    _each:      _each,
+    include:    include
+  };
+})());
+
+
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.isString(this.options.parameters) ?
+          this.options.parameters :
+          Object.toQueryString(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      params += (params ? '&' : '') + "_method=" + this.method;
+      this.method = 'post';
+    }
+
+    if (params && this.method === 'get') {
+      this.url += (this.url.include('?') ? '&' : '?') + params;
+    }
+
+    this.parameters = params.toQueryParams();
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300) || status == 304;
+  },
+
+  getStatus: function() {
+    try {
+      if (this.transport.status === 1223) return 204;
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && this.isSameOrigin() && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null; }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+
+
+
+
+
+
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if (readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+
+
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+
+
+(function(global) {
+  function shouldUseCache(tagName, attributes) {
+    if (tagName === 'select') return false;
+    if ('type' in attributes) return false;
+    return true;
+  }
+
+  var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){
+    try {
+      var el = document.createElement('<input name="x">');
+      return el.tagName.toLowerCase() === 'input' && el.name === 'x';
+    }
+    catch(err) {
+      return false;
+    }
+  })();
+
+  var element = global.Element;
+
+  global.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+
+    if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+
+    var node = shouldUseCache(tagName, attributes) ?
+     cache[tagName].cloneNode(false) : document.createElement(tagName);
+
+    return Element.writeAttribute(node, attributes);
+  };
+
+  Object.extend(global.Element, element || { });
+  if (element) global.Element.prototype = element.prototype;
+
+})(this);
+
+Element.idCounter = 1;
+Element.cache = { };
+
+Element._purgeElement = function(element) {
+  var uid = element._prototypeUID;
+  if (uid) {
+    Element.stopObserving(element);
+    element._prototypeUID = void 0;
+    delete Element.Storage[uid];
+  }
+}
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    element = $(element);
+    element.style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    element = $(element);
+    element.style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: (function(){
+
+    var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
+      var el = document.createElement("select"),
+          isBuggy = true;
+      el.innerHTML = "<option value=\"test\">test</option>";
+      if (el.options && el.options[0]) {
+        isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
+      }
+      el = null;
+      return isBuggy;
+    })();
+
+    var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
+      try {
+        var el = document.createElement("table");
+        if (el && el.tBodies) {
+          el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
+          var isBuggy = typeof el.tBodies[0] == "undefined";
+          el = null;
+          return isBuggy;
+        }
+      } catch (e) {
+        return true;
+      }
+    })();
+
+    var LINK_ELEMENT_INNERHTML_BUGGY = (function() {
+      try {
+        var el = document.createElement('div');
+        el.innerHTML = "<link>";
+        var isBuggy = (el.childNodes.length === 0);
+        el = null;
+        return isBuggy;
+      } catch(e) {
+        return true;
+      }
+    })();
+
+    var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY ||
+     TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY;
+
+    var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
+      var s = document.createElement("script"),
+          isBuggy = false;
+      try {
+        s.appendChild(document.createTextNode(""));
+        isBuggy = !s.firstChild ||
+          s.firstChild && s.firstChild.nodeType !== 3;
+      } catch (e) {
+        isBuggy = true;
+      }
+      s = null;
+      return isBuggy;
+    })();
+
+
+    function update(element, content) {
+      element = $(element);
+      var purgeElement = Element._purgeElement;
+
+      var descendants = element.getElementsByTagName('*'),
+       i = descendants.length;
+      while (i--) purgeElement(descendants[i]);
+
+      if (content && content.toElement)
+        content = content.toElement();
+
+      if (Object.isElement(content))
+        return element.update().insert(content);
+
+      content = Object.toHTML(content);
+
+      var tagName = element.tagName.toUpperCase();
+
+      if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
+        element.text = content;
+        return element;
+      }
+
+      if (ANY_INNERHTML_BUGGY) {
+        if (tagName in Element._insertionTranslations.tags) {
+          while (element.firstChild) {
+            element.removeChild(element.firstChild);
+          }
+          Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+            .each(function(node) {
+              element.appendChild(node)
+            });
+        } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf('<link') > -1) {
+          while (element.firstChild) {
+            element.removeChild(element.firstChild);
+          }
+          var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true);
+          nodes.each(function(node) { element.appendChild(node) });
+        }
+        else {
+          element.innerHTML = content.stripScripts();
+        }
+      }
+      else {
+        element.innerHTML = content.stripScripts();
+      }
+
+      content.evalScripts.bind(content).defer();
+      return element;
+    }
+
+    return update;
+  })(),
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(),
+          attribute = pair.last(),
+          value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property, maximumLength) {
+    element = $(element);
+    maximumLength = maximumLength || -1;
+    var elements = [];
+
+    while (element = element[property]) {
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+      if (elements.length == maximumLength)
+        break;
+    }
+
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return Element.recursivelyCollect(element, 'parentNode');
+  },
+
+  descendants: function(element) {
+    return Element.select(element, "*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    var results = [], child = $(element).firstChild;
+    while (child) {
+      if (child.nodeType === 1) {
+        results.push(Element.extend(child));
+      }
+      child = child.nextSibling;
+    }
+    return results;
+  },
+
+  previousSiblings: function(element, maximumLength) {
+    return Element.recursivelyCollect(element, 'previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return Element.recursivelyCollect(element, 'nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return Element.previousSiblings(element).reverse()
+      .concat(Element.nextSiblings(element));
+  },
+
+  match: function(element, selector) {
+    element = $(element);
+    if (Object.isString(selector))
+      return Prototype.Selector.match(element, selector);
+    return selector.match(element);
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = Element.ancestors(element);
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Prototype.Selector.find(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return Element.firstDescendant(element);
+    return Object.isNumber(expression) ? Element.descendants(element)[expression] :
+      Element.select(element, expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (Object.isNumber(expression)) index = expression, expression = false;
+    if (!Object.isNumber(index)) index = 0;
+
+    if (expression) {
+      return Prototype.Selector.find(element.previousSiblings(), expression, index);
+    } else {
+      return element.recursivelyCollect("previousSibling", index + 1)[index];
+    }
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (Object.isNumber(expression)) index = expression, expression = false;
+    if (!Object.isNumber(index)) index = 0;
+
+    if (expression) {
+      return Prototype.Selector.find(element.nextSiblings(), expression, index);
+    } else {
+      var maximumLength = Object.isNumber(index) ? index + 1 : 1;
+      return element.recursivelyCollect("nextSibling", index + 1)[index];
+    }
+  },
+
+
+  select: function(element) {
+    element = $(element);
+    var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+    return Prototype.Selector.select(expressions, element);
+  },
+
+  adjacent: function(element) {
+    element = $(element);
+    var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+    return Prototype.Selector.select(expressions, element.parentNode).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = Element.readAttribute(element, 'id');
+    if (id) return id;
+    do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
+    Element.writeAttribute(element, 'id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return Element.getDimensions(element).height;
+  },
+
+  getWidth: function(element) {
+    return Element.getDimensions(element).width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!Element.hasClassName(element, className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return Element[Element.hasClassName(element, className) ?
+      'removeClassName' : 'addClassName'](element, className);
+  },
+
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (ancestor.contains)
+      return ancestor.contains(element) && ancestor !== element;
+
+    while (element = element.parentNode)
+      if (element == ancestor) return true;
+
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = Element.cumulativeOffset(element);
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value || value == 'auto') {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      if (Prototype.Browser.Opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    source = $(source);
+    var p = Element.viewportOffset(source), delta = [0, 0], parent = null;
+
+    element = $(element);
+
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = Element.getOffsetParent(element);
+      delta = Element.viewportOffset(parent);
+    }
+
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'height': case 'width':
+          if (!Element.visible(element)) return null;
+
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = (function(){
+
+    var classProp = 'className',
+        forProp = 'for',
+        el = document.createElement('div');
+
+    el.setAttribute(classProp, 'x');
+
+    if (el.className !== 'x') {
+      el.setAttribute('class', 'x');
+      if (el.className === 'x') {
+        classProp = 'class';
+      }
+    }
+    el = null;
+
+    el = document.createElement('label');
+    el.setAttribute(forProp, 'x');
+    if (el.htmlFor !== 'x') {
+      el.setAttribute('htmlFor', 'x');
+      if (el.htmlFor === 'x') {
+        forProp = 'htmlFor';
+      }
+    }
+    el = null;
+
+    return {
+      read: {
+        names: {
+          'class':      classProp,
+          'className':  classProp,
+          'for':        forProp,
+          'htmlFor':    forProp
+        },
+        values: {
+          _getAttr: function(element, attribute) {
+            return element.getAttribute(attribute);
+          },
+          _getAttr2: function(element, attribute) {
+            return element.getAttribute(attribute, 2);
+          },
+          _getAttrNode: function(element, attribute) {
+            var node = element.getAttributeNode(attribute);
+            return node ? node.value : "";
+          },
+          _getEv: (function(){
+
+            var el = document.createElement('div'), f;
+            el.onclick = Prototype.emptyFunction;
+            var value = el.getAttribute('onclick');
+
+            if (String(value).indexOf('{') > -1) {
+              f = function(element, attribute) {
+                attribute = element.getAttribute(attribute);
+                if (!attribute) return null;
+                attribute = attribute.toString();
+                attribute = attribute.split('{')[1];
+                attribute = attribute.split('}')[0];
+                return attribute.strip();
+              };
+            }
+            else if (value === '') {
+              f = function(element, attribute) {
+                attribute = element.getAttribute(attribute);
+                if (!attribute) return null;
+                return attribute.strip();
+              };
+            }
+            el = null;
+            return f;
+          })(),
+          _flag: function(element, attribute) {
+            return $(element).hasAttribute(attribute) ? attribute : null;
+          },
+          style: function(element) {
+            return element.style.cssText.toLowerCase();
+          },
+          title: function(element) {
+            return element.title;
+          }
+        }
+      }
+    }
+  })();
+
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr2,
+      src:         v._getAttr2,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+
+  if (Prototype.BrowserFeatures.ElementExtensions) {
+    (function() {
+      function _descendants(element) {
+        var nodes = element.getElementsByTagName('*'), results = [];
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName !== "!") // Filter out comment nodes.
+            results.push(node);
+        return results;
+      }
+
+      Element.Methods.down = function(element, expression, index) {
+        element = $(element);
+        if (arguments.length == 1) return element.firstDescendant();
+        return Object.isNumber(expression) ? _descendants(element)[expression] :
+          Element.select(element, expression)[index || 0];
+      }
+    })();
+  }
+
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if (element.tagName.toUpperCase() == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+}
+
+if ('outerHTML' in document.documentElement) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next(),
+          fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html, force) {
+  var div = new Element('div'),
+      t = Element._insertionTranslations.tags[tagName];
+
+  var workaround = false;
+  if (t) workaround = true;
+  else if (force) {
+    workaround = true;
+    t = ['', '', 0];
+  }
+
+  if (workaround) {
+    div.innerHTML = '&nbsp;' + t[0] + html + t[1];
+    div.removeChild(div.firstChild);
+    for (var i = t[2]; i--; ) {
+      div = div.firstChild;
+    }
+  }
+  else {
+    div.innerHTML = html;
+  }
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  var tags = Element._insertionTranslations.tags;
+  Object.extend(tags, {
+    THEAD: tags.TBODY,
+    TFOOT: tags.TBODY,
+    TH:    tags.TD
+  });
+})();
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return !!(node && node.specified);
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+(function(div) {
+
+  if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) {
+    window.HTMLElement = { };
+    window.HTMLElement.prototype = div['__proto__'];
+    Prototype.BrowserFeatures.ElementExtensions = true;
+  }
+
+  div = null;
+
+})(document.createElement('div'));
+
+Element.extend = (function() {
+
+  function checkDeficiency(tagName) {
+    if (typeof window.Element != 'undefined') {
+      var proto = window.Element.prototype;
+      if (proto) {
+        var id = '_' + (Math.random()+'').slice(2),
+            el = document.createElement(tagName);
+        proto[id] = 'x';
+        var isBuggy = (el[id] !== 'x');
+        delete proto[id];
+        el = null;
+        return isBuggy;
+      }
+    }
+    return false;
+  }
+
+  function extendElementWith(element, methods) {
+    for (var property in methods) {
+      var value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+  }
+
+  var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');
+
+  if (Prototype.BrowserFeatures.SpecificElementExtensions) {
+    if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
+      return function(element) {
+        if (element && typeof element._extendedByPrototype == 'undefined') {
+          var t = element.tagName;
+          if (t && (/^(?:object|applet|embed)$/i.test(t))) {
+            extendElementWith(element, Element.Methods);
+            extendElementWith(element, Element.Methods.Simulated);
+            extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
+          }
+        }
+        return element;
+      }
+    }
+    return Prototype.K;
+  }
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || typeof element._extendedByPrototype != 'undefined' ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+        tagName = element.tagName.toUpperCase();
+
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    extendElementWith(element, methods);
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+if (document.documentElement.hasAttribute) {
+  Element.hasAttribute = function(element, attribute) {
+    return element.hasAttribute(attribute);
+  };
+}
+else {
+  Element.hasAttribute = Element.Methods.Simulated.hasAttribute;
+}
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods),
+      "BUTTON":   Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    var element = document.createElement(tagName),
+        proto = element['__proto__'] || element.constructor.prototype;
+
+    element = null;
+    return proto;
+  }
+
+  var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
+   Element.prototype;
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, elementPrototype);
+    copy(Element.Methods.Simulated, elementPrototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+
+document.viewport = {
+
+  getDimensions: function() {
+    return { width: this.getWidth(), height: this.getHeight() };
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop  || document.body.scrollTop);
+  }
+};
+
+(function(viewport) {
+  var B = Prototype.Browser, doc = document, element, property = {};
+
+  function getRootElement() {
+    if (B.WebKit && !doc.evaluate)
+      return document;
+
+    if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
+      return document.body;
+
+    return document.documentElement;
+  }
+
+  function define(D) {
+    if (!element) element = getRootElement();
+
+    property[D] = 'client' + D;
+
+    viewport['get' + D] = function() { return element[property[D]] };
+    return viewport['get' + D]();
+  }
+
+  viewport.getWidth  = define.curry('Width');
+
+  viewport.getHeight = define.curry('Height');
+})(document.viewport);
+
+
+Element.Storage = {
+  UID: 1
+};
+
+Element.addMethods({
+  getStorage: function(element) {
+    if (!(element = $(element))) return;
+
+    var uid;
+    if (element === window) {
+      uid = 0;
+    } else {
+      if (typeof element._prototypeUID === "undefined")
+        element._prototypeUID = Element.Storage.UID++;
+      uid = element._prototypeUID;
+    }
+
+    if (!Element.Storage[uid])
+      Element.Storage[uid] = $H();
+
+    return Element.Storage[uid];
+  },
+
+  store: function(element, key, value) {
+    if (!(element = $(element))) return;
+
+    if (arguments.length === 2) {
+      Element.getStorage(element).update(key);
+    } else {
+      Element.getStorage(element).set(key, value);
+    }
+
+    return element;
+  },
+
+  retrieve: function(element, key, defaultValue) {
+    if (!(element = $(element))) return;
+    var hash = Element.getStorage(element), value = hash.get(key);
+
+    if (Object.isUndefined(value)) {
+      hash.set(key, defaultValue);
+      value = defaultValue;
+    }
+
+    return value;
+  },
+
+  clone: function(element, deep) {
+    if (!(element = $(element))) return;
+    var clone = element.cloneNode(deep);
+    clone._prototypeUID = void 0;
+    if (deep) {
+      var descendants = Element.select(clone, '*'),
+          i = descendants.length;
+      while (i--) {
+        descendants[i]._prototypeUID = void 0;
+      }
+    }
+    return Element.extend(clone);
+  },
+
+  purge: function(element) {
+    if (!(element = $(element))) return;
+    var purgeElement = Element._purgeElement;
+
+    purgeElement(element);
+
+    var descendants = element.getElementsByTagName('*'),
+     i = descendants.length;
+
+    while (i--) purgeElement(descendants[i]);
+
+    return null;
+  }
+});
+
+(function() {
+
+  function toDecimal(pctString) {
+    var match = pctString.match(/^(\d+)%?$/i);
+    if (!match) return null;
+    return (Number(match[1]) / 100);
+  }
+
+  function getPixelValue(value, property, context) {
+    var element = null;
+    if (Object.isElement(value)) {
+      element = value;
+      value = element.getStyle(property);
+    }
+
+    if (value === null) {
+      return null;
+    }
+
+    if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) {
+      return window.parseFloat(value);
+    }
+
+    var isPercentage = value.include('%'), isViewport = (context === document.viewport);
+
+    if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) {
+      var style = element.style.left, rStyle = element.runtimeStyle.left;
+      element.runtimeStyle.left = element.currentStyle.left;
+      element.style.left = value || 0;
+      value = element.style.pixelLeft;
+      element.style.left = style;
+      element.runtimeStyle.left = rStyle;
+
+      return value;
+    }
+
+    if (element && isPercentage) {
+      context = context || element.parentNode;
+      var decimal = toDecimal(value);
+      var whole = null;
+      var position = element.getStyle('position');
+
+      var isHorizontal = property.include('left') || property.include('right') ||
+       property.include('width');
+
+      var isVertical =  property.include('top') || property.include('bottom') ||
+        property.include('height');
+
+      if (context === document.viewport) {
+        if (isHorizontal) {
+          whole = document.viewport.getWidth();
+        } else if (isVertical) {
+          whole = document.viewport.getHeight();
+        }
+      } else {
+        if (isHorizontal) {
+          whole = $(context).measure('width');
+        } else if (isVertical) {
+          whole = $(context).measure('height');
+        }
+      }
+
+      return (whole === null) ? 0 : whole * decimal;
+    }
+
+    return 0;
+  }
+
+  function toCSSPixels(number) {
+    if (Object.isString(number) && number.endsWith('px')) {
+      return number;
+    }
+    return number + 'px';
+  }
+
+  function isDisplayed(element) {
+    var originalElement = element;
+    while (element && element.parentNode) {
+      var display = element.getStyle('display');
+      if (display === 'none') {
+        return false;
+      }
+      element = $(element.parentNode);
+    }
+    return true;
+  }
+
+  var hasLayout = Prototype.K;
+  if ('currentStyle' in document.documentElement) {
+    hasLayout = function(element) {
+      if (!element.currentStyle.hasLayout) {
+        element.style.zoom = 1;
+      }
+      return element;
+    };
+  }
+
+  function cssNameFor(key) {
+    if (key.include('border')) key = key + '-width';
+    return key.camelize();
+  }
+
+  Element.Layout = Class.create(Hash, {
+    initialize: function($super, element, preCompute) {
+      $super();
+      this.element = $(element);
+
+      Element.Layout.PROPERTIES.each( function(property) {
+        this._set(property, null);
+      }, this);
+
+      if (preCompute) {
+        this._preComputing = true;
+        this._begin();
+        Element.Layout.PROPERTIES.each( this._compute, this );
+        this._end();
+        this._preComputing = false;
+      }
+    },
+
+    _set: function(property, value) {
+      return Hash.prototype.set.call(this, property, value);
+    },
+
+    set: function(property, value) {
+      throw "Properties of Element.Layout are read-only.";
+    },
+
+    get: function($super, property) {
+      var value = $super(property);
+      return value === null ? this._compute(property) : value;
+    },
+
+    _begin: function() {
+      if (this._prepared) return;
+
+      var element = this.element;
+      if (isDisplayed(element)) {
+        this._prepared = true;
+        return;
+      }
+
+      var originalStyles = {
+        position:   element.style.position   || '',
+        width:      element.style.width      || '',
+        visibility: element.style.visibility || '',
+        display:    element.style.display    || ''
+      };
+
+      element.store('prototype_original_styles', originalStyles);
+
+      var position = element.getStyle('position'),
+       width = element.getStyle('width');
+
+      if (width === "0px" || width === null) {
+        element.style.display = 'block';
+        width = element.getStyle('width');
+      }
+
+      var context = (position === 'fixed') ? document.viewport :
+       element.parentNode;
+
+      element.setStyle({
+        position:   'absolute',
+        visibility: 'hidden',
+        display:    'block'
+      });
+
+      var positionedWidth = element.getStyle('width');
+
+      var newWidth;
+      if (width && (positionedWidth === width)) {
+        newWidth = getPixelValue(element, 'width', context);
+      } else if (position === 'absolute' || position === 'fixed') {
+        newWidth = getPixelValue(element, 'width', context);
+      } else {
+        var parent = element.parentNode, pLayout = $(parent).getLayout();
+
+        newWidth = pLayout.get('width') -
+         this.get('margin-left') -
+         this.get('border-left') -
+         this.get('padding-left') -
+         this.get('padding-right') -
+         this.get('border-right') -
+         this.get('margin-right');
+      }
+
+      element.setStyle({ width: newWidth + 'px' });
+
+      this._prepared = true;
+    },
+
+    _end: function() {
+      var element = this.element;
+      var originalStyles = element.retrieve('prototype_original_styles');
+      element.store('prototype_original_styles', null);
+      element.setStyle(originalStyles);
+      this._prepared = false;
+    },
+
+    _compute: function(property) {
+      var COMPUTATIONS = Element.Layout.COMPUTATIONS;
+      if (!(property in COMPUTATIONS)) {
+        throw "Property not found.";
+      }
+
+      return this._set(property, COMPUTATIONS[property].call(this, this.element));
+    },
+
+    toObject: function() {
+      var args = $A(arguments);
+      var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+       args.join(' ').split(' ');
+      var obj = {};
+      keys.each( function(key) {
+        if (!Element.Layout.PROPERTIES.include(key)) return;
+        var value = this.get(key);
+        if (value != null) obj[key] = value;
+      }, this);
+      return obj;
+    },
+
+    toHash: function() {
+      var obj = this.toObject.apply(this, arguments);
+      return new Hash(obj);
+    },
+
+    toCSS: function() {
+      var args = $A(arguments);
+      var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+       args.join(' ').split(' ');
+      var css = {};
+
+      keys.each( function(key) {
+        if (!Element.Layout.PROPERTIES.include(key)) return;
+        if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return;
+
+        var value = this.get(key);
+        if (value != null) css[cssNameFor(key)] = value + 'px';
+      }, this);
+      return css;
+    },
+
+    inspect: function() {
+      return "#<Element.Layout>";
+    }
+  });
+
+  Object.extend(Element.Layout, {
+    PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'),
+
+    COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'),
+
+    COMPUTATIONS: {
+      'height': function(element) {
+        if (!this._preComputing) this._begin();
+
+        var bHeight = this.get('border-box-height');
+        if (bHeight <= 0) {
+          if (!this._preComputing) this._end();
+          return 0;
+        }
+
+        var bTop = this.get('border-top'),
+         bBottom = this.get('border-bottom');
+
+        var pTop = this.get('padding-top'),
+         pBottom = this.get('padding-bottom');
+
+        if (!this._preComputing) this._end();
+
+        return bHeight - bTop - bBottom - pTop - pBottom;
+      },
+
+      'width': function(element) {
+        if (!this._preComputing) this._begin();
+
+        var bWidth = this.get('border-box-width');
+        if (bWidth <= 0) {
+          if (!this._preComputing) this._end();
+          return 0;
+        }
+
+        var bLeft = this.get('border-left'),
+         bRight = this.get('border-right');
+
+        var pLeft = this.get('padding-left'),
+         pRight = this.get('padding-right');
+
+        if (!this._preComputing) this._end();
+
+        return bWidth - bLeft - bRight - pLeft - pRight;
+      },
+
+      'padding-box-height': function(element) {
+        var height = this.get('height'),
+         pTop = this.get('padding-top'),
+         pBottom = this.get('padding-bottom');
+
+        return height + pTop + pBottom;
+      },
+
+      'padding-box-width': function(element) {
+        var width = this.get('width'),
+         pLeft = this.get('padding-left'),
+         pRight = this.get('padding-right');
+
+        return width + pLeft + pRight;
+      },
+
+      'border-box-height': function(element) {
+        if (!this._preComputing) this._begin();
+        var height = element.offsetHeight;
+        if (!this._preComputing) this._end();
+        return height;
+      },
+
+      'border-box-width': function(element) {
+        if (!this._preComputing) this._begin();
+        var width = element.offsetWidth;
+        if (!this._preComputing) this._end();
+        return width;
+      },
+
+      'margin-box-height': function(element) {
+        var bHeight = this.get('border-box-height'),
+         mTop = this.get('margin-top'),
+         mBottom = this.get('margin-bottom');
+
+        if (bHeight <= 0) return 0;
+
+        return bHeight + mTop + mBottom;
+      },
+
+      'margin-box-width': function(element) {
+        var bWidth = this.get('border-box-width'),
+         mLeft = this.get('margin-left'),
+         mRight = this.get('margin-right');
+
+        if (bWidth <= 0) return 0;
+
+        return bWidth + mLeft + mRight;
+      },
+
+      'top': function(element) {
+        var offset = element.positionedOffset();
+        return offset.top;
+      },
+
+      'bottom': function(element) {
+        var offset = element.positionedOffset(),
+         parent = element.getOffsetParent(),
+         pHeight = parent.measure('height');
+
+        var mHeight = this.get('border-box-height');
+
+        return pHeight - mHeight - offset.top;
+      },
+
+      'left': function(element) {
+        var offset = element.positionedOffset();
+        return offset.left;
+      },
+
+      'right': function(element) {
+        var offset = element.positionedOffset(),
+         parent = element.getOffsetParent(),
+         pWidth = parent.measure('width');
+
+        var mWidth = this.get('border-box-width');
+
+        return pWidth - mWidth - offset.left;
+      },
+
+      'padding-top': function(element) {
+        return getPixelValue(element, 'paddingTop');
+      },
+
+      'padding-bottom': function(element) {
+        return getPixelValue(element, 'paddingBottom');
+      },
+
+      'padding-left': function(element) {
+        return getPixelValue(element, 'paddingLeft');
+      },
+
+      'padding-right': function(element) {
+        return getPixelValue(element, 'paddingRight');
+      },
+
+      'border-top': function(element) {
+        return getPixelValue(element, 'borderTopWidth');
+      },
+
+      'border-bottom': function(element) {
+        return getPixelValue(element, 'borderBottomWidth');
+      },
+
+      'border-left': function(element) {
+        return getPixelValue(element, 'borderLeftWidth');
+      },
+
+      'border-right': function(element) {
+        return getPixelValue(element, 'borderRightWidth');
+      },
+
+      'margin-top': function(element) {
+        return getPixelValue(element, 'marginTop');
+      },
+
+      'margin-bottom': function(element) {
+        return getPixelValue(element, 'marginBottom');
+      },
+
+      'margin-left': function(element) {
+        return getPixelValue(element, 'marginLeft');
+      },
+
+      'margin-right': function(element) {
+        return getPixelValue(element, 'marginRight');
+      }
+    }
+  });
+
+  if ('getBoundingClientRect' in document.documentElement) {
+    Object.extend(Element.Layout.COMPUTATIONS, {
+      'right': function(element) {
+        var parent = hasLayout(element.getOffsetParent());
+        var rect = element.getBoundingClientRect(),
+         pRect = parent.getBoundingClientRect();
+
+        return (pRect.right - rect.right).round();
+      },
+
+      'bottom': function(element) {
+        var parent = hasLayout(element.getOffsetParent());
+        var rect = element.getBoundingClientRect(),
+         pRect = parent.getBoundingClientRect();
+
+        return (pRect.bottom - rect.bottom).round();
+      }
+    });
+  }
+
+  Element.Offset = Class.create({
+    initialize: function(left, top) {
+      this.left = left.round();
+      this.top  = top.round();
+
+      this[0] = this.left;
+      this[1] = this.top;
+    },
+
+    relativeTo: function(offset) {
+      return new Element.Offset(
+        this.left - offset.left,
+        this.top  - offset.top
+      );
+    },
+
+    inspect: function() {
+      return "#<Element.Offset left: #{left} top: #{top}>".interpolate(this);
+    },
+
+    toString: function() {
+      return "[#{left}, #{top}]".interpolate(this);
+    },
+
+    toArray: function() {
+      return [this.left, this.top];
+    }
+  });
+
+  function getLayout(element, preCompute) {
+    return new Element.Layout(element, preCompute);
+  }
+
+  function measure(element, property) {
+    return $(element).getLayout().get(property);
+  }
+
+  function getDimensions(element) {
+    element = $(element);
+    var display = Element.getStyle(element, 'display');
+
+    if (display && display !== 'none') {
+      return { width: element.offsetWidth, height: element.offsetHeight };
+    }
+
+    var style = element.style;
+    var originalStyles = {
+      visibility: style.visibility,
+      position:   style.position,
+      display:    style.display
+    };
+
+    var newStyles = {
+      visibility: 'hidden',
+      display:    'block'
+    };
+
+    if (originalStyles.position !== 'fixed')
+      newStyles.position = 'absolute';
+
+    Element.setStyle(element, newStyles);
+
+    var dimensions = {
+      width:  element.offsetWidth,
+      height: element.offsetHeight
+    };
+
+    Element.setStyle(element, originalStyles);
+
+    return dimensions;
+  }
+
+  function getOffsetParent(element) {
+    element = $(element);
+
+    if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element))
+      return $(document.body);
+
+    var isInline = (Element.getStyle(element, 'display') === 'inline');
+    if (!isInline && element.offsetParent) return $(element.offsetParent);
+
+    while ((element = element.parentNode) && element !== document.body) {
+      if (Element.getStyle(element, 'position') !== 'static') {
+        return isHtml(element) ? $(document.body) : $(element);
+      }
+    }
+
+    return $(document.body);
+  }
+
+
+  function cumulativeOffset(element) {
+    element = $(element);
+    var valueT = 0, valueL = 0;
+    if (element.parentNode) {
+      do {
+        valueT += element.offsetTop  || 0;
+        valueL += element.offsetLeft || 0;
+        element = element.offsetParent;
+      } while (element);
+    }
+    return new Element.Offset(valueL, valueT);
+  }
+
+  function positionedOffset(element) {
+    element = $(element);
+
+    var layout = element.getLayout();
+
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (isBody(element)) break;
+        var p = Element.getStyle(element, 'position');
+        if (p !== 'static') break;
+      }
+    } while (element);
+
+    valueL -= layout.get('margin-top');
+    valueT -= layout.get('margin-left');
+
+    return new Element.Offset(valueL, valueT);
+  }
+
+  function cumulativeScrollOffset(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return new Element.Offset(valueL, valueT);
+  }
+
+  function viewportOffset(forElement) {
+    element = $(element);
+    var valueT = 0, valueL = 0, docBody = document.body;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == docBody &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (element != docBody) {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+    return new Element.Offset(valueL, valueT);
+  }
+
+  function absolutize(element) {
+    element = $(element);
+
+    if (Element.getStyle(element, 'position') === 'absolute') {
+      return element;
+    }
+
+    var offsetParent = getOffsetParent(element);
+    var eOffset = element.viewportOffset(),
+     pOffset = offsetParent.viewportOffset();
+
+    var offset = eOffset.relativeTo(pOffset);
+    var layout = element.getLayout();
+
+    element.store('prototype_absolutize_original_styles', {
+      left:   element.getStyle('left'),
+      top:    element.getStyle('top'),
+      width:  element.getStyle('width'),
+      height: element.getStyle('height')
+    });
+
+    element.setStyle({
+      position: 'absolute',
+      top:    offset.top + 'px',
+      left:   offset.left + 'px',
+      width:  layout.get('width') + 'px',
+      height: layout.get('height') + 'px'
+    });
+
+    return element;
+  }
+
+  function relativize(element) {
+    element = $(element);
+    if (Element.getStyle(element, 'position') === 'relative') {
+      return element;
+    }
+
+    var originalStyles =
+     element.retrieve('prototype_absolutize_original_styles');
+
+    if (originalStyles) element.setStyle(originalStyles);
+    return element;
+  }
+
+  if (Prototype.Browser.IE) {
+    getOffsetParent = getOffsetParent.wrap(
+      function(proceed, element) {
+        element = $(element);
+
+        if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element))
+          return $(document.body);
+
+        var position = element.getStyle('position');
+        if (position !== 'static') return proceed(element);
+
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+
+    positionedOffset = positionedOffset.wrap(function(proceed, element) {
+      element = $(element);
+      if (!element.parentNode) return new Element.Offset(0, 0);
+      var position = element.getStyle('position');
+      if (position !== 'static') return proceed(element);
+
+      var offsetParent = element.getOffsetParent();
+      if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+        hasLayout(offsetParent);
+
+      element.setStyle({ position: 'relative' });
+      var value = proceed(element);
+      element.setStyle({ position: position });
+      return value;
+    });
+  } else if (Prototype.Browser.Webkit) {
+    cumulativeOffset = function(element) {
+      element = $(element);
+      var valueT = 0, valueL = 0;
+      do {
+        valueT += element.offsetTop  || 0;
+        valueL += element.offsetLeft || 0;
+        if (element.offsetParent == document.body)
+          if (Element.getStyle(element, 'position') == 'absolute') break;
+
+        element = element.offsetParent;
+      } while (element);
+
+      return new Element.Offset(valueL, valueT);
+    };
+  }
+
+
+  Element.addMethods({
+    getLayout:              getLayout,
+    measure:                measure,
+    getDimensions:          getDimensions,
+    getOffsetParent:        getOffsetParent,
+    cumulativeOffset:       cumulativeOffset,
+    positionedOffset:       positionedOffset,
+    cumulativeScrollOffset: cumulativeScrollOffset,
+    viewportOffset:         viewportOffset,
+    absolutize:             absolutize,
+    relativize:             relativize
+  });
+
+  function isBody(element) {
+    return element.nodeName.toUpperCase() === 'BODY';
+  }
+
+  function isHtml(element) {
+    return element.nodeName.toUpperCase() === 'HTML';
+  }
+
+  function isDocument(element) {
+    return element.nodeType === Node.DOCUMENT_NODE;
+  }
+
+  function isDetached(element) {
+    return element !== document.body &&
+     !Element.descendantOf(element, document.body);
+  }
+
+  if ('getBoundingClientRect' in document.documentElement) {
+    Element.addMethods({
+      viewportOffset: function(element) {
+        element = $(element);
+        if (isDetached(element)) return new Element.Offset(0, 0);
+
+        var rect = element.getBoundingClientRect(),
+         docEl = document.documentElement;
+        return new Element.Offset(rect.left - docEl.clientLeft,
+         rect.top - docEl.clientTop);
+      }
+    });
+  }
+})();
+window.$$ = function() {
+  var expression = $A(arguments).join(', ');
+  return Prototype.Selector.select(expression, document);
+};
+
+Prototype.Selector = (function() {
+
+  function select() {
+    throw new Error('Method "Prototype.Selector.select" must be defined.');
+  }
+
+  function match() {
+    throw new Error('Method "Prototype.Selector.match" must be defined.');
+  }
+
+  function find(elements, expression, index) {
+    index = index || 0;
+    var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i;
+
+    for (i = 0; i < length; i++) {
+      if (match(elements[i], expression) && index == matchIndex++) {
+        return Element.extend(elements[i]);
+      }
+    }
+  }
+
+  function extendElements(elements) {
+    for (var i = 0, length = elements.length; i < length; i++) {
+      Element.extend(elements[i]);
+    }
+    return elements;
+  }
+
+
+  var K = Prototype.K;
+
+  return {
+    select: select,
+    match: match,
+    find: find,
+    extendElements: (Element.extend === K) ? K : extendElements,
+    extendElement: Element.extend
+  };
+})();
+Prototype._original_property = window.Sizzle;
+/*!
+ * Sizzle CSS Selector Engine - v1.0
+ *  Copyright 2009, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+       done = 0,
+       toString = Object.prototype.toString,
+       hasDuplicate = false,
+       baseHasDuplicate = true;
+
+[0, 0].sort(function(){
+       baseHasDuplicate = false;
+       return 0;
+});
+
+var Sizzle = function(selector, context, results, seed) {
+       results = results || [];
+       var origContext = context = context || document;
+
+       if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+               return [];
+       }
+
+       if ( !selector || typeof selector !== "string" ) {
+               return results;
+       }
+
+       var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context),
+               soFar = selector;
+
+       while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
+               soFar = m[3];
+
+               parts.push( m[1] );
+
+               if ( m[2] ) {
+                       extra = m[3];
+                       break;
+               }
+       }
+
+       if ( parts.length > 1 && origPOS.exec( selector ) ) {
+               if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+                       set = posProcess( parts[0] + parts[1], context );
+               } else {
+                       set = Expr.relative[ parts[0] ] ?
+                               [ context ] :
+                               Sizzle( parts.shift(), context );
+
+                       while ( parts.length ) {
+                               selector = parts.shift();
+
+                               if ( Expr.relative[ selector ] )
+                                       selector += parts.shift();
+
+                               set = posProcess( selector, set );
+                       }
+               }
+       } else {
+               if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+                               Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+                       var ret = Sizzle.find( parts.shift(), context, contextXML );
+                       context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
+               }
+
+               if ( context ) {
+                       var ret = seed ?
+                               { expr: parts.pop(), set: makeArray(seed) } :
+                               Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+                       set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
+
+                       if ( parts.length > 0 ) {
+                               checkSet = makeArray(set);
+                       } else {
+                               prune = false;
+                       }
+
+                       while ( parts.length ) {
+                               var cur = parts.pop(), pop = cur;
+
+                               if ( !Expr.relative[ cur ] ) {
+                                       cur = "";
+                               } else {
+                                       pop = parts.pop();
+                               }
+
+                               if ( pop == null ) {
+                                       pop = context;
+                               }
+
+                               Expr.relative[ cur ]( checkSet, pop, contextXML );
+                       }
+               } else {
+                       checkSet = parts = [];
+               }
+       }
+
+       if ( !checkSet ) {
+               checkSet = set;
+       }
+
+       if ( !checkSet ) {
+               throw "Syntax error, unrecognized expression: " + (cur || selector);
+       }
+
+       if ( toString.call(checkSet) === "[object Array]" ) {
+               if ( !prune ) {
+                       results.push.apply( results, checkSet );
+               } else if ( context && context.nodeType === 1 ) {
+                       for ( var i = 0; checkSet[i] != null; i++ ) {
+                               if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+                                       results.push( set[i] );
+                               }
+                       }
+               } else {
+                       for ( var i = 0; checkSet[i] != null; i++ ) {
+                               if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+                                       results.push( set[i] );
+                               }
+                       }
+               }
+       } else {
+               makeArray( checkSet, results );
+       }
+
+       if ( extra ) {
+               Sizzle( extra, origContext, results, seed );
+               Sizzle.uniqueSort( results );
+       }
+
+       return results;
+};
+
+Sizzle.uniqueSort = function(results){
+       if ( sortOrder ) {
+               hasDuplicate = baseHasDuplicate;
+               results.sort(sortOrder);
+
+               if ( hasDuplicate ) {
+                       for ( var i = 1; i < results.length; i++ ) {
+                               if ( results[i] === results[i-1] ) {
+                                       results.splice(i--, 1);
+                               }
+                       }
+               }
+       }
+
+       return results;
+};
+
+Sizzle.matches = function(expr, set){
+       return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+       var set, match;
+
+       if ( !expr ) {
+               return [];
+       }
+
+       for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+               var type = Expr.order[i], match;
+
+               if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+                       var left = match[1];
+                       match.splice(1,1);
+
+                       if ( left.substr( left.length - 1 ) !== "\\" ) {
+                               match[1] = (match[1] || "").replace(/\\/g, "");
+                               set = Expr.find[ type ]( match, context, isXML );
+                               if ( set != null ) {
+                                       expr = expr.replace( Expr.match[ type ], "" );
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       if ( !set ) {
+               set = context.getElementsByTagName("*");
+       }
+
+       return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+       var old = expr, result = [], curLoop = set, match, anyFound,
+               isXMLFilter = set && set[0] && isXML(set[0]);
+
+       while ( expr && set.length ) {
+               for ( var type in Expr.filter ) {
+                       if ( (match = Expr.match[ type ].exec( expr )) != null ) {
+                               var filter = Expr.filter[ type ], found, item;
+                               anyFound = false;
+
+                               if ( curLoop == result ) {
+                                       result = [];
+                               }
+
+                               if ( Expr.preFilter[ type ] ) {
+                                       match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+                                       if ( !match ) {
+                                               anyFound = found = true;
+                                       } else if ( match === true ) {
+                                               continue;
+                                       }
+                               }
+
+                               if ( match ) {
+                                       for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+                                               if ( item ) {
+                                                       found = filter( item, match, i, curLoop );
+                                                       var pass = not ^ !!found;
+
+                                                       if ( inplace && found != null ) {
+                                                               if ( pass ) {
+                                                                       anyFound = true;
+                                                               } else {
+                                                                       curLoop[i] = false;
+                                                               }
+                                                       } else if ( pass ) {
+                                                               result.push( item );
+                                                               anyFound = true;
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               if ( found !== undefined ) {
+                                       if ( !inplace ) {
+                                               curLoop = result;
+                                       }
+
+                                       expr = expr.replace( Expr.match[ type ], "" );
+
+                                       if ( !anyFound ) {
+                                               return [];
+                                       }
+
+                                       break;
+                               }
+                       }
+               }
+
+               if ( expr == old ) {
+                       if ( anyFound == null ) {
+                               throw "Syntax error, unrecognized expression: " + expr;
+                       } else {
+                               break;
+                       }
+               }
+
+               old = expr;
+       }
+
+       return curLoop;
+};
+
+var Expr = Sizzle.selectors = {
+       order: [ "ID", "NAME", "TAG" ],
+       match: {
+               ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+               CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+               NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
+               ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+               TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
+               CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+               POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+               PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
+       },
+       leftMatch: {},
+       attrMap: {
+               "class": "className",
+               "for": "htmlFor"
+       },
+       attrHandle: {
+               href: function(elem){
+                       return elem.getAttribute("href");
+               }
+       },
+       relative: {
+               "+": function(checkSet, part, isXML){
+                       var isPartStr = typeof part === "string",
+                               isTag = isPartStr && !/\W/.test(part),
+                               isPartStrNotTag = isPartStr && !isTag;
+
+                       if ( isTag && !isXML ) {
+                               part = part.toUpperCase();
+                       }
+
+                       for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+                               if ( (elem = checkSet[i]) ) {
+                                       while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+                                       checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
+                                               elem || false :
+                                               elem === part;
+                               }
+                       }
+
+                       if ( isPartStrNotTag ) {
+                               Sizzle.filter( part, checkSet, true );
+                       }
+               },
+               ">": function(checkSet, part, isXML){
+                       var isPartStr = typeof part === "string";
+
+                       if ( isPartStr && !/\W/.test(part) ) {
+                               part = isXML ? part : part.toUpperCase();
+
+                               for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+                                       var elem = checkSet[i];
+                                       if ( elem ) {
+                                               var parent = elem.parentNode;
+                                               checkSet[i] = parent.nodeName === part ? parent : false;
+                                       }
+                               }
+                       } else {
+                               for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+                                       var elem = checkSet[i];
+                                       if ( elem ) {
+                                               checkSet[i] = isPartStr ?
+                                                       elem.parentNode :
+                                                       elem.parentNode === part;
+                                       }
+                               }
+
+                               if ( isPartStr ) {
+                                       Sizzle.filter( part, checkSet, true );
+                               }
+                       }
+               },
+               "": function(checkSet, part, isXML){
+                       var doneName = done++, checkFn = dirCheck;
+
+                       if ( !/\W/.test(part) ) {
+                               var nodeCheck = part = isXML ? part : part.toUpperCase();
+                               checkFn = dirNodeCheck;
+                       }
+
+                       checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+               },
+               "~": function(checkSet, part, isXML){
+                       var doneName = done++, checkFn = dirCheck;
+
+                       if ( typeof part === "string" && !/\W/.test(part) ) {
+                               var nodeCheck = part = isXML ? part : part.toUpperCase();
+                               checkFn = dirNodeCheck;
+                       }
+
+                       checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+               }
+       },
+       find: {
+               ID: function(match, context, isXML){
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                               var m = context.getElementById(match[1]);
+                               return m ? [m] : [];
+                       }
+               },
+               NAME: function(match, context, isXML){
+                       if ( typeof context.getElementsByName !== "undefined" ) {
+                               var ret = [], results = context.getElementsByName(match[1]);
+
+                               for ( var i = 0, l = results.length; i < l; i++ ) {
+                                       if ( results[i].getAttribute("name") === match[1] ) {
+                                               ret.push( results[i] );
+                                       }
+                               }
+
+                               return ret.length === 0 ? null : ret;
+                       }
+               },
+               TAG: function(match, context){
+                       return context.getElementsByTagName(match[1]);
+               }
+       },
+       preFilter: {
+               CLASS: function(match, curLoop, inplace, result, not, isXML){
+                       match = " " + match[1].replace(/\\/g, "") + " ";
+
+                       if ( isXML ) {
+                               return match;
+                       }
+
+                       for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+                               if ( elem ) {
+                                       if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
+                                               if ( !inplace )
+                                                       result.push( elem );
+                                       } else if ( inplace ) {
+                                               curLoop[i] = false;
+                                       }
+                               }
+                       }
+
+                       return false;
+               },
+               ID: function(match){
+                       return match[1].replace(/\\/g, "");
+               },
+               TAG: function(match, curLoop){
+                       for ( var i = 0; curLoop[i] === false; i++ ){}
+                       return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+               },
+               CHILD: function(match){
+                       if ( match[1] == "nth" ) {
+                               var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+                                       match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
+                                       !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+                               match[2] = (test[1] + (test[2] || 1)) - 0;
+                               match[3] = test[3] - 0;
+                       }
+
+                       match[0] = done++;
+
+                       return match;
+               },
+               ATTR: function(match, curLoop, inplace, result, not, isXML){
+                       var name = match[1].replace(/\\/g, "");
+
+                       if ( !isXML && Expr.attrMap[name] ) {
+                               match[1] = Expr.attrMap[name];
+                       }
+
+                       if ( match[2] === "~=" ) {
+                               match[4] = " " + match[4] + " ";
+                       }
+
+                       return match;
+               },
+               PSEUDO: function(match, curLoop, inplace, result, not){
+                       if ( match[1] === "not" ) {
+                               if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+                                       match[3] = Sizzle(match[3], null, null, curLoop);
+                               } else {
+                                       var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+                                       if ( !inplace ) {
+                                               result.push.apply( result, ret );
+                                       }
+                                       return false;
+                               }
+                       } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+                               return true;
+                       }
+
+                       return match;
+               },
+               POS: function(match){
+                       match.unshift( true );
+                       return match;
+               }
+       },
+       filters: {
+               enabled: function(elem){
+                       return elem.disabled === false && elem.type !== "hidden";
+               },
+               disabled: function(elem){
+                       return elem.disabled === true;
+               },
+               checked: function(elem){
+                       return elem.checked === true;
+               },
+               selected: function(elem){
+                       elem.parentNode.selectedIndex;
+                       return elem.selected === true;
+               },
+               parent: function(elem){
+                       return !!elem.firstChild;
+               },
+               empty: function(elem){
+                       return !elem.firstChild;
+               },
+               has: function(elem, i, match){
+                       return !!Sizzle( match[3], elem ).length;
+               },
+               header: function(elem){
+                       return /h\d/i.test( elem.nodeName );
+               },
+               text: function(elem){
+                       return "text" === elem.type;
+               },
+               radio: function(elem){
+                       return "radio" === elem.type;
+               },
+               checkbox: function(elem){
+                       return "checkbox" === elem.type;
+               },
+               file: function(elem){
+                       return "file" === elem.type;
+               },
+               password: function(elem){
+                       return "password" === elem.type;
+               },
+               submit: function(elem){
+                       return "submit" === elem.type;
+               },
+               image: function(elem){
+                       return "image" === elem.type;
+               },
+               reset: function(elem){
+                       return "reset" === elem.type;
+               },
+               button: function(elem){
+                       return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
+               },
+               input: function(elem){
+                       return /input|select|textarea|button/i.test(elem.nodeName);
+               }
+       },
+       setFilters: {
+               first: function(elem, i){
+                       return i === 0;
+               },
+               last: function(elem, i, match, array){
+                       return i === array.length - 1;
+               },
+               even: function(elem, i){
+                       return i % 2 === 0;
+               },
+               odd: function(elem, i){
+                       return i % 2 === 1;
+               },
+               lt: function(elem, i, match){
+                       return i < match[3] - 0;
+               },
+               gt: function(elem, i, match){
+                       return i > match[3] - 0;
+               },
+               nth: function(elem, i, match){
+                       return match[3] - 0 == i;
+               },
+               eq: function(elem, i, match){
+                       return match[3] - 0 == i;
+               }
+       },
+       filter: {
+               PSEUDO: function(elem, match, i, array){
+                       var name = match[1], filter = Expr.filters[ name ];
+
+                       if ( filter ) {
+                               return filter( elem, i, match, array );
+                       } else if ( name === "contains" ) {
+                               return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
+                       } else if ( name === "not" ) {
+                               var not = match[3];
+
+                               for ( var i = 0, l = not.length; i < l; i++ ) {
+                                       if ( not[i] === elem ) {
+                                               return false;
+                                       }
+                               }
+
+                               return true;
+                       }
+               },
+               CHILD: function(elem, match){
+                       var type = match[1], node = elem;
+                       switch (type) {
+                               case 'only':
+                               case 'first':
+                                       while ( (node = node.previousSibling) )  {
+                                               if ( node.nodeType === 1 ) return false;
+                                       }
+                                       if ( type == 'first') return true;
+                                       node = elem;
+                               case 'last':
+                                       while ( (node = node.nextSibling) )  {
+                                               if ( node.nodeType === 1 ) return false;
+                                       }
+                                       return true;
+                               case 'nth':
+                                       var first = match[2], last = match[3];
+
+                                       if ( first == 1 && last == 0 ) {
+                                               return true;
+                                       }
+
+                                       var doneName = match[0],
+                                               parent = elem.parentNode;
+
+                                       if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+                                               var count = 0;
+                                               for ( node = parent.firstChild; node; node = node.nextSibling ) {
+                                                       if ( node.nodeType === 1 ) {
+                                                               node.nodeIndex = ++count;
+                                                       }
+                                               }
+                                               parent.sizcache = doneName;
+                                       }
+
+                                       var diff = elem.nodeIndex - last;
+                                       if ( first == 0 ) {
+                                               return diff == 0;
+                                       } else {
+                                               return ( diff % first == 0 && diff / first >= 0 );
+                                       }
+                       }
+               },
+               ID: function(elem, match){
+                       return elem.nodeType === 1 && elem.getAttribute("id") === match;
+               },
+               TAG: function(elem, match){
+                       return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
+               },
+               CLASS: function(elem, match){
+                       return (" " + (elem.className || elem.getAttribute("class")) + " ")
+                               .indexOf( match ) > -1;
+               },
+               ATTR: function(elem, match){
+                       var name = match[1],
+                               result = Expr.attrHandle[ name ] ?
+                                       Expr.attrHandle[ name ]( elem ) :
+                                       elem[ name ] != null ?
+                                               elem[ name ] :
+                                               elem.getAttribute( name ),
+                               value = result + "",
+                               type = match[2],
+                               check = match[4];
+
+                       return result == null ?
+                               type === "!=" :
+                               type === "=" ?
+                               value === check :
+                               type === "*=" ?
+                               value.indexOf(check) >= 0 :
+                               type === "~=" ?
+                               (" " + value + " ").indexOf(check) >= 0 :
+                               !check ?
+                               value && result !== false :
+                               type === "!=" ?
+                               value != check :
+                               type === "^=" ?
+                               value.indexOf(check) === 0 :
+                               type === "$=" ?
+                               value.substr(value.length - check.length) === check :
+                               type === "|=" ?
+                               value === check || value.substr(0, check.length + 1) === check + "-" :
+                               false;
+               },
+               POS: function(elem, match, i, array){
+                       var name = match[2], filter = Expr.setFilters[ name ];
+
+                       if ( filter ) {
+                               return filter( elem, i, match, array );
+                       }
+               }
+       }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+       Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+       Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source );
+}
+
+var makeArray = function(array, results) {
+       array = Array.prototype.slice.call( array, 0 );
+
+       if ( results ) {
+               results.push.apply( results, array );
+               return results;
+       }
+
+       return array;
+};
+
+try {
+       Array.prototype.slice.call( document.documentElement.childNodes, 0 );
+
+} catch(e){
+       makeArray = function(array, results) {
+               var ret = results || [];
+
+               if ( toString.call(array) === "[object Array]" ) {
+                       Array.prototype.push.apply( ret, array );
+               } else {
+                       if ( typeof array.length === "number" ) {
+                               for ( var i = 0, l = array.length; i < l; i++ ) {
+                                       ret.push( array[i] );
+                               }
+                       } else {
+                               for ( var i = 0; array[i]; i++ ) {
+                                       ret.push( array[i] );
+                               }
+                       }
+               }
+
+               return ret;
+       };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+       sortOrder = function( a, b ) {
+               if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+                       if ( a == b ) {
+                               hasDuplicate = true;
+                       }
+                       return 0;
+               }
+
+               var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+               if ( ret === 0 ) {
+                       hasDuplicate = true;
+               }
+               return ret;
+       };
+} else if ( "sourceIndex" in document.documentElement ) {
+       sortOrder = function( a, b ) {
+               if ( !a.sourceIndex || !b.sourceIndex ) {
+                       if ( a == b ) {
+                               hasDuplicate = true;
+                       }
+                       return 0;
+               }
+
+               var ret = a.sourceIndex - b.sourceIndex;
+               if ( ret === 0 ) {
+                       hasDuplicate = true;
+               }
+               return ret;
+       };
+} else if ( document.createRange ) {
+       sortOrder = function( a, b ) {
+               if ( !a.ownerDocument || !b.ownerDocument ) {
+                       if ( a == b ) {
+                               hasDuplicate = true;
+                       }
+                       return 0;
+               }
+
+               var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+               aRange.setStart(a, 0);
+               aRange.setEnd(a, 0);
+               bRange.setStart(b, 0);
+               bRange.setEnd(b, 0);
+               var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+               if ( ret === 0 ) {
+                       hasDuplicate = true;
+               }
+               return ret;
+       };
+}
+
+(function(){
+       var form = document.createElement("div"),
+               id = "script" + (new Date).getTime();
+       form.innerHTML = "<a name='" + id + "'/>";
+
+       var root = document.documentElement;
+       root.insertBefore( form, root.firstChild );
+
+       if ( !!document.getElementById( id ) ) {
+               Expr.find.ID = function(match, context, isXML){
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                               var m = context.getElementById(match[1]);
+                               return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+                       }
+               };
+
+               Expr.filter.ID = function(elem, match){
+                       var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+                       return elem.nodeType === 1 && node && node.nodeValue === match;
+               };
+       }
+
+       root.removeChild( form );
+       root = form = null; // release memory in IE
+})();
+
+(function(){
+
+       var div = document.createElement("div");
+       div.appendChild( document.createComment("") );
+
+       if ( div.getElementsByTagName("*").length > 0 ) {
+               Expr.find.TAG = function(match, context){
+                       var results = context.getElementsByTagName(match[1]);
+
+                       if ( match[1] === "*" ) {
+                               var tmp = [];
+
+                               for ( var i = 0; results[i]; i++ ) {
+                                       if ( results[i].nodeType === 1 ) {
+                                               tmp.push( results[i] );
+                                       }
+                               }
+
+                               results = tmp;
+                       }
+
+                       return results;
+               };
+       }
+
+       div.innerHTML = "<a href='#'></a>";
+       if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+                       div.firstChild.getAttribute("href") !== "#" ) {
+               Expr.attrHandle.href = function(elem){
+                       return elem.getAttribute("href", 2);
+               };
+       }
+
+       div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) (function(){
+       var oldSizzle = Sizzle, div = document.createElement("div");
+       div.innerHTML = "<p class='TEST'></p>";
+
+       if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+               return;
+       }
+
+       Sizzle = function(query, context, extra, seed){
+               context = context || document;
+
+               if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+                       try {
+                               return makeArray( context.querySelectorAll(query), extra );
+                       } catch(e){}
+               }
+
+               return oldSizzle(query, context, extra, seed);
+       };
+
+       for ( var prop in oldSizzle ) {
+               Sizzle[ prop ] = oldSizzle[ prop ];
+       }
+
+       div = null; // release memory in IE
+})();
+
+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
+       var div = document.createElement("div");
+       div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+       if ( div.getElementsByClassName("e").length === 0 )
+               return;
+
+       div.lastChild.className = "e";
+
+       if ( div.getElementsByClassName("e").length === 1 )
+               return;
+
+       Expr.order.splice(1, 0, "CLASS");
+       Expr.find.CLASS = function(match, context, isXML) {
+               if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+                       return context.getElementsByClassName(match[1]);
+               }
+       };
+
+       div = null; // release memory in IE
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+       var sibDir = dir == "previousSibling" && !isXML;
+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+               var elem = checkSet[i];
+               if ( elem ) {
+                       if ( sibDir && elem.nodeType === 1 ){
+                               elem.sizcache = doneName;
+                               elem.sizset = i;
+                       }
+                       elem = elem[dir];
+                       var match = false;
+
+                       while ( elem ) {
+                               if ( elem.sizcache === doneName ) {
+                                       match = checkSet[elem.sizset];
+                                       break;
+                               }
+
+                               if ( elem.nodeType === 1 && !isXML ){
+                                       elem.sizcache = doneName;
+                                       elem.sizset = i;
+                               }
+
+                               if ( elem.nodeName === cur ) {
+                                       match = elem;
+                                       break;
+                               }
+
+                               elem = elem[dir];
+                       }
+
+                       checkSet[i] = match;
+               }
+       }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+       var sibDir = dir == "previousSibling" && !isXML;
+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+               var elem = checkSet[i];
+               if ( elem ) {
+                       if ( sibDir && elem.nodeType === 1 ) {
+                               elem.sizcache = doneName;
+                               elem.sizset = i;
+                       }
+                       elem = elem[dir];
+                       var match = false;
+
+                       while ( elem ) {
+                               if ( elem.sizcache === doneName ) {
+                                       match = checkSet[elem.sizset];
+                                       break;
+                               }
+
+                               if ( elem.nodeType === 1 ) {
+                                       if ( !isXML ) {
+                                               elem.sizcache = doneName;
+                                               elem.sizset = i;
+                                       }
+                                       if ( typeof cur !== "string" ) {
+                                               if ( elem === cur ) {
+                                                       match = true;
+                                                       break;
+                                               }
+
+                                       } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+                                               match = elem;
+                                               break;
+                                       }
+                               }
+
+                               elem = elem[dir];
+                       }
+
+                       checkSet[i] = match;
+               }
+       }
+}
+
+var contains = document.compareDocumentPosition ?  function(a, b){
+       return a.compareDocumentPosition(b) & 16;
+} : function(a, b){
+       return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+       return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+               !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
+};
+
+var posProcess = function(selector, context){
+       var tmpSet = [], later = "", match,
+               root = context.nodeType ? [context] : context;
+
+       while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+               later += match[0];
+               selector = selector.replace( Expr.match.PSEUDO, "" );
+       }
+
+       selector = Expr.relative[selector] ? selector + "*" : selector;
+
+       for ( var i = 0, l = root.length; i < l; i++ ) {
+               Sizzle( selector, root[i], tmpSet );
+       }
+
+       return Sizzle.filter( later, tmpSet );
+};
+
+
+window.Sizzle = Sizzle;
+
+})();
+
+;(function(engine) {
+  var extendElements = Prototype.Selector.extendElements;
+
+  function select(selector, scope) {
+    return extendElements(engine(selector, scope || document));
+  }
+
+  function match(element, selector) {
+    return engine.matches(selector, [element]).length == 1;
+  }
+
+  Prototype.Selector.engine = engine;
+  Prototype.Selector.select = select;
+  Prototype.Selector.match = match;
+})(Sizzle);
+
+window.Sizzle = Prototype._original_property;
+delete Prototype._original_property;
+
+var Form = {
+  reset: function(form) {
+    form = $(form);
+    form.reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit, accumulator, initial;
+
+    if (options.hash) {
+      initial = {};
+      accumulator = function(result, key, value) {
+        if (key in result) {
+          if (!Object.isArray(result[key])) result[key] = [result[key]];
+          result[key].push(value);
+        } else result[key] = value;
+        return result;
+      };
+    } else {
+      initial = '';
+      accumulator = function(result, key, value) {
+        return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value);
+      }
+    }
+
+    return elements.inject(initial, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          result = accumulator(result, key, value);
+        }
+      }
+      return result;
+    });
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    var elements = $(form).getElementsByTagName('*'),
+        element,
+        arr = [ ],
+        serializers = Form.Element.Serializers;
+    for (var i = 0; element = elements[i]; i++) {
+      arr.push(element);
+    }
+    return arr.inject([], function(elements, child) {
+      if (serializers[child.tagName.toLowerCase()])
+        elements.push(Element.extend(child));
+      return elements;
+    })
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return /^(?:input|select|textarea)$/i.test(element.tagName);
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    var element = form.findFirstElement();
+    if (element) element.activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !(/^(?:button|reset|submit)$/i.test(element.type))))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = (function() {
+  function input(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return inputSelector(element, value);
+      default:
+        return valueSelector(element, value);
+    }
+  }
+
+  function inputSelector(element, value) {
+    if (Object.isUndefined(value))
+      return element.checked ? element.value : null;
+    else element.checked = !!value;
+  }
+
+  function valueSelector(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  }
+
+  function select(element, value) {
+    if (Object.isUndefined(value))
+      return (element.type === 'select-one' ? selectOne : selectMany)(element);
+
+    var opt, currentValue, single = !Object.isArray(value);
+    for (var i = 0, length = element.length; i < length; i++) {
+      opt = element.options[i];
+      currentValue = this.optionValue(opt);
+      if (single) {
+        if (currentValue == value) {
+          opt.selected = true;
+          return;
+        }
+      }
+      else opt.selected = value.include(currentValue);
+    }
+  }
+
+  function selectOne(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? optionValue(element.options[index]) : null;
+  }
+
+  function selectMany(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(optionValue(opt));
+    }
+    return values;
+  }
+
+  function optionValue(opt) {
+    return Element.hasAttribute(opt, 'value') ? opt.value : opt.text;
+  }
+
+  return {
+    input:         input,
+    inputSelector: inputSelector,
+    textarea:      valueSelector,
+    select:        select,
+    selectOne:     selectOne,
+    selectMany:    selectMany,
+    optionValue:   optionValue,
+    button:        valueSelector
+  };
+})();
+
+/*--------------------------------------------------------------------------*/
+
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+(function() {
+
+  var Event = {
+    KEY_BACKSPACE: 8,
+    KEY_TAB:       9,
+    KEY_RETURN:   13,
+    KEY_ESC:      27,
+    KEY_LEFT:     37,
+    KEY_UP:       38,
+    KEY_RIGHT:    39,
+    KEY_DOWN:     40,
+    KEY_DELETE:   46,
+    KEY_HOME:     36,
+    KEY_END:      35,
+    KEY_PAGEUP:   33,
+    KEY_PAGEDOWN: 34,
+    KEY_INSERT:   45,
+
+    cache: {}
+  };
+
+  var docEl = document.documentElement;
+  var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
+    && 'onmouseleave' in docEl;
+
+
+
+  var isIELegacyEvent = function(event) { return false; };
+
+  if (window.attachEvent) {
+    if (window.addEventListener) {
+      isIELegacyEvent = function(event) {
+        return !(event instanceof window.Event);
+      };
+    } else {
+      isIELegacyEvent = function(event) { return true; };
+    }
+  }
+
+  var _isButton;
+
+  function _isButtonForDOMEvents(event, code) {
+    return event.which ? (event.which === code + 1) : (event.button === code);
+  }
+
+  var legacyButtonMap = { 0: 1, 1: 4, 2: 2 };
+  function _isButtonForLegacyEvents(event, code) {
+    return event.button === legacyButtonMap[code];
+  }
+
+  function _isButtonForWebKit(event, code) {
+    switch (code) {
+      case 0: return event.which == 1 && !event.metaKey;
+      case 1: return event.which == 2 || (event.which == 1 && event.metaKey);
+      case 2: return event.which == 3;
+      default: return false;
+    }
+  }
+
+  if (window.attachEvent) {
+    if (!window.addEventListener) {
+      _isButton = _isButtonForLegacyEvents;
+    } else {
+      _isButton = function(event, code) {
+        return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) :
+         _isButtonForDOMEvents(event, code);
+      }
+    }
+  } else if (Prototype.Browser.WebKit) {
+    _isButton = _isButtonForWebKit;
+  } else {
+    _isButton = _isButtonForDOMEvents;
+  }
+
+  function isLeftClick(event)   { return _isButton(event, 0) }
+
+  function isMiddleClick(event) { return _isButton(event, 1) }
+
+  function isRightClick(event)  { return _isButton(event, 2) }
+
+  function element(event) {
+    event = Event.extend(event);
+
+    var node = event.target, type = event.type,
+     currentTarget = event.currentTarget;
+
+    if (currentTarget && currentTarget.tagName) {
+      if (type === 'load' || type === 'error' ||
+        (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
+          && currentTarget.type === 'radio'))
+            node = currentTarget;
+    }
+
+    if (node.nodeType == Node.TEXT_NODE)
+      node = node.parentNode;
+
+    return Element.extend(node);
+  }
+
+  function findElement(event, expression) {
+    var element = Event.element(event);
+
+    if (!expression) return element;
+    while (element) {
+      if (Object.isElement(element) && Prototype.Selector.match(element, expression)) {
+        return Element.extend(element);
+      }
+      element = element.parentNode;
+    }
+  }
+
+  function pointer(event) {
+    return { x: pointerX(event), y: pointerY(event) };
+  }
+
+  function pointerX(event) {
+    var docElement = document.documentElement,
+     body = document.body || { scrollLeft: 0 };
+
+    return event.pageX || (event.clientX +
+      (docElement.scrollLeft || body.scrollLeft) -
+      (docElement.clientLeft || 0));
+  }
+
+  function pointerY(event) {
+    var docElement = document.documentElement,
+     body = document.body || { scrollTop: 0 };
+
+    return  event.pageY || (event.clientY +
+       (docElement.scrollTop || body.scrollTop) -
+       (docElement.clientTop || 0));
+  }
+
+
+  function stop(event) {
+    Event.extend(event);
+    event.preventDefault();
+    event.stopPropagation();
+
+    event.stopped = true;
+  }
+
+
+  Event.Methods = {
+    isLeftClick:   isLeftClick,
+    isMiddleClick: isMiddleClick,
+    isRightClick:  isRightClick,
+
+    element:     element,
+    findElement: findElement,
+
+    pointer:  pointer,
+    pointerX: pointerX,
+    pointerY: pointerY,
+
+    stop: stop
+  };
+
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (window.attachEvent) {
+    function _relatedTarget(event) {
+      var element;
+      switch (event.type) {
+        case 'mouseover':
+        case 'mouseenter':
+          element = event.fromElement;
+          break;
+        case 'mouseout':
+        case 'mouseleave':
+          element = event.toElement;
+          break;
+        default:
+          return null;
+      }
+      return Element.extend(element);
+    }
+
+    var additionalMethods = {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return '[object Event]' }
+    };
+
+    Event.extend = function(event, element) {
+      if (!event) return false;
+
+      if (!isIELegacyEvent(event)) return event;
+
+      if (event._extendedByPrototype) return event;
+      event._extendedByPrototype = Prototype.emptyFunction;
+
+      var pointer = Event.pointer(event);
+
+      Object.extend(event, {
+        target: event.srcElement || element,
+        relatedTarget: _relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+
+      Object.extend(event, methods);
+      Object.extend(event, additionalMethods);
+
+      return event;
+    };
+  } else {
+    Event.extend = Prototype.K;
+  }
+
+  if (window.addEventListener) {
+    Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
+    Object.extend(Event.prototype, methods);
+  }
+
+  function _createResponder(element, eventName, handler) {
+    var registry = Element.retrieve(element, 'prototype_event_registry');
+
+    if (Object.isUndefined(registry)) {
+      CACHE.push(element);
+      registry = Element.retrieve(element, 'prototype_event_registry', $H());
+    }
+
+    var respondersForEvent = registry.get(eventName);
+    if (Object.isUndefined(respondersForEvent)) {
+      respondersForEvent = [];
+      registry.set(eventName, respondersForEvent);
+    }
+
+    if (respondersForEvent.pluck('handler').include(handler)) return false;
+
+    var responder;
+    if (eventName.include(":")) {
+      responder = function(event) {
+        if (Object.isUndefined(event.eventName))
+          return false;
+
+        if (event.eventName !== eventName)
+          return false;
+
+        Event.extend(event, element);
+        handler.call(element, event);
+      };
+    } else {
+      if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
+       (eventName === "mouseenter" || eventName === "mouseleave")) {
+        if (eventName === "mouseenter" || eventName === "mouseleave") {
+          responder = function(event) {
+            Event.extend(event, element);
+
+            var parent = event.relatedTarget;
+            while (parent && parent !== element) {
+              try { parent = parent.parentNode; }
+              catch(e) { parent = element; }
+            }
+
+            if (parent === element) return;
+
+            handler.call(element, event);
+          };
+        }
+      } else {
+        responder = function(event) {
+          Event.extend(event, element);
+          handler.call(element, event);
+        };
+      }
+    }
+
+    responder.handler = handler;
+    respondersForEvent.push(responder);
+    return responder;
+  }
+
+  function _destroyCache() {
+    for (var i = 0, length = CACHE.length; i < length; i++) {
+      Event.stopObserving(CACHE[i]);
+      CACHE[i] = null;
+    }
+  }
+
+  var CACHE = [];
+
+  if (Prototype.Browser.IE)
+    window.attachEvent('onunload', _destroyCache);
+
+  if (Prototype.Browser.WebKit)
+    window.addEventListener('unload', Prototype.emptyFunction, false);
+
+
+  var _getDOMEventName = Prototype.K,
+      translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
+
+  if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
+    _getDOMEventName = function(eventName) {
+      return (translations[eventName] || eventName);
+    };
+  }
+
+  function observe(element, eventName, handler) {
+    element = $(element);
+
+    var responder = _createResponder(element, eventName, handler);
+
+    if (!responder) return element;
+
+    if (eventName.include(':')) {
+      if (element.addEventListener)
+        element.addEventListener("dataavailable", responder, false);
+      else {
+        element.attachEvent("ondataavailable", responder);
+        element.attachEvent("onlosecapture", responder);
+      }
+    } else {
+      var actualEventName = _getDOMEventName(eventName);
+
+      if (element.addEventListener)
+        element.addEventListener(actualEventName, responder, false);
+      else
+        element.attachEvent("on" + actualEventName, responder);
+    }
+
+    return element;
+  }
+
+  function stopObserving(element, eventName, handler) {
+    element = $(element);
+
+    var registry = Element.retrieve(element, 'prototype_event_registry');
+    if (!registry) return element;
+
+    if (!eventName) {
+      registry.each( function(pair) {
+        var eventName = pair.key;
+        stopObserving(element, eventName);
+      });
+      return element;
+    }
+
+    var responders = registry.get(eventName);
+    if (!responders) return element;
+
+    if (!handler) {
+      responders.each(function(r) {
+        stopObserving(element, eventName, r.handler);
+      });
+      return element;
+    }
+
+    var i = responders.length, responder;
+    while (i--) {
+      if (responders[i].handler === handler) {
+        responder = responders[i];
+        break;
+      }
+    }
+    if (!responder) return element;
+
+    if (eventName.include(':')) {
+      if (element.removeEventListener)
+        element.removeEventListener("dataavailable", responder, false);
+      else {
+        element.detachEvent("ondataavailable", responder);
+        element.detachEvent("onlosecapture", responder);
+      }
+    } else {
+      var actualEventName = _getDOMEventName(eventName);
+      if (element.removeEventListener)
+        element.removeEventListener(actualEventName, responder, false);
+      else
+        element.detachEvent('on' + actualEventName, responder);
+    }
+
+    registry.set(eventName, responders.without(responder));
+
+    return element;
+  }
+
+  function fire(element, eventName, memo, bubble) {
+    element = $(element);
+
+    if (Object.isUndefined(bubble))
+      bubble = true;
+
+    if (element == document && document.createEvent && !element.dispatchEvent)
+      element = document.documentElement;
+
+    var event;
+    if (document.createEvent) {
+      event = document.createEvent('HTMLEvents');
+      event.initEvent('dataavailable', bubble, true);
+    } else {
+      event = document.createEventObject();
+      event.eventType = bubble ? 'ondataavailable' : 'onlosecapture';
+    }
+
+    event.eventName = eventName;
+    event.memo = memo || { };
+
+    if (document.createEvent)
+      element.dispatchEvent(event);
+    else
+      element.fireEvent(event.eventType, event);
+
+    return Event.extend(event);
+  }
+
+  Event.Handler = Class.create({
+    initialize: function(element, eventName, selector, callback) {
+      this.element   = $(element);
+      this.eventName = eventName;
+      this.selector  = selector;
+      this.callback  = callback;
+      this.handler   = this.handleEvent.bind(this);
+    },
+
+    start: function() {
+      Event.observe(this.element, this.eventName, this.handler);
+      return this;
+    },
+
+    stop: function() {
+      Event.stopObserving(this.element, this.eventName, this.handler);
+      return this;
+    },
+
+    handleEvent: function(event) {
+      var element = Event.findElement(event, this.selector);
+      if (element) this.callback.call(this.element, event, element);
+    }
+  });
+
+  function on(element, eventName, selector, callback) {
+    element = $(element);
+    if (Object.isFunction(selector) && Object.isUndefined(callback)) {
+      callback = selector, selector = null;
+    }
+
+    return new Event.Handler(element, eventName, selector, callback).start();
+  }
+
+  Object.extend(Event, Event.Methods);
+
+  Object.extend(Event, {
+    fire:          fire,
+    observe:       observe,
+    stopObserving: stopObserving,
+    on:            on
+  });
+
+  Element.addMethods({
+    fire:          fire,
+
+    observe:       observe,
+
+    stopObserving: stopObserving,
+
+    on:            on
+  });
+
+  Object.extend(document, {
+    fire:          fire.methodize(),
+
+    observe:       observe.methodize(),
+
+    stopObserving: stopObserving.methodize(),
+
+    on:            on.methodize(),
+
+    loaded:        false
+  });
+
+  if (window.Event) Object.extend(window.Event, Event);
+  else window.Event = Event;
+})();
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearTimeout(timer);
+    document.loaded = true;
+    document.fire('dom:loaded');
+  }
+
+  function checkReadyState() {
+    if (document.readyState === 'complete') {
+      document.stopObserving('readystatechange', checkReadyState);
+      fireContentLoadedEvent();
+    }
+  }
+
+  function pollDoScroll() {
+    try { document.documentElement.doScroll('left'); }
+    catch(e) {
+      timer = pollDoScroll.defer();
+      return;
+    }
+    fireContentLoadedEvent();
+  }
+
+  if (document.addEventListener) {
+    document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
+  } else {
+    document.observe('readystatechange', checkReadyState);
+    if (window == top)
+      timer = pollDoScroll.defer();
+  }
+
+  Event.observe(window, 'load', fireContentLoadedEvent);
+})();
+
+Element.addMethods();
+
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+var Position = {
+  includeScrollOffsets: false,
+
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+(function() {
+  window.Selector = Class.create({
+    initialize: function(expression) {
+      this.expression = expression.strip();
+    },
+
+    findElements: function(rootElement) {
+      return Prototype.Selector.select(this.expression, rootElement);
+    },
+
+    match: function(element) {
+      return Prototype.Selector.match(element, this.expression);
+    },
+
+    toString: function() {
+      return this.expression;
+    },
+
+    inspect: function() {
+      return "#<Selector: " + this.expression + ">";
+    }
+  });
+
+  Object.extend(Selector, {
+    matchElements: function(elements, expression) {
+      var match = Prototype.Selector.match,
+          results = [];
+
+      for (var i = 0, length = elements.length; i < length; i++) {
+        var element = elements[i];
+        if (match(element, expression)) {
+          results.push(Element.extend(element));
+        }
+      }
+      return results;
+    },
+
+    findElement: function(elements, expression, index) {
+      index = index || 0;
+      var matchIndex = 0, element;
+      for (var i = 0, length = elements.length; i < length; i++) {
+        element = elements[i];
+        if (Prototype.Selector.match(element, expression) && index === matchIndex++) {
+          return Element.extend(element);
+        }
+      }
+    },
+
+    findChildElements: function(element, expressions) {
+      var selector = expressions.toArray().join(', ');
+      return Prototype.Selector.select(selector, element || document);
+    }
+  });
+})();
diff --git a/Sites/MapServer/main.css b/Sites/MapServer/main.css
new file mode 100644 (file)
index 0000000..b922ad7
--- /dev/null
@@ -0,0 +1,146 @@
+/* Main HTML stuff */
+html { 
+    height: 100%;
+}
+
+body { 
+    height: 100%; 
+    margin: 0; 
+    padding: 0;
+    
+    /* Font stuff */
+    font-family: Verdana, Sans-Serif;
+    font-size: 8pt;
+    color: #444444;
+}
+
+a {
+    color: #003399;
+    text-decoration: none;
+}
+a:hover {
+    text-decoration: underline
+}
+a:visited {
+    color: #990033;
+}
+
+p {
+    margin: 0;
+    padding: 5px 0;
+}
+
+select {
+    margin: -2px;
+    padding: 3px;
+    width: 100%;
+}
+
+input:not([type='checkbox']) {
+    width: 95%;
+}
+
+#mapCanvas { 
+    height: 100%; 
+}
+
+
+/* The control panel */
+#controlPanel {
+    background-color: white;
+    border-color: #717B87;
+    border-style: solid;
+    border-width: 1px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
+    margin: 5px;
+    max-height: 90%;
+    overflow: auto;
+    padding: 1px 6px;
+    position: absolute;
+    right: 0;
+    top: 25px;
+    width: 216px;
+}
+#controlPanel p {
+       color: #333333;
+    direction: ltr;
+    font-family: Arial,sans-serif;
+    font-size: 13px;
+}
+.ColorMarker {
+    border-color: white;
+    border-style: solid;
+    border-width: 5px 0 6px;
+    opacity: 0.6;
+    width: 5px;
+}
+.TextMarker{
+    border-color: white;
+    border-style: solid;
+    border-width: 3px 0;    
+    color: #333333;
+    direction: ltr;
+    font-family: Arial,sans-serif;
+    font-size: 13px;
+}
+
+/* The context menu */
+.contextMenu {
+    background-color: white;
+    border-color: #717B87;
+    border-style: solid;
+    border-width: 1px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
+    margin: 5px;
+    padding: 1px 6px;
+    position: absolute;
+    width: 150px;
+}
+
+#infoArea tr {
+    cursor: pointer;
+}
+
+div.block {
+    cursor: wait;
+    z-index: 999;
+    height: 100%;
+    width: 100%;
+    position: absolute;
+}
+
+tr.hr {
+    height: 1px;
+}
+
+tr.hr td {
+    background-color: #CCCCCC;
+    padding: 0;
+}
+
+/* The function canvas */
+#functionCanvas {
+    background-color: white;
+    border-color: #717b87;
+    border-style: solid;
+    border-width: 1px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
+    height: 200px;
+    left: 10px;
+    opacity: 0.9;
+    padding: 8px;
+    position: absolute;
+    top: 10px;
+    width: 300px;
+}
+
+/* Map controls */
+[title="OpenStreetMap"] {
+    width: 113px;
+}
+[title="Stadtplan anzeigen"] {
+    width: 90px;
+}
+[title="Show street map"] {
+    width: 90px;
+}
diff --git a/Sites/MapServer/mymap.ico b/Sites/MapServer/mymap.ico
new file mode 100644 (file)
index 0000000..3864c5a
Binary files /dev/null and b/Sites/MapServer/mymap.ico differ