/*
 * Copyright (c) 2020-2021 Samsung Electronics Co., Ltd. All rights reserved.

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/*
 * Copyright notice for the EFL:

 * Copyright (C) EFL developers (see AUTHORS)

 * All rights reserved.

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:

 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.

 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <math.h>
#include <string>
#include "tvgSvgLoaderCommon.h"
#include "tvgSvgSceneBuilder.h"
#include "tvgSvgPath.h"
#include "tvgSvgUtil.h"
#include <float.h>

static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh);
static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath);

/************************************************************************/
/* Internal Class Implementation                                        */
/************************************************************************/

static inline bool _isGroupType(SvgNodeType type)
{
    if (type == SvgNodeType::Doc || type == SvgNodeType::G || type == SvgNodeType::Use || type == SvgNodeType::ClipPath) return true;
    return false;
}


static void _transformMultiply(const Matrix* mBBox, Matrix* gradTransf)
{
    gradTransf->e13 = gradTransf->e13 * mBBox->e11 + mBBox->e13;
    gradTransf->e12 *= mBBox->e11;
    gradTransf->e11 *= mBBox->e11;

    gradTransf->e23 = gradTransf->e23 * mBBox->e22 + mBBox->e23;
    gradTransf->e22 *= mBBox->e22;
    gradTransf->e21 *= mBBox->e22;
}


static unique_ptr<LinearGradient> _applyLinearGradientProperty(SvgStyleGradient* g, const Shape* vg, float rx, float ry, float rw, float rh, int opacity)
{
    Fill::ColorStop* stops;
    int stopCount = 0;
    auto fillGrad = LinearGradient::gen();

    bool isTransform = (g->transform ? true : false);
    Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
    if (isTransform) finalTransform = *g->transform;

    if (g->userSpace) {
        g->linear->x1 = g->linear->x1 * rw;
        g->linear->y1 = g->linear->y1 * rh;
        g->linear->x2 = g->linear->x2 * rw;
        g->linear->y2 = g->linear->y2 * rh;
    } else {
        Matrix m = {rw, 0, rx, 0, rh, ry, 0, 0, 1};
        if (isTransform) _transformMultiply(&m, &finalTransform);
        else {
            finalTransform = m;
            isTransform = true;
        }
    }

    if (isTransform) fillGrad->transform(finalTransform);

    fillGrad->linear(g->linear->x1, g->linear->y1, g->linear->x2, g->linear->y2);
    fillGrad->spread(g->spread);

    //Update the stops
    stopCount = g->stops.count;
    if (stopCount > 0) {
        stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
        if (!stops) return fillGrad;
        auto prevOffset = 0.0f;
        for (uint32_t i = 0; i < g->stops.count; ++i) {
            auto colorStop = &g->stops.data[i];
            //Use premultiplied color
            stops[i].r = colorStop->r;
            stops[i].g = colorStop->g;
            stops[i].b = colorStop->b;
            stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
            stops[i].offset = colorStop->offset;
            //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
            if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
            else if (colorStop->offset > 1) stops[i].offset = 1;
            prevOffset = stops[i].offset;
        }
        fillGrad->colorStops(stops, stopCount);
        free(stops);
    }
    return fillGrad;
}


