#include "property.h"
#include "element.h"
#include "lunasvg.h"

#include <algorithm>
#include <cmath>

namespace lunasvg {

const Color Color::Black(0xFF000000);
const Color Color::White(0xFFFFFFFF);
const Color Color::Transparent(0x00000000);

Color& Color::combine(double opacity)
{
    *this = combined(opacity);
    return *this;
}

Color Color::combined(double opacity) const
{
    auto rgb = m_value & 0x00FFFFFF;
    auto a = static_cast<int>(std::clamp(opacity * alpha(), 0.0, 255.0));
    return Color(rgb | a << 24);
}

Paint::Paint(const Color& color)
    : m_color(color)
{
}

Paint::Paint(const std::string& ref, const Color& color)
    : m_ref(ref), m_color(color)
{
}

Point::Point(double x, double y)
    : x(x), y(y)
{
}

const Rect Rect::Empty{0, 0, 0, 0};
const Rect Rect::Invalid{0, 0, -1, -1};

Rect::Rect(double x, double y, double w, double h)
    : x(x), y(y), w(w), h(h)
{
}

Rect::Rect(const Box& box)
    : x(box.x), y(box.y), w(box.w), h(box.h)
{
}

Rect Rect::operator&(const Rect& rect) const
{
    if(!rect.valid())
        return *this;

    if(!valid())
        return rect;

    auto l = std::max(x, rect.x);
    auto t = std::max(y, rect.y);
    auto r = std::min(x + w, rect.x + rect.w);
    auto b = std::min(y + h, rect.y + rect.h);

    return Rect{l, t, r-l, b-t};
}

Rect Rect::operator|(const Rect& rect) const
{
    if(!rect.valid())
        return *this;

    if(!valid())
        return rect;

    auto l = std::min(x, rect.x);
    auto t = std::min(y, rect.y);
    auto r = std::max(x + w, rect.x + rect.w);
    auto b = std::max(y + h, rect.y + rect.h);

    return Rect{l, t, r-l, b-t};
}

Rect& Rect::intersect(const Rect& rect)
{
    *this = *this & rect;
    return *this;
}

Rect& Rect::unite(const Rect& rect)
{
    *this = *this | rect;
    return *this;
}

Transform::Transform(double m00, double m10, double m01, double m11, double m02, double m12)
    : m00(m00), m10(m10), m01(m01), m11(m11), m02(m02), m12(m12)
{
}

Transform::Transform(const Matrix& matrix)
    : m00(matrix.a), m10(matrix.b), m01(matrix.c), m11(matrix.d), m02(matrix.e), m12(matrix.f)
{
}

Transform Transform::inverted() const
{
    double det = (this->m00 * this->m11 - this->m10 * this->m01);
    if(det == 0.0)
        return Transform{};

    double inv_det = 1.0 / det;
    double m00 = this->m00 * inv_det;
    double m10 = this->m10 * inv_det;
    double m01 = this->m01 * inv_det;
    double m11 = this->m11 * inv_det;
    double m02 = (this->m01 * this->m12 - this->m11 * this->m02) * inv_det;
    double m12 = (this->m10 * this->m02 - this->m00 * this->m12) * inv_det;

    return Transform{m11, -m10, -m01, m00, m02, m12};
}

Transform Transform::operator*(const Transform& transform) const
{
    double m00 = this->m00 * transform.m00 + this->m10 * transform.m01;
    double m10 = this->m00 * transform.m10 + this->m10 * transform.m11;
    double m01 = this->m01 * transform.m00 + this->m11 * transform.m01;
    double m11 = this->m01 * transform.m10 + this->m11 * transform.m11;
    double m02 = this->m02 * transform.m00 + this->m12 * transform.m01 + transform.m02;
    double m12 = this->m02 * transform.m10 + this->m12 * transform.m11 + transform.m12;

    return Transform{m00, m10, m01, m11, m02, m12};
}

Transform& Transform::operator*=(const Transform& transform)
{
    *this = *this * transform;
    return *this;
}

Transform& Transform::premultiply(const Transform& transform)
{
    *this = transform * *this;
    return *this;
}

Transform& Transform::postmultiply(const Transform& transform)
{
    *this = *this * transform;
    return *this;
}

Transform& Transform::rotate(double angle)
{
    *this = rotated(angle) * *this;
    return *this;
}

Transform& Transform::rotate(double angle, double cx, double cy)
{
    *this = rotated(angle, cx, cy) * *this;
    return *this;
}

Transform& Transform::scale(double sx, double sy)
{
    *this = scaled(sx, sy) * *this;
    return *this;
}

Transform& Transform::shear(double shx, double shy)
{
    *this = sheared(shx, shy) * *this;
    return *this;
}

Transform& Transform::translate(double tx, double ty)
{
    *this = translated(tx, ty) * *this;
    return *this;
}

Transform& Transform::transform(double m00, double m10, double m01, double m11, double m02, double m12)
{
    *this = Transform{m00, m10, m01, m11, m02, m12} * *this;
    return *this;
}

Transform& Transform::identity()
{
    *this = Transform{1, 0, 0, 1, 0, 0};
    return *this;
}

Transform& Transform::invert()
{
    *this = inverted();
    return *this;
}

void Transform::map(double x, double y, double* _x, double* _y) const
{
    *_x = x * m00 + y * m01 + m02;
    *_y = x * m10 + y * m11 + m12;
}

Point Transform::map(double x, double y) const
{
    map(x, y, &x, &y);
    return Point{x, y};
}

Point Transform::map(const Point& point) const
{
    return map(point.x, point.y);
}

Rect Transform::map(const Rect& rect) const
{
    if(!rect.valid())
        return Rect::Invalid;

    auto x1 = rect.x;
    auto y1 = rect.y;
    auto x2 = rect.x + rect.w;
    auto y2 = rect.y + rect.h;

    const Point p[] = {
        map(x1, y1), map(x2, y1),
        map(x2, y2), map(x1, y2)
    };

    auto l = p[0].x;
    auto t = p[0].y;
    auto r = p[0].x;
    auto b = p[0].y;

    for(int i = 1;i < 4;i++)
    {
        if(p[i].x < l) l = p[i].x;
        if(p[i].x > r) r = p[i].x;
        if(p[i].y < t) t = p[i].y;
        if(p[i].y > b) b = p[i].y;
    }

    return Rect{l, t, r-l, b-t};
}

static const double pi = 3.14159265358979323846;

Transform Transform::rotated(double angle)
{
    auto c = std::cos(angle * pi / 180.0);
    auto s = std::sin(angle * pi / 180.0);

    return Transform{c, s, -s, c, 0, 0};
}

Transform Transform::rotated(double angle, double cx, double cy)
{
    auto c = std::cos(angle * pi / 180.0);
    auto s = std::sin(angle * pi / 180.0);

    auto x = cx * (1 - c) + cy * s;
    auto y = cy * (1 - c) - cx * s;

    return Transform{c, s, -s, c, x, y};
}

Transform Transform::scaled(double sx, double sy)
{
    return Transform{sx, 0, 0, sy, 0, 0};
}

Transform Transform::sheared(double shx, double shy)
{
    auto x = std::tan(shx * pi / 180.0);
    auto y = std::tan(shy * pi / 180.0);

    return Transform{1, y, x, 1, 0, 0};
}

Transform Transform::translated(double tx, double ty)
{
    return Transform{1, 0, 0, 1, tx, ty};
}

void Path::moveTo(double x, double y)
{
    m_commands.push_back(PathCommand::MoveTo);
    m_points.emplace_back(x, y);
}

void Path::lineTo(double x, double y)
{
    m_commands.push_back(PathCommand::LineTo);
    m_points.emplace_back(x, y);
}

void Path::cubicTo(double x1, double y1, double x2, double y2, double x3, double y3)
{
    m_commands.push_back(PathCommand::CubicTo);
    m_points.emplace_back(x1, y1);
    m_points.emplace_back(x2, y2);
    m_points.emplace_back(x3, y3);
}

void Path::close()
{
    if(m_commands.empty())
        return;

    if(m_commands.back() == PathCommand::Close)
        return;

    m_commands.push_back(PathCommand::Close);
}

void Path::reset()
{
    m_commands.clear();
    m_points.clear();
}

bool Path::empty() const
{
    return m_commands.empty();
}

void Path::quadTo(double cx, double cy, double x1, double y1, double x2, double y2)
{
    auto cx1 = 2.0 / 3.0 * x1 + 1.0 / 3.0 * cx;
    auto cy1 = 2.0 / 3.0 * y1 + 1.0 / 3.0 * cy;
    auto cx2 = 2.0 / 3.0 * x1 + 1.0 / 3.0 * x2;
    auto cy2 = 2.0 / 3.0 * y1 + 1.0 / 3.0 * y2;
    cubicTo(cx1, cy1, cx2, cy2, x2, y2);
}

void Path::arcTo(double cx, double cy, double rx, double ry, double xAxisRotation, bool largeArcFlag, bool sweepFlag, double x, double y)
{
    rx = std::fabs(rx);
    ry = std::fabs(ry);

    auto sin_th = std::sin(xAxisRotation * pi / 180.0);
    auto cos_th = std::cos(xAxisRotation * pi / 180.0);

    auto dx = (cx - x) / 2.0;
    auto dy = (cy - y) / 2.0;
    auto dx1 =  cos_th * dx + sin_th * dy;
    auto dy1 = -sin_th * dx + cos_th * dy;
    auto Pr1 = rx * rx;
    auto Pr2 = ry * ry;
    auto Px = dx1 * dx1;
    auto Py = dy1 * dy1;
    auto check = Px / Pr1 + Py / Pr2;
    if(check > 1)
    {
        rx = rx * std::sqrt(check);
        ry = ry * std::sqrt(check);
    }

    auto a00 =  cos_th / rx;
    auto a01 =  sin_th / rx;
    auto a10 = -sin_th / ry;
    auto a11 =  cos_th / ry;
    auto x0 = a00 * cx + a01 * cy;
    auto y0 = a10 * cx + a11 * cy;
    auto x1 = a00 * x + a01 * y;
    auto y1 = a10 * x + a11 * y;
    auto d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
    auto sfactor_sq = 1.0 / d - 0.25;
    if(sfactor_sq < 0) sfactor_sq = 0;
    auto sfactor = std::sqrt(sfactor_sq);
    if(sweepFlag == largeArcFlag) sfactor = -sfactor;
    auto xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
    auto yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);

