/*
 * Copyright (C) 2004, 2005, 2008 Nikolas Zimmermann <zimmermann@kde.org>
 * Copyright (C) 2004, 2005 Rob Buis <buis@kde.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#pragma once

#include "AffineTransform.h"
#include "FloatConversion.h"
#include "FloatPoint.h"
#include "FloatSize.h"
#include "SVGMatrix.h"
#include <wtf/HashMap.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/text/StringBuilder.h>

namespace WebCore {

class FloatSize;

class SVGTransformValue {
public:
    enum SVGTransformType {
        SVG_TRANSFORM_UNKNOWN = 0,
        SVG_TRANSFORM_MATRIX = 1,
        SVG_TRANSFORM_TRANSLATE = 2,
        SVG_TRANSFORM_SCALE = 3,
        SVG_TRANSFORM_ROTATE = 4,
        SVG_TRANSFORM_SKEWX = 5,
        SVG_TRANSFORM_SKEWY = 6
    };

    enum ConstructionMode {
        ConstructIdentityTransform,
        ConstructZeroTransform
    };

    SVGTransformValue(SVGTransformType type = SVG_TRANSFORM_MATRIX, const AffineTransform& transform = { })
        : m_type(type)
        , m_matrix(SVGMatrix::create(transform))
    {
    }

    SVGTransformValue(const SVGTransformValue& other)
        : m_type(other.m_type)
        , m_matrix(SVGMatrix::create(other.matrix()->value()))
        , m_angle(other.m_angle)
        , m_rotationCenter(other.m_rotationCenter)
    {
    }

    SVGTransformValue(SVGTransformType type, Ref<SVGMatrix>&& matrix, float angle, const FloatPoint& rotationCenter)
        : m_type(type)
        , m_matrix(WTFMove(matrix))
        , m_angle(angle)
        , m_rotationCenter(rotationCenter)
    {
    }

    SVGTransformValue(SVGTransformValue&& other)
        : m_type(other.m_type)
        , m_matrix(other.m_matrix.copyRef())
        , m_angle(other.m_angle)
        , m_rotationCenter(other.m_rotationCenter)
    {
    }

    SVGTransformValue& operator=(const SVGTransformValue& other)
    {
        m_type = other.m_type;
        m_matrix->setValue(other.m_matrix->value());
        m_angle = other.m_angle;
        m_rotationCenter = other.m_rotationCenter;
        return *this;
    }

    SVGTransformType type() const { return m_type; }
    const Ref<SVGMatrix>& matrix() const { return m_matrix; }
    float angle() const { return m_angle; }
    FloatPoint rotationCenter() const { return m_rotationCenter; }

    bool isValid() const { return m_type != SVG_TRANSFORM_UNKNOWN; }

    void setMatrix(const AffineTransform& matrix)
    {
        m_type = SVG_TRANSFORM_MATRIX;
        m_angle = 0;
        m_rotationCenter = FloatPoint();
        m_matrix->setValue(matrix);
    }

    void matrixDidChange()
    {
        // The underlying matrix has been changed, alter the transformation type.
        // Spec: In case the matrix object is changed directly (i.e., without using the methods on the SVGTransform interface itself)
        // then the type of the SVGTransform changes to SVG_TRANSFORM_MATRIX.
        m_type = SVG_TRANSFORM_MATRIX;
        m_angle = 0;
        m_rotationCenter = FloatPoint();
    }

    FloatPoint translate() const
    {
        return FloatPoint::narrowPrecision(m_matrix->e(), m_matrix->f());
    }

    void setTranslate(float tx, float ty)
    {
        m_type = SVG_TRANSFORM_TRANSLATE;
        m_angle = 0;
        m_rotationCenter = FloatPoint();

        m_matrix->value().makeIdentity();
        m_matrix->value().translate(tx, ty);
    }

    FloatSize scale() const
    {
        return FloatSize::narrowPrecision(m_matrix->a(), m_matrix->d());
    }

    void setScale(float sx, float sy)
    {
        m_type = SVG_TRANSFORM_SCALE;
        m_angle = 0;
        m_rotationCenter = FloatPoint();

        m_matrix->value().makeIdentity();
        m_matrix->value().scaleNonUniform(sx, sy);
    }

    void setRotate(float angle, float cx, float cy)
    {
        m_type = SVG_TRANSFORM_ROTATE;
        m_angle = angle;
        m_rotationCenter = FloatPoint(cx, cy);

        // TODO: toString() implementation, which can show cx, cy (need to be stored?)
        m_matrix->value().makeIdentity();
        m_matrix->value().translate(cx, cy);
        m_matrix->value().rotate(angle);
        m_matrix->value().translate(-cx, -cy);
    }

    void setSkewX(float angle)
    {
        m_type = SVG_TRANSFORM_SKEWX;
        m_angle = angle;
        m_rotationCenter = FloatPoint();

        m_matrix->value().makeIdentity();
        m_matrix->value().skewX(angle);
    }

    void setSkewY(float angle)
    {
        m_type = SVG_TRANSFORM_SKEWY;
        m_angle = angle;
        m_rotationCenter = FloatPoint();

        m_matrix->value().makeIdentity();
        m_matrix->value().skewY(angle);
    }

    String valueAsString() const
    {
        StringBuilder builder;
        builder.append(prefixForTransfromType(m_type));
        switch (m_type) {
        case SVG_TRANSFORM_UNKNOWN:
            break;
        case SVG_TRANSFORM_MATRIX:
            appendMatrix(builder);
            break;
        case SVG_TRANSFORM_TRANSLATE:
            appendTranslate(builder);
            break;
        case SVG_TRANSFORM_SCALE:
            appendScale(builder);
            break;
        case SVG_TRANSFORM_ROTATE:
            appendRotate(builder);
            break;
        case SVG_TRANSFORM_SKEWX:
            appendSkewX(builder);
            break;
        case SVG_TRANSFORM_SKEWY:
            appendSkewY(builder);
            break;
        }
        return builder.toString();
    }

    static String prefixForTransfromType(SVGTransformType type)
    {
        switch (type) {
        case SVG_TRANSFORM_UNKNOWN:
            return emptyString();
        case SVG_TRANSFORM_MATRIX:
            return "matrix(";
        case SVG_TRANSFORM_TRANSLATE:
            return "translate(";
        case SVG_TRANSFORM_SCALE:
            return "scale(";
        case SVG_TRANSFORM_ROTATE:
            return "rotate(";
        case SVG_TRANSFORM_SKEWX:
            return "skewX(";
        case SVG_TRANSFORM_SKEWY:
            return "skewY(";
        }
        ASSERT_NOT_REACHED();
        return emptyString();
    }

private:
    static void appendNumbers(StringBuilder& builder)
    {
        builder.append(')');
    }

    template<typename Number, typename... Numbers>
    static void appendNumbers(StringBuilder& builder, Number number, Numbers... numbers)
    {
        if (builder.length() && builder[builder.length() - 1] != '(')
            builder.append(' ');
        builder.appendNumber(number);
        appendNumbers(builder, numbers...);
    }

    void appendMatrix(StringBuilder& builder) const
    {
        appendNumbers(builder, m_matrix->a(), m_matrix->b(), m_matrix->c(), m_matrix->d(), m_matrix->e(), m_matrix->f());
    }

    void appendTranslate(StringBuilder& builder) const
    {
        appendNumbers(builder, m_matrix->e(), m_matrix->f());
    }

    void appendScale(StringBuilder& builder) const
    {
        appendNumbers(builder, m_matrix->value().xScale(), m_matrix->value().yScale());
    }

    void appendRotate(StringBuilder& builder) const
    {
        double angleInRad = deg2rad(m_angle);
        double cosAngle = std::cos(angleInRad);
        double sinAngle = std::sin(angleInRad);

        float cx = narrowPrecisionToFloat(cosAngle != 1 ? (m_matrix->e() * (1 - cosAngle) - m_matrix->f() * sinAngle) / (1 - cosAngle) / 2 : 0);
        float cy = narrowPrecisionToFloat(cosAngle != 1 ? (m_matrix->e() * sinAngle / (1 - cosAngle) + m_matrix->f()) / 2 : 0);

        if (cx || cy)
            appendNumbers(builder, m_angle, cx, cy);
        else
            appendNumbers(builder, m_angle);
    }

    void appendSkewX(StringBuilder& builder) const
    {
        appendNumbers(builder, m_angle);
    }

    void appendSkewY(StringBuilder& builder) const
    {
        appendNumbers(builder, m_angle);
    }

    SVGTransformType m_type { SVG_TRANSFORM_UNKNOWN };
    Ref<SVGMatrix> m_matrix;
    float m_angle { 0 };
    FloatPoint m_rotationCenter;
};

} // namespace WebCore