static unique_ptr<RadialGradient> _applyRadialGradientProperty(SvgStyleGradient* g, const Shape* vg, float rx, float ry, float rw, float rh, int opacity)
{
    Fill::ColorStop *stops;
    int stopCount = 0;
    auto fillGrad = RadialGradient::gen();

    bool isTransform = (g->transform ? true : false);
    Matrix finalTransform = {1, 0, 0, 0, 1, 0, 0, 0, 1};
    if (isTransform) finalTransform = *g->transform;

    if (g->userSpace) {
        //The radius scalling is done according to the Units section:
        //https://www.w3.org/TR/2015/WD-SVG2-20150915/coords.html
        g->radial->cx = g->radial->cx * rw;
        g->radial->cy = g->radial->cy * rh;
        g->radial->r = g->radial->r * sqrtf(powf(rw, 2.0f) + powf(rh, 2.0f)) / sqrtf(2.0f);
        g->radial->fx = g->radial->fx * rw;
        g->radial->fy = g->radial->fy * rh;
    } else {
        Matrix m = {rw, 0, rx, 0, rh, ry, 0, 0, 1};
        if (isTransform) _transformMultiply(&m, &finalTransform);
        else {
            finalTransform = m;
            isTransform = true;
        }
    }

    if (isTransform) fillGrad->transform(finalTransform);

    //TODO: Tvg is not support to focal
    //if (g->radial->fx != 0 && g->radial->fy != 0) {
    //    fillGrad->radial(g->radial->fx, g->radial->fy, g->radial->r);
    //}
    fillGrad->radial(g->radial->cx, g->radial->cy, g->radial->r);
    fillGrad->spread(g->spread);

    //Update the stops
    stopCount = g->stops.count;
    if (stopCount > 0) {
        stops = (Fill::ColorStop*)calloc(stopCount, sizeof(Fill::ColorStop));
        if (!stops) return fillGrad;
        auto prevOffset = 0.0f;
        for (uint32_t i = 0; i < g->stops.count; ++i) {
            auto colorStop = &g->stops.data[i];
            //Use premultiplied color
            stops[i].r = colorStop->r;
            stops[i].g = colorStop->g;
            stops[i].b = colorStop->b;
            stops[i].a = static_cast<uint8_t>((colorStop->a * opacity) / 255);
            stops[i].offset = colorStop->offset;
            //check the offset corner cases - refer to: https://svgwg.org/svg2-draft/pservers.html#StopNotes
            if (colorStop->offset < prevOffset) stops[i].offset = prevOffset;
            else if (colorStop->offset > 1) stops[i].offset = 1;
            prevOffset = stops[i].offset;
        }
        fillGrad->colorStops(stops, stopCount);
        free(stops);
    }
    return fillGrad;
}


static bool _appendChildShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh)
{
    auto valid = false;

    if (_appendShape(node, shape, vx, vy, vw, vh)) valid = true;

    if (node->child.count > 0) {
        auto child = node->child.data;
        for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
            if (_appendChildShape(*child, shape, vx, vy, vw, vh)) valid = true;
        }
    }

    return valid;
}


static void _applyComposition(Paint* paint, const SvgNode* node, float vx, float vy, float vw, float vh)
{
    /* ClipPath */
    /* Do not drop in Circular Dependency for ClipPath.
       Composition can be applied recursively if its children nodes have composition target to this one. */
    if (node->style->clipPath.applying) {
        TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
    } else {
        auto compNode = node->style->clipPath.node;
        if (compNode && compNode->child.count > 0) {
            node->style->clipPath.applying = true;

            auto comp = Shape::gen();
            comp->fill(255, 255, 255, 255);
            if (node->transform) comp->transform(*node->transform);

            auto child = compNode->child.data;
            auto valid = false; //Composite only when valid shapes are existed

            for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
                if (_appendChildShape(*child, comp.get(), vx, vy, vw, vh)) valid = true;
            }

            if (valid) paint->composite(move(comp), CompositeMethod::ClipPath);

            node->style->clipPath.applying = false;
        }
    }

    /* Mask */
    /* Do not drop in Circular Dependency for Mask.
       Composition can be applied recursively if its children nodes have composition target to this one. */
    if (node->style->mask.applying) {
        TVGLOG("SVG", "Multiple Composition Tried! Check out Circular dependency?");
    } else  {
        auto compNode = node->style->mask.node;
        if (compNode && compNode->child.count > 0) {
            node->style->mask.applying = true;

            auto comp = Shape::gen();
            comp->fill(255, 255, 255, 255);
            if (node->transform) comp->transform(*node->transform);

            auto child = compNode->child.data;
            auto valid = false; //Composite only when valid shapes are existed

            for (uint32_t i = 0; i < compNode->child.count; ++i, ++child) {
                if (_appendChildShape(*child, comp.get(), vx, vy, vw, vh)) valid = true;
            }

            if (valid) paint->composite(move(comp), CompositeMethod::AlphaMask);

            node->style->mask.applying = false;
        }
    }
}