    auto th0 = std::atan2(y0 - yc, x0 - xc);
    auto th1 = std::atan2(y1 - yc, x1 - xc);

    double th_arc = th1 - th0;
    if(th_arc < 0.0 && sweepFlag)
        th_arc += 2.0 * pi;
    else if(th_arc > 0.0 && !sweepFlag)
        th_arc -= 2.0 * pi;

    auto n_segs = static_cast<int>(std::ceil(std::fabs(th_arc / (pi * 0.5 + 0.001))));
    for(int i = 0;i < n_segs;i++)
    {
        auto th2 = th0 + i * th_arc / n_segs;
        auto th3 = th0 + (i + 1) * th_arc / n_segs;

        auto a00 =  cos_th * rx;
        auto a01 = -sin_th * ry;
        auto a10 =  sin_th * rx;
        auto a11 =  cos_th * ry;

        auto thHalf = 0.5 * (th3 - th2);
        auto t = (8.0 / 3.0) * std::sin(thHalf * 0.5) * std::sin(thHalf * 0.5) / std::sin(thHalf);
        auto x1 = xc + std::cos(th2) - t * std::sin(th2);
        auto y1 = yc + std::sin(th2) + t * std::cos(th2);
        auto x3 = xc + std::cos(th3);
        auto y3 = yc + std::sin(th3);
        auto x2 = x3 + t * std::sin(th3);
        auto y2 = y3 - t * std::cos(th3);

        auto cx1 = a00 * x1 + a01 * y1;
        auto cy1 = a10 * x1 + a11 * y1;
        auto cx2 = a00 * x2 + a01 * y2;
        auto cy2 = a10 * x2 + a11 * y2;
        auto cx3 = a00 * x3 + a01 * y3;
        auto cy3 = a10 * x3 + a11 * y3;
        cubicTo(cx1, cy1, cx2, cy2, cx3, cy3);
    }
}

static const double kappa = 0.55228474983079339840;

void Path::ellipse(double cx, double cy, double rx, double ry)
{
    auto left = cx - rx;
    auto top = cy - ry;
    auto right = cx + rx;
    auto bottom = cy + ry;

    auto cpx = rx * kappa;
    auto cpy = ry * kappa;

    moveTo(cx, top);
    cubicTo(cx+cpx, top, right, cy-cpy, right, cy);
    cubicTo(right, cy+cpy, cx+cpx, bottom, cx, bottom);
    cubicTo(cx-cpx, bottom, left, cy+cpy, left, cy);
    cubicTo(left, cy-cpy, cx-cpx, top, cx, top);
    close();
}

void Path::rect(double x, double y, double w, double h, double rx, double ry)
{
    rx = std::min(rx, w * 0.5);
    ry = std::min(ry, h * 0.5);

    auto right = x + w;
    auto bottom = y + h;

    if(rx == 0.0 && ry == 0.0)
    {
        moveTo(x, y);
        lineTo(right, y);
        lineTo(right, bottom);
        lineTo(x, bottom);
        lineTo(x, y);
        close();
    }
    else
    {
        double cpx = rx * kappa;
        double cpy = ry * kappa;
        moveTo(x, y+ry);
        cubicTo(x, y+ry-cpy, x+rx-cpx, y, x+rx, y);
        lineTo(right-rx, y);
        cubicTo(right-rx+cpx, y, right, y+ry-cpy, right, y+ry);
        lineTo(right, bottom-ry);
        cubicTo(right, bottom-ry+cpy, right-rx+cpx, bottom, right-rx, bottom);
        lineTo(x+rx, bottom);
        cubicTo(x+rx-cpx, bottom, x, bottom-ry+cpy, x, bottom-ry);
        lineTo(x, y+ry);
        close();
    }
}