static void _applyProperty(SvgNode* node, Shape* vg, float vx, float vy, float vw, float vh)
{
    SvgStyleProperty* style = node->style;

    if (node->transform) vg->transform(*node->transform);
    if (node->type == SvgNodeType::Doc || !node->display) return;

    //If fill property is nullptr then do nothing
    if (style->fill.paint.none) {
        //Do nothing
    } else if (style->fill.paint.gradient) {
        if (!style->fill.paint.gradient->userSpace) {
            vg->bounds(&vx, &vy, &vw, &vh, false);
            //According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
            //a stroke width should be ignored for bounding box calculations
            if (auto strokeW = vg->strokeWidth()) {
                vx += 0.5f * strokeW;
                vy += 0.5f * strokeW;
                vw -= strokeW;
                vh -= strokeW;
            }
        }

        if (style->fill.paint.gradient->type == SvgGradientType::Linear) {
             auto linear = _applyLinearGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh, style->fill.opacity);
             vg->fill(move(linear));
        } else if (style->fill.paint.gradient->type == SvgGradientType::Radial) {
             auto radial = _applyRadialGradientProperty(style->fill.paint.gradient, vg, vx, vy, vw, vh, style->fill.opacity);
             vg->fill(move(radial));
        }
    } else if (style->fill.paint.url) {
        //TODO: Apply the color pointed by url
    } else if (style->fill.paint.curColor) {
        //Apply the current style color
        vg->fill(style->color.r, style->color.g, style->color.b, style->fill.opacity);
    } else {
        //Apply the fill color
        vg->fill(style->fill.paint.color.r, style->fill.paint.color.g, style->fill.paint.color.b, style->fill.opacity);
    }

    //Apply the fill rule
    vg->fill((tvg::FillRule)style->fill.fillRule);

    //Apply node opacity
    if (style->opacity < 255) vg->opacity(style->opacity);

    if (node->type == SvgNodeType::G || node->type == SvgNodeType::Use) return;

    //Apply the stroke style property
    vg->stroke(style->stroke.width);
    vg->stroke(style->stroke.cap);
    vg->stroke(style->stroke.join);
    if (style->stroke.dash.array.count > 0) {
        vg->stroke(style->stroke.dash.array.data, style->stroke.dash.array.count);
    }

    //If stroke property is nullptr then do nothing
    if (style->stroke.paint.none) {
        //Do nothing
    } else if (style->stroke.paint.gradient) {
        if (!style->stroke.paint.gradient->userSpace) {
            //According to: https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBoxUnits (the last paragraph)
            //a stroke width should be ignored for bounding box calculations
            vg->bounds(&vx, &vy, &vw, &vh, false);
            if (auto strokeW = vg->strokeWidth()) {
                vx += 0.5f * strokeW;
                vy += 0.5f * strokeW;
                vw -= strokeW;
                vh -= strokeW;
            }
        }

        if (style->stroke.paint.gradient->type == SvgGradientType::Linear) {
             auto linear = _applyLinearGradientProperty(style->stroke.paint.gradient, vg, vx, vy, vw, vh, style->stroke.opacity);
             vg->stroke(move(linear));
        } else if (style->stroke.paint.gradient->type == SvgGradientType::Radial) {
             auto radial = _applyRadialGradientProperty(style->stroke.paint.gradient, vg, vx, vy, vw, vh, style->stroke.opacity);
             vg->stroke(move(radial));
        }
    } else if (style->stroke.paint.url) {
        //TODO: Apply the color pointed by url
    } else if (style->stroke.paint.curColor) {
        //Apply the current style color
        vg->stroke(style->color.r, style->color.g, style->color.b, style->stroke.opacity);
    } else {
        //Apply the stroke color
        vg->stroke(style->stroke.paint.color.r, style->stroke.paint.color.g, style->stroke.paint.color.b, style->stroke.opacity);
    }

    _applyComposition(vg, node, vx, vy, vw, vh);
}


static unique_ptr<Shape> _shapeBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh)
{
    auto shape = Shape::gen();
    if (_appendShape(node, shape.get(), vx, vy, vw, vh)) return shape;
    else return nullptr;
}


static bool _appendShape(SvgNode* node, Shape* shape, float vx, float vy, float vw, float vh)
{
    Array<PathCommand> cmds;
    Array<Point> pts;

    switch (node->type) {
        case SvgNodeType::Path: {
            if (node->node.path.path) {
                if (svgPathToTvgPath(node->node.path.path->c_str(), cmds, pts)) {
                    shape->appendPath(cmds.data, cmds.count, pts.data, pts.count);
                }
            }
            break;
        }
        case SvgNodeType::Ellipse: {
            shape->appendCircle(node->node.ellipse.cx, node->node.ellipse.cy, node->node.ellipse.rx, node->node.ellipse.ry);
            break;
        }
        case SvgNodeType::Polygon: {
            if (node->node.polygon.pointsCount < 2) break;
            shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
            for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
                shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
            }
            shape->close();
            break;
        }
        case SvgNodeType::Polyline: {
            if (node->node.polygon.pointsCount < 2) break;
            shape->moveTo(node->node.polygon.points[0], node->node.polygon.points[1]);
            for (int i = 2; i < node->node.polygon.pointsCount - 1; i += 2) {
                shape->lineTo(node->node.polygon.points[i], node->node.polygon.points[i + 1]);
            }
            break;
        }
        case SvgNodeType::Circle: {
            shape->appendCircle(node->node.circle.cx, node->node.circle.cy, node->node.circle.r, node->node.circle.r);
            break;
        }
        case SvgNodeType::Rect: {
            shape->appendRect(node->node.rect.x, node->node.rect.y, node->node.rect.w, node->node.rect.h, node->node.rect.rx, node->node.rect.ry);
            break;
        }
        case SvgNodeType::Line: {
            shape->moveTo(node->node.line.x1, node->node.line.y1);
            shape->lineTo(node->node.line.x2, node->node.line.y2);
            break;
        }
        default: {
            return false;
        }
    }

    _applyProperty(node, shape, vx, vy, vw, vh);
    return true;
}