Rect Path::box() const
{
    if(m_points.empty())
        return Rect{};

    auto l = m_points[0].x;
    auto t = m_points[0].y;
    auto r = m_points[0].x;
    auto b = m_points[0].y;

    for(std::size_t i = 1;i < m_points.size();i++)
    {
        if(m_points[i].x < l) l = m_points[i].x;
        if(m_points[i].x > r) r = m_points[i].x;
        if(m_points[i].y < t) t = m_points[i].y;
        if(m_points[i].y > b) b = m_points[i].y;
    }

    return Rect{l, t, r-l, b-t};
}

PathIterator::PathIterator(const Path& path)
    : m_commands(path.commands()),
      m_points(path.points().data())
{
}

PathCommand PathIterator::currentSegment(std::array<Point, 3>& points) const
{
    auto command = m_commands[m_index];
    switch(command) {
    case PathCommand::MoveTo:
        points[0] = m_points[0];
        m_startPoint = points[0];
        break;
    case PathCommand::LineTo:
        points[0] = m_points[0];
        break;
    case PathCommand::CubicTo:
        points[0] = m_points[0];
        points[1] = m_points[1];
        points[2] = m_points[2];
        break;
    case PathCommand::Close:
        points[0] = m_startPoint;
        break;
    }

    return command;
}

bool PathIterator::isDone() const
{
    return (m_index >= m_commands.size());
}

void PathIterator::next()
{
    switch(m_commands[m_index]) {
    case PathCommand::MoveTo:
    case PathCommand::LineTo:
        m_points += 1;
        break;
    case PathCommand::CubicTo:
        m_points += 3;
        break;
    default:
        break;
    }

    m_index += 1;
}

const Length Length::Unknown{0, LengthUnits::Unknown};
const Length Length::Zero{0, LengthUnits::Number};
const Length Length::One{1, LengthUnits::Number};
const Length Length::Three{3, LengthUnits::Number};
const Length Length::HundredPercent{100, LengthUnits::Percent};
const Length Length::FiftyPercent{50, LengthUnits::Percent};
const Length Length::OneTwentyPercent{120, LengthUnits::Percent};
const Length Length::MinusTenPercent{-10, LengthUnits::Percent};

Length::Length(double value)
    : m_value(value)
{
}

Length::Length(double value, LengthUnits units)
    : m_value(value), m_units(units)
{
}

static const double dpi = 96.0;

double Length::value(double max) const
{
    switch(m_units) {
    case LengthUnits::Number:
    case LengthUnits::Px:
        return m_value;
    case LengthUnits::In:
        return m_value * dpi;
    case LengthUnits::Cm:
        return m_value * dpi / 2.54;
    case LengthUnits::Mm:
        return m_value * dpi / 25.4;
    case LengthUnits::Pt:
        return m_value * dpi / 72.0;
    case LengthUnits::Pc:
        return m_value * dpi / 6.0;
    case LengthUnits::Percent:
        return m_value * max / 100.0;
    default:
        break;
    }

    return 0.0;
}

static const double sqrt2 = 1.41421356237309504880;

double Length::value(const Element* element, LengthMode mode) const
{
    if(m_units == LengthUnits::Percent)
    {
        auto viewport = element->currentViewport();
        auto w = viewport.w;
        auto h = viewport.h;
        auto max = (mode == LengthMode::Width) ? w : (mode == LengthMode::Height) ? h : std::sqrt(w*w+h*h) / sqrt2;
        return m_value * max / 100.0;
    }

    return value(1.0);
}

LengthContext::LengthContext(const Element* element)
    : m_element(element)
{
}

LengthContext::LengthContext(const Element* element, Units units)
    : m_element(element), m_units(units)
{
}

double LengthContext::valueForLength(const Length& length, LengthMode mode) const
{
    if(m_units == Units::ObjectBoundingBox)
        return length.value(1.0);
    return length.value(m_element, mode);
}

PreserveAspectRatio::PreserveAspectRatio(Align align, MeetOrSlice scale)
    : m_align(align), m_scale(scale)
{
}

Transform PreserveAspectRatio::getMatrix(double width, double height, const Rect& viewBox) const
{
    if(viewBox.empty())
        return Transform{};

    auto xscale = width / viewBox.w;
    auto yscale = height / viewBox.h;
    if(m_align == Align::None)
    {
        auto xoffset = -viewBox.x * xscale;
        auto yoffset = -viewBox.y * yscale;
        return Transform{xscale, 0, 0, yscale, xoffset, yoffset};
    }

    auto scale = (m_scale == MeetOrSlice::Meet) ? std::min(xscale, yscale) : std::max(xscale, yscale);
    auto viewWidth = viewBox.w * scale;
    auto viewHeight = viewBox.h * scale;

    auto xoffset = -viewBox.x * scale;
    auto yoffset = -viewBox.y * scale;

    switch(m_align) {
    case Align::xMidYMin:
    case Align::xMidYMid:
    case Align::xMidYMax:
        xoffset += (width - viewWidth) * 0.5;
        break;
    case Align::xMaxYMin:
    case Align::xMaxYMid:
    case Align::xMaxYMax:
        xoffset += (width - viewWidth);
        break;
    default:
        break;
    }

    switch(m_align) {
    case Align::xMinYMid:
    case Align::xMidYMid:
    case Align::xMaxYMid:
        yoffset += (height - viewHeight) * 0.5;
        break;
    case Align::xMinYMax:
    case Align::xMidYMax:
    case Align::xMaxYMax:
        yoffset += (height - viewHeight);
        break;
    default:
        break;
    }

    return Transform{scale, 0, 0, scale, xoffset, yoffset};
}

Rect PreserveAspectRatio::getClip(double width, double height, const Rect& viewBox) const
{
    if(viewBox.empty())
        return Rect{0, 0, width, height};

    return viewBox;
}

Angle::Angle(MarkerOrient type)
    : m_type(type)
{
}

Angle::Angle(double value, MarkerOrient type)
    : m_value(value), m_type(type)
{
}

} // namespace lunasvg