enum class imageMimeTypeEncoding
{
    base64 = 0x1,
    utf8 = 0x2
};
constexpr imageMimeTypeEncoding operator|(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
    return static_cast<imageMimeTypeEncoding>(static_cast<int>(a) | static_cast<int>(b));
}
constexpr bool operator&(imageMimeTypeEncoding a, imageMimeTypeEncoding b) {
    return (static_cast<int>(a) & static_cast<int>(b));
}


static constexpr struct
{
    const char* name;
    int sz;
    imageMimeTypeEncoding encoding;
} imageMimeTypes[] = {
    {"jpeg", sizeof("jpeg"), imageMimeTypeEncoding::base64},
    {"png", sizeof("png"), imageMimeTypeEncoding::base64},
    {"svg+xml", sizeof("svg+xml"), imageMimeTypeEncoding::base64 | imageMimeTypeEncoding::utf8},
};


static bool _isValidImageMimeTypeAndEncoding(const char** href, const char** mimetype, imageMimeTypeEncoding* encoding) {
    if (strncmp(*href, "image/", sizeof("image/") - 1)) return false; //not allowed mime type
    *href += sizeof("image/") - 1;

    //RFC2397 data:[<mediatype>][;base64],<data>
    //mediatype  := [ type "/" subtype ] *( ";" parameter )
    //parameter  := attribute "=" value
    for (unsigned int i = 0; i < sizeof(imageMimeTypes) / sizeof(imageMimeTypes[0]); i++) {
        if (!strncmp(*href, imageMimeTypes[i].name, imageMimeTypes[i].sz - 1)) {
            *href += imageMimeTypes[i].sz  - 1;
            *mimetype = imageMimeTypes[i].name;

            while (**href && **href != ',') {
                while (**href && **href != ';') ++(*href);
                if (!**href) return false;
                ++(*href);

                if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::base64) {
                    if (!strncmp(*href, "base64,", sizeof("base64,") - 1)) {
                        *href += sizeof("base64,") - 1;
                        *encoding = imageMimeTypeEncoding::base64;
                        return true; //valid base64
                    }
                }
                if (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8) {
                    if (!strncmp(*href, "utf8,", sizeof("utf8,") - 1)) {
                        *href += sizeof("utf8,") - 1;
                        *encoding = imageMimeTypeEncoding::utf8;
                        return true; //valid utf8
                    }
                }
            }
            //no encoding defined
            if (**href == ',' && (imageMimeTypes[i].encoding & imageMimeTypeEncoding::utf8)) {
                ++(*href);
                *encoding = imageMimeTypeEncoding::utf8;
                return true; //allow no encoding defined if utf8 expected
            }
            return false;
        }
    }
    return false;
}


static unique_ptr<Picture> _imageBuildHelper(SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath)
{
    if (!node->node.image.href) return nullptr;
    auto picture = Picture::gen();

    const char* href = (*node->node.image.href).c_str();
    if (!strncmp(href, "data:", sizeof("data:") - 1)) {
        href += sizeof("data:") - 1;
        const char* mimetype;
        imageMimeTypeEncoding encoding;
        if (!_isValidImageMimeTypeAndEncoding(&href, &mimetype, &encoding)) return nullptr; //not allowed mime type or encoding
        if (encoding == imageMimeTypeEncoding::base64) {
            string decoded = svgUtilBase64Decode(href);
            if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
        } else {
            string decoded = svgUtilURLDecode(href);
            if (picture->load(decoded.c_str(), decoded.size(), mimetype, true) != Result::Success) return nullptr;
        }
    } else {
        if (!strncmp(href, "file://", sizeof("file://") - 1)) href += sizeof("file://") - 1;
        //TODO: protect against recursive svg image loading
        //Temporarily disable embedded svg:
        const char *dot = strrchr(href, '.');
        if (dot && !strcmp(dot, ".svg")) {
            TVGLOG("SVG", "Embedded svg file is disabled.");
            return nullptr;
        }
        string imagePath = href;
        if (strncmp(href, "/", 1)) {
            auto last = svgPath.find_last_of("/");
            imagePath = svgPath.substr(0, (last == string::npos ? 0 : last + 1 )) + imagePath;
        }
        if (picture->load(imagePath) != Result::Success) return nullptr;
    }

    float w, h;
    if (picture->size(&w, &h) == Result::Success && w  > 0 && h > 0) {
        auto sx = node->node.image.w / w;
        auto sy = node->node.image.h / h;
        Matrix m = {sx, 0, node->node.image.x, 0, sy, node->node.image.y, 0, 0, 1};
        picture->transform(m);
    }

    _applyComposition(picture.get(), node, vx, vy, vw, vh);
    return picture;
}


static unique_ptr<Scene> _useBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath)
{
    auto scene = _sceneBuildHelper(node, vx, vy, vw, vh, svgPath);
    if (node->node.use.x != 0.0f || node->node.use.y != 0.0f) {
        scene->translate(node->node.use.x, node->node.use.y);
    }
    if (node->node.use.w > 0.0f && node->node.use.h > 0.0f) {
        //TODO: handle width/height properties
    }
    return scene;
}


static unique_ptr<Scene> _sceneBuildHelper(const SvgNode* node, float vx, float vy, float vw, float vh, const string& svgPath)
{
    if (_isGroupType(node->type)) {
        auto scene = Scene::gen();
        if (node->transform) scene->transform(*node->transform);

        if (node->display && node->style->opacity != 0) {
            auto child = node->child.data;
            for (uint32_t i = 0; i < node->child.count; ++i, ++child) {
                if (_isGroupType((*child)->type)) {
                    if ((*child)->type == SvgNodeType::Use)
                        scene->push(_useBuildHelper(*child, vx, vy, vw, vh, svgPath));
                    else
                        scene->push(_sceneBuildHelper(*child, vx, vy, vw, vh, svgPath));
                } else if ((*child)->type == SvgNodeType::Image) {
                    auto image = _imageBuildHelper(*child, vx, vy, vw, vh, svgPath);
                    if (image) scene->push(move(image));
                } else {
                    auto shape = _shapeBuildHelper(*child, vx, vy, vw, vh);
                    if (shape) scene->push(move(shape));
                }
            }
            _applyComposition(scene.get(), node, vx, vy, vw, vh);
            scene->opacity(node->style->opacity);
        }
        return scene;
    }
    return nullptr;
}


/************************************************************************/
/* External Class Implementation                                        */
/************************************************************************/

unique_ptr<Scene> svgSceneBuild(SvgNode* node, float vx, float vy, float vw, float vh, float w, float h, bool preserveAspect, const string& svgPath)
{
    if (!node || (node->type != SvgNodeType::Doc)) return nullptr;

    auto docNode = _sceneBuildHelper(node, vx, vy, vw, vh, svgPath);

    if (fabsf(w - vw) > FLT_EPSILON || fabsf(h - vh) > FLT_EPSILON) {
        auto sx = w / vw;
        auto sy = h / vh;

        if (preserveAspect) {
            //Scale
            auto scale = sx < sy ? sx : sy;
            docNode->scale(scale);
            //Align
            auto tvx = vx * scale;
            auto tvy = vy * scale;
            auto tvw = vw * scale;
            auto tvh = vh * scale;
            if (vw > vh) tvy -= (h - tvh) * 0.5f;
            else  tvx -= (w - tvw) * 0.5f;
            docNode->translate(-tvx, -tvy);
        } else {
            //Align
            auto tvx = vx * sx;
            auto tvy = vy * sy;
            auto tvw = vw * sx;
            auto tvh = vh * sy;
            if (tvw > tvh) tvy -= (h - tvh) * 0.5f;
            else tvx -= (w - tvw) * 0.5f;
            Matrix m = {sx, 0, -tvx, 0, sy, -tvy, 0, 0, 1};
            docNode->transform(m);
        }
    } else if (fabs(vx) > FLT_EPSILON || fabs(vy) > FLT_EPSILON) {
        docNode->translate(-vx, -vy);
    }

    auto viewBoxClip = Shape::gen();
    viewBoxClip->appendRect(0, 0, w, h, 0, 0);
    viewBoxClip->fill(0, 0, 0, 255);

    auto compositeLayer = Scene::gen();
    compositeLayer->composite(move(viewBoxClip), CompositeMethod::ClipPath);
    compositeLayer->push(move(docNode));

    auto root = Scene::gen();
    root->push(move(compositeLayer));

    return root;
}
