#pragma once

#include "olcPGEX_TTF.h"
#include "olcUTIL_Geometry2D.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <iostream>
#include <vector>
#include <limits>

// Declarations
namespace olc {
    class ViewPort : public olc::PGEX {
    public:
        ViewPort();
        ViewPort(std::vector<vf2d> vertices, vf2d offset = {0, 0});
        geom2d::rect<float>rect{};
        virtual ~ViewPort();
        void addPoint(vf2d point);
        void clear();
        void drawEdges();
        void setOffset(vf2d offset);
        const vf2d&GetOffset();

        static ViewPort rectViewPort(vf2d topLeft,
                                     vf2d size,
                                     olc::vf2d offset = {0, 0});

        void DrawDecal(const olc::vf2d &pos,
                       olc::Decal *decal,
                       const olc::vf2d &scale = {1.0f, 1.0f},
                       const olc::Pixel &tint = olc::WHITE) const;
        void DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col=olc::WHITE)const;
        void DrawPartialDecal(const olc::vf2d &pos,
                              olc::Decal *decal,
                              const olc::vf2d &source_pos,
                              const olc::vf2d &source_size,
                              const olc::vf2d &scale = {1.0f, 1.0f},
                              const olc::Pixel &tint = olc::WHITE) const;
        void DrawPartialDecal(const vf2d &pos,
                              const vf2d &size,
                              Decal *decal,
                              const vf2d source_pos,
                              const vf2d &source_size,
                              const Pixel &tint = olc::WHITE) const;
        void DrawExplicitDecal(olc::Decal *decal,
                               const olc::vf2d *pos,
                               const olc::vf2d *uv,
                               const olc::Pixel *col,
                               const float *ws,
                               uint32_t elements = 4) const;
        void DrawWarpedDecal(Decal *decal,
                             const vf2d (&pos)[4],
                             const Pixel &tint = WHITE) const;
        void DrawWarpedDecal(Decal *decal,
                             const vf2d *pos,
                             const Pixel &tint = WHITE) const;
        void DrawWarpedDecal(Decal *decal,
                             const std::array<vf2d, 4> &pos,
                             const Pixel &tint = WHITE) const;
        void DrawPartialWarpedDecal(Decal *decal,
                                    const vf2d (&pos)[4],
                                    const vf2d &source_pos,
                                    const vf2d &source_size,
                                    const Pixel &tint = WHITE) const;
        void DrawPartialWarpedDecal(Decal *decal,
                                    const vf2d *pos,
                                    const vf2d &source_pos,
                                    const vf2d &source_size,
                                    const Pixel &tint = WHITE) const;
        void DrawPartialWarpedDecal(Decal *decal,
                                    const std::array<vf2d, 4> &pos,
                                    const vf2d &source_pos,
                                    const vf2d &source_size,
                                    const Pixel &tint = WHITE) const;
        void DrawRotatedDecal(const vf2d &pos,
                              Decal *decal,
                              const float fAngle,
                              const vf2d &center = {0.0f, 0.0f},
                              const vf2d &scale = {1.0f, 1.0f},
                              const Pixel &tint = WHITE) const;
        void DrawPartialRotatedDecal(const vf2d &pos,
                                     Decal *decal,
                                     const float fAngle,
                                     const vf2d &center,
                                     const vf2d &source_pos,
                                     const vf2d &source_size,
                                     const vf2d &scale = {1.0f, 1.0f},
                                     const Pixel &tint = WHITE) const;
        void FillRectDecal(const vf2d &pos,
                           const vf2d &size,
                           const Pixel col = WHITE) const;
        void GradientFillRectDecal(const vf2d &pos,
                                   const vf2d &size,
                                   const Pixel colTL,
                                   const Pixel colBL,
                                   const Pixel colBR,
                                   const Pixel colTR) const;
        void DrawPolygonDecal(Decal *decal,
                              const std::vector<vf2d> &pos,
                              const std::vector<vf2d> &uv,
                              const Pixel tint = WHITE) const;
        void DrawPolygonDecal(Decal *decal,
                              const std::vector<vf2d> &pos,
                              const std::vector<float> &depth,
                              const std::vector<vf2d> &uv,
                              const Pixel tint = WHITE) const;
        void DrawPolygonDecal(Decal *decal,
                              const std::vector<vf2d> &pos,
                              const std::vector<vf2d> &uv,
                              const std::vector<Pixel> &tint) const;
        void DrawLineDecal(const vf2d &pos1,
                           const vf2d &pos2,
                           Pixel p = WHITE) const;
        // Draws a multiline string as a decal, with tinting and scaling
		void DrawStringDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f },const float width=std::numeric_limits<float>::max(),const bool disableDynamicScaling=false);
		void DrawStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f });
		void DrawStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col = olc::WHITE, const olc::vf2d& scale = { 1.0f, 1.0f }, const float width=std::numeric_limits<float>::max(),const bool disableDynamicScaling=false);
		void DrawShadowStringDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f },const float width=std::numeric_limits<float>::max(),const float shadowSizeFactor=1,const bool disableDynamicScaling=false);
		void DrawShadowStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f },const float width=std::numeric_limits<float>::max(),const float shadowSizeFactor=1,const bool disableDynamicScaling=false);
		void DrawShadowStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f },const float shadowSizeFactor=1);
		void DrawDropShadowStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col = olc::WHITE, const Pixel shadowCol = olc::BLACK, const olc::vf2d& scale = { 1.0f, 1.0f });

    private:
        void drawClippedDecal(Decal *decal,
                              const vf2d *points,
                              const vf2d *uvs,
                              const Pixel *col,
                              const float *ws,
                              uint32_t elements = 0) const;

        static float lineSegmentIntersect(vf2d lineA,
                                          vf2d lineB,
                                          vf2d segmentA,
                                          vf2d segmentB);
        static float directionFromLine(vf2d lineA, vf2d lineB, vf2d point);

        std::vector<vf2d> clipVertices;
        olc::vf2d offset;
    };
} // namespace olc

// Definitions

#ifdef OLC_PGEX_VIEWPORT
#undef OLC_PGEX_VIEWPORT

olc::ViewPort::ViewPort() {
}
olc::ViewPort::~ViewPort() {
}

olc::ViewPort::ViewPort(std::vector<vf2d> vertices, olc::vf2d offset)
        : clipVertices{vertices},
          offset{offset} {
}

void olc::ViewPort::addPoint(vf2d point) {
    clipVertices.push_back(point);
}

void olc::ViewPort::clear() {
    clipVertices.clear();
}

void olc::ViewPort::drawEdges() {
    for (auto i = 0u; i < clipVertices.size(); i++) {
        auto current = clipVertices[i] + offset;
        auto next = clipVertices[(i + 1) % clipVertices.size()] + offset;

        pge->DrawLineDecal(current, next, olc::RED);
    }
}

void olc::ViewPort::setOffset(vf2d offset) {
    this->offset = offset;
}

const vf2d&olc::ViewPort::GetOffset() {
    return offset;
}

olc::ViewPort
        olc::ViewPort::rectViewPort(vf2d topLeft, vf2d size, olc::vf2d offset) {
    olc::ViewPort newPort={{
                    topLeft,
                    {topLeft.x, topLeft.y + size.y},
                    topLeft + size,
                    {topLeft.x + size.x, topLeft.y},
            },
            offset};
    newPort.rect={topLeft,size};
    return newPort;
}

void olc::ViewPort::DrawDecal(const olc::vf2d &pos,
                              olc::Decal *decal,
                              const olc::vf2d &scale,
                              const olc::Pixel &tint) const {
    std::vector<olc::vf2d> points{
            pos,
            {pos.x, pos.y + decal->sprite->height * scale.y},
            {pos.x + decal->sprite->width * scale.x,
             pos.y + decal->sprite->height * scale.y},
            {pos.x + decal->sprite->width * scale.x, pos.y},
    };
    DrawWarpedDecal(decal, points.data(), tint);
}

void olc::ViewPort::DrawPartialDecal(const olc::vf2d &pos,
                                     olc::Decal *decal,
                                     const olc::vf2d &source_pos,
                                     const olc::vf2d &source_size,
                                     const olc::vf2d &scale,
                                     const olc::Pixel &tint) const {
    DrawPartialDecal(pos, source_size * scale, decal, source_pos, source_size, tint);
}

void olc::ViewPort::DrawPartialDecal(const vf2d &pos,
                                     const vf2d &size,
                                     Decal *decal,
                                     const vf2d source_pos,
                                     const vf2d &source_size,
                                     const Pixel &tint) const {
    std::vector<vf2d> points{
            pos,
            {pos.x, pos.y + size.y},
            pos + size,
            {pos.x + size.x, pos.y},
    };
    DrawPartialWarpedDecal(decal, points.data(), source_pos, source_size, tint);
}

void olc::ViewPort::DrawExplicitDecal(olc::Decal *decal,
                                      const olc::vf2d *pos,
                                      const olc::vf2d *uv,
                                      const olc::Pixel *col,
                                      const float *ws,
                                      uint32_t elements) const {
    drawClippedDecal(decal, pos, uv, col, ws, elements);
}

void olc::ViewPort::DrawWarpedDecal(Decal *decal,
                                    const vf2d (&pos)[4],
                                    const Pixel &tint) const {
    DrawWarpedDecal(decal, (const vf2d *)pos, tint);
}
void olc::ViewPort::DrawWarpedDecal(Decal *decal,
                                    const vf2d *pos,
                                    const Pixel &tint) const {
    std::vector<float> w{ 1, 1, 1, 1 };
    std::vector<olc::vf2d> newPos;
	newPos.resize(4);
    std::vector<vf2d> uvs{
            {0, 0},
            {0, 1},
            {1, 1},
            {1, 0},
    };
    std::vector<Pixel> cols{
            tint,
            tint,
            tint,
            tint,
    };

    olc::vf2d vInvScreenSize={ 1.0f / pge->GetScreenSize().x, 1.0f / pge->GetScreenSize().y };

    olc::vf2d center;
	float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y));
	if (rd != 0)
	{
		rd = 1.0f / rd;
		float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd;
		float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd;
		if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]);
		float d[4];	for (int i = 0; i < 4; i++)	d[i] = (pos[i] - center).mag();
		for (int i = 0; i < 4; i++)
		{
			float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3];
			uvs[i] *= q; w[i] *= q;
		}

        drawClippedDecal(decal, pos, uvs.data(), cols.data(), w.data(), 4);
    }

}
void olc::ViewPort::DrawWarpedDecal(Decal *decal,
                                    const std::array<vf2d, 4> &pos,
                                    const Pixel &tint) const {
    DrawWarpedDecal(decal, pos.data(), tint);
}

void olc::ViewPort::DrawPartialWarpedDecal(Decal *decal,
                                           const vf2d (&pos)[4],
                                           const vf2d &source_pos,
                                           const vf2d &source_size,
                                           const Pixel &tint) const {
    DrawPartialWarpedDecal(decal,
                           (const vf2d *)pos,
                           source_pos,
                           source_size,
                           tint);
}

void olc::ViewPort::DrawPartialWarpedDecal(Decal *decal,
                                           const vf2d *pos,
                                           const vf2d &source_pos,
                                           const vf2d &source_size,
                                           const Pixel &tint) const {
    olc::vf2d sourceUvPos =
            source_pos
            / olc::vf2d{static_cast<float>(decal->sprite->width),
                        static_cast<float>(decal->sprite->height)};
    olc::vf2d sourceUvSize =
            source_size
            / olc::vf2d{static_cast<float>(decal->sprite->width),
                        static_cast<float>(decal->sprite->height)};
    std::vector<vf2d> uvs{
            sourceUvPos,
            {sourceUvPos.x, sourceUvPos.y + sourceUvSize.y},
            sourceUvPos + sourceUvSize,
            {sourceUvPos.x + sourceUvSize.x, sourceUvPos.y},
    };
    std::vector<Pixel> cols{
            tint,
            tint,
            tint,
            tint,
    };

    std::vector<float>ws{1,1,1,1};

    olc::vf2d center;
	float rd = ((pos[2].x - pos[0].x) * (pos[3].y - pos[1].y) - (pos[3].x - pos[1].x) * (pos[2].y - pos[0].y));
	if (rd != 0)
	{
		rd = 1.0f / rd;
		float rn = ((pos[3].x - pos[1].x) * (pos[0].y - pos[1].y) - (pos[3].y - pos[1].y) * (pos[0].x - pos[1].x)) * rd;
		float sn = ((pos[2].x - pos[0].x) * (pos[0].y - pos[1].y) - (pos[2].y - pos[0].y) * (pos[0].x - pos[1].x)) * rd;
		if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) center = pos[0] + rn * (pos[2] - pos[0]);
		float d[4];	for (int i = 0; i < 4; i++)	d[i] = (pos[i] - center).mag();
		for (int i = 0; i < 4; i++)
		{
			float q = d[i] == 0.0f ? 1.0f : (d[i] + d[(i + 2) & 3]) / d[(i + 2) & 3];
			uvs[i] *= q; ws[i] *= q;
		}

        drawClippedDecal(decal, pos, uvs.data(), cols.data(), ws.data(), 4);
    }
}

void olc::ViewPort::DrawPartialWarpedDecal(Decal *decal,
                                           const std::array<vf2d, 4> &pos,
                                           const vf2d &source_pos,
                                           const vf2d &source_size,
                                           const Pixel &tint) const {
    DrawPartialWarpedDecal(decal, pos.data(), source_pos, source_size, tint);
}

void olc::ViewPort::DrawRotatedDecal(const vf2d &pos,
                                     Decal *decal,
                                     const float fAngle,
                                     const vf2d &center,
                                     const vf2d &scale,
                                     const Pixel &tint) const {
    auto sin = std::sin(fAngle);
    auto cos = std::cos(fAngle);

    std::vector<vf2d> points{
            -center * scale,
            olc::vf2d{-center.x, decal->sprite->height - center.y} * scale,
            olc::vf2d{decal->sprite->width - center.x,
                      decal->sprite->height - center.y}
                    * scale,
            olc::vf2d{decal->sprite->width - center.x, -center.y} * scale,
    };

    for (auto i = 0u; i < points.size(); i++) {
        points[i] = pos
                    + olc::vf2d{points[i].x * cos - points[i].y * sin,
                                points[i].x * sin + points[i].y * cos};
    }

    DrawWarpedDecal(decal, points.data(), tint);
}

void olc::ViewPort::DrawPartialRotatedDecal(const vf2d &pos,
                                            Decal *decal,
                                            const float fAngle,
                                            const vf2d &center,
                                            const vf2d &source_pos,
                                            const vf2d &source_size,
                                            const vf2d &scale,
                                            const Pixel &tint) const {
    auto sin = std::sin(fAngle);
    auto cos = std::cos(fAngle);

    std::vector<vf2d> points{
            -center * scale,
            olc::vf2d{-center.x, source_size.y - center.y} * scale,
            (source_size - center) * scale,
            olc::vf2d{source_size.x - center.x, -center.y} * scale,
    };

    for (auto i = 0u; i < points.size(); i++) {
        points[i] = pos
                    + olc::vf2d{points[i].x * cos - points[i].y * sin,
                                points[i].x * sin + points[i].y * cos};
    }

    DrawPartialWarpedDecal(decal, points.data(), source_pos, source_size, tint);
}

void olc::ViewPort::FillRectDecal(const vf2d &pos,
                                  const vf2d &size,
                                  const Pixel col) const {
    std::vector<vf2d> points{
            pos,
            {pos.x, pos.y + size.y},
            pos + size,
            {pos.x + size.x, pos.y},
    };
    std::vector<vf2d> uvs{
            {0, 0},
            {0, 1},
            {1, 1},
            {1, 0},
    };

    DrawPolygonDecal(nullptr, points, uvs, col);
}

void olc::ViewPort::GradientFillRectDecal(const vf2d &pos,
                                          const vf2d &size,
                                          const Pixel colTL,
                                          const Pixel colBL,
                                          const Pixel colBR,
                                          const Pixel colTR) const {
    std::vector<vf2d> points{
            pos,
            {pos.x, pos.y + size.y},
            pos + size,
            {pos.x + size.x, pos.y},
    };

    std::vector<vf2d> uvs{
            {0, 0},
            {0, 1},
            {1, 1},
            {1, 0},
    };

    std::vector<Pixel> colors{
            colTL,
            colBL,
            colBR,
            colTR,
    };

    std::vector<float>w{1,1,1,1};

    drawClippedDecal(nullptr, points.data(), uvs.data(), colors.data(), w.data(), points.size());
}

void olc::ViewPort::DrawPolygonDecal(Decal *decal,
                                     const std::vector<vf2d> &pos,
                                     const std::vector<vf2d> &uv,
                                     const Pixel tint) const {
    std::vector<Pixel> colors;
    colors.resize(pos.size());
    for (auto i = 0u; i < colors.size(); i++) {
        colors[i] = tint;
    }

    std::vector<float>w{1,1,1,1};

    drawClippedDecal(decal, pos.data(), uv.data(), colors.data(), w.data(), pos.size());
}

void olc::ViewPort::DrawPolygonDecal(Decal *decal,
                                     const std::vector<vf2d> &pos,
                                     const std::vector<float> &,
                                     const std::vector<vf2d> &uv,
                                     const Pixel tint) const {
    DrawPolygonDecal(decal, pos, uv, tint);
}

void olc::ViewPort::DrawPolygonDecal(Decal *decal,
                                     const std::vector<vf2d> &pos,
                                     const std::vector<vf2d> &uv,
                                     const std::vector<Pixel> &tint) const {
    std::vector<float>w{1,1,1,1};
    drawClippedDecal(decal, pos.data(), uv.data(), tint.data(), w.data(), pos.size());
}

void olc::ViewPort::DrawLineDecal(const vf2d &pos1,
                                  const vf2d &pos2,
                                  Pixel p) const {
    vf2d posA = pos1;
    vf2d posB = pos2;

    for (auto i = 0u; i < clipVertices.size(); i++) {
        auto clipA = clipVertices[i];
        auto clipB = clipVertices[(i + 1) % clipVertices.size()];

        auto intersection = lineSegmentIntersect(clipA, clipB, posA, posB);
        if (intersection < 0 || intersection > 1) {
            continue;
        }

        auto clipDirection = directionFromLine(clipA, clipB, posA);
        auto intersectionPoint = posA + (posB - posA) * intersection;

        if (clipDirection >= 0) {
            posA = intersectionPoint;
        } else {
            posB = intersectionPoint;
        }
    }

    pge->DrawLineDecal(posA + offset, posB + offset, p);
}

void olc::ViewPort::drawClippedDecal(Decal *decal,
                                     const vf2d *points,
                                     const vf2d *uvs,
                                     const Pixel *col,
                                     const float *ws,
                                     uint32_t elements) const {
    std::vector<vf2d> outputList{points, points + elements};
    std::vector<vf2d> outputUvs{uvs, uvs + elements};
    std::vector<float> outputWs{ws, ws + elements};
    std::vector<Pixel> outputCols{col, col + elements};

    vf2d min={std::numeric_limits<float>::max(),std::numeric_limits<float>::max()},max;
    bool pointsOutside=false;
    if(rect!=geom2d::rect<float>{}){
        for(vf2d&points:outputList){
            if(!geom2d::contains(rect,points)){
                pointsOutside=true;
                break;
            }
        }
    }else{pointsOutside=true;}
    if(!pointsOutside)goto render;
    for (auto i = 0u; i < clipVertices.size(); i++) {
        auto clipA = clipVertices[i];
        auto clipB = clipVertices[(i + 1) % clipVertices.size()];

        auto inputList{outputList};
        auto inputUvs{outputUvs};
        auto inputWs{outputWs};
        auto inputCols{outputCols};
        outputList.clear();
        outputUvs.clear();
        outputWs.clear();
        outputCols.clear();

        for (auto i = 0u; i < inputList.size(); i++) {
            auto polygonA = inputList[i];
            auto polygonB = inputList[(i + 1) % inputList.size()];
            auto uvA = inputUvs[i];
            auto uvB = inputUvs[(i + 1) % inputList.size()];
            auto Wa = inputWs[i];
            auto Wb = inputWs[(i + 1) % inputList.size()];
            auto colA = inputCols[i];
            auto colB = inputCols[(i + 1) % inputList.size()];

            auto intersection =
                    lineSegmentIntersect(clipA, clipB, polygonA, polygonB);
            auto intersectionPoint =
                    polygonA + (polygonB - polygonA) * intersection;
            auto intersectionUv = uvA + (uvB - uvA) * intersection;
            auto intersectionW = Wa + (Wb - Wa) * intersection;
            auto intersectionCol = PixelLerp(colA, colB, intersection);

            float aDirection = directionFromLine(clipA, clipB, polygonA);
            float bDirection = directionFromLine(clipA, clipB, polygonB);

            if (bDirection <= 0) {
                if (aDirection > 0) {
                    outputList.push_back(intersectionPoint);
                    outputUvs.push_back(intersectionUv);
                    outputWs.push_back(intersectionW);
                    outputCols.push_back(intersectionCol);
                }
                outputList.push_back(polygonB);
                outputUvs.push_back(uvB);
                outputWs.push_back(Wb);
                outputCols.push_back(colB);
            } else if (aDirection <= 0) {
                outputList.push_back(intersectionPoint);
                outputUvs.push_back(intersectionUv);
                outputWs.push_back(intersectionW);
                outputCols.push_back(intersectionCol);
            }
        }
    }

    if (outputList.size() == 0) {
        return;
    }

    render:

    for (auto &point : outputList) {
        point += offset;
    }

    pge->DrawExplicitDecal(decal,
                           outputList.data(),
                           outputUvs.data(),
                           outputCols.data(),
                           outputWs.data(),
                           outputList.size());
}

float olc::ViewPort::lineSegmentIntersect(vf2d lineA,
                                          vf2d lineB,
                                          vf2d segmentA,
                                          vf2d segmentB) {
    return ((lineA.x - segmentA.x) * (lineA.y - lineB.y)
            - (lineA.y - segmentA.y) * (lineA.x - lineB.x))
           / ((lineA.x - lineB.x) * (segmentA.y - segmentB.y)
              - (lineA.y - lineB.y) * (segmentA.x - segmentB.x));
}

float olc::ViewPort::directionFromLine(vf2d lineA, vf2d lineB, vf2d point) {
    return (lineB.x - lineA.x) * (point.y - lineA.y)
           - (point.x - lineA.x) * (lineB.y - lineA.y);
}

void olc::ViewPort::DrawStringDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const olc::vf2d& scale, const float width,const bool disableDynamicScaling){
	if(sText.length()==0)return;
	std::string key{"DSD_"+std::string(pge->stripCol(sText))};
    key+=std::to_string(width);
    if(!disableDynamicScaling){
        key+=scale.str();
    }
	if(!pge->garbageCollector.count(key)||pge->garbageCollector[key].originalStr!=sText){ //If the text key already exists, don't have to recreate the decal, just update the expire time.
		vi2d imageSize=pge->GetWrappedTextSize(sText,width,scale);
		if(imageSize.x<1||imageSize.y<1)return;
		Decal*newDecal=nullptr;
		if(!pge->garbageCollector.count(key)){
			newDecal=new Decal(new Sprite(imageSize.x/scale.x,imageSize.y/scale.x));
			pge->garbageCollector[key].decal=newDecal;
		}else{
			newDecal=pge->garbageCollector[key].decal;
		}
		pge->garbageCollector[key].originalStr=sText;
		pge->SetDrawTarget(newDecal->sprite);
		pge->Clear(BLANK);
		pge->DrawString({0,0},sText,WHITE,1U,width/scale.x);
		pge->SetDrawTarget(nullptr);
		newDecal->Update();
	}
	pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
    DrawDecal(pos,pge->garbageCollector[key].decal,scale,col);
}

void olc::ViewPort::DrawStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col, const olc::vf2d& scale){
	if(sText.length()==0)return;
	std::u32string Ukey=U"DSD_"+font.GetFontName()+U"_"+pge->stripCol(sText);
	std::string key=std::string(Ukey.begin(),Ukey.end());
	if(!pge->garbageCollector.count(key)||pge->garbageCollector[key].originalStr!=std::string(sText.begin(),sText.end())){ //If the text key already exists, don't have to recreate the decal, just update the expire time.
		if(pge->garbageCollector.count(key)){
			delete pge->garbageCollector[key].decal;
		}
		pge->garbageCollector[key].decal=font.RenderStringToDecal(sText,WHITE);
		pge->garbageCollector[key].originalStr=std::string(sText.begin(),sText.end());
	}
	pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
	DrawDecal(pos,pge->garbageCollector[key].decal,scale/4,col);
}

void olc::ViewPort::DrawStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const olc::vf2d& scale,const float width,const bool disableDynamicScaling){
	if(sText.length()==0)return;
	std::string key{"DSPD"+std::string(pge->stripCol(sText))};
    key+=std::to_string(width);
    if(!disableDynamicScaling){
        key+=scale.str();
    }
	if(!pge->garbageCollector.count(key)||pge->garbageCollector[key].originalStr!=sText){ //If the text key already exists, don't have to recreate the decal, just update the expire time.
		vi2d imageSize=pge->GetWrappedTextSizeProp(sText,width,scale);
		if(imageSize.x<1||imageSize.y<1)return;
		Decal*newDecal=nullptr;
		if(!pge->garbageCollector.count(key)){
			newDecal=new Decal(new Sprite(imageSize.x/scale.x,imageSize.y/scale.x));
			pge->garbageCollector[key].decal=newDecal;
		}else{
			newDecal=pge->garbageCollector[key].decal;
		}
		pge->garbageCollector[key].originalStr=sText;
		pge->SetDrawTarget(newDecal->sprite);
		pge->Clear(BLANK);
		pge->DrawStringProp({0,0},sText,WHITE,1U,width/scale.x);
		pge->SetDrawTarget(nullptr);
		newDecal->Update();
	}
	pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
    DrawDecal(pos,pge->garbageCollector[key].decal,scale,col);
}

void olc::ViewPort::DrawShadowStringDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const float width,const float shadowSizeFactor,const bool disableDynamicScaling){
	if(sText.length()==0)return;
	std::string key{"DSSD_"+std::string(pge->stripCol(sText))};
	key+=std::to_string(width);
	if(!disableDynamicScaling){
		key+=scale.str();
	}
	if(!pge->garbageCollector.count(key)||pge->garbageCollector[key].originalStr!=sText){ //If the text key already exists, don't have to recreate the decal, just update the expire time.
		vi2d imageSize=pge->GetWrappedTextSize(sText,width,scale);
		if(imageSize.x<1||imageSize.y<1)return;
		Decal*newDecal=nullptr;
		if(!pge->garbageCollector.count(key)){
			newDecal=new Decal(new Sprite(imageSize.x/scale.x,imageSize.x/scale.x));
			pge->garbageCollector[key].decal=newDecal;
		}else{
			newDecal=pge->garbageCollector[key].decal;
		}
		pge->garbageCollector[key].originalStr=sText;
		pge->SetDrawTarget(newDecal->sprite);
		pge->Clear(BLANK);
		pge->DrawString({0,0},sText,WHITE,1U,width/scale.x);
		newDecal->Update();
        vf2d adjustedShadowSizeFactor=vf2d{shadowSizeFactor,shadowSizeFactor}*4/scale;
		Decal*newShadowDecal=nullptr;
		if(!pge->garbageCollector.count(key+"_SHADOW")){
			newShadowDecal=new Decal(new Sprite((imageSize.x/scale.x*4)+adjustedShadowSizeFactor.x*2,(imageSize.x/scale.x*4)+adjustedShadowSizeFactor.y*2));
			pge->garbageCollector[key+"_SHADOW"].decal=newShadowDecal;
		}else{
			newShadowDecal=pge->garbageCollector[key+"_SHADOW"].decal;
		}
		pge->SetDrawTarget(newShadowDecal->sprite);
		pge->Clear(BLANK);
		for(float y=-adjustedShadowSizeFactor.y;y<=adjustedShadowSizeFactor.y+0.1;y+=adjustedShadowSizeFactor.y/2){
			for(float x=-adjustedShadowSizeFactor.x;x<=adjustedShadowSizeFactor.x+0.1;x+=adjustedShadowSizeFactor.x/2){
				if(x!=0||y!=0){
					pge->DrawString(vf2d{x,y}+adjustedShadowSizeFactor, sText, WHITE,4U,width/scale.x*4);
				}
			}
		}
		pge->SetDrawTarget(nullptr);
		newShadowDecal->Update();
	}
	pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
	pge->garbageCollector[key+"_SHADOW"].expireTime=pge->GetRuntime()+120.0f;
	DrawDecal(pos-vf2d{shadowSizeFactor,shadowSizeFactor},pge->garbageCollector[key+"_SHADOW"].decal,scale/4,shadowCol);
	DrawDecal(pos,pge->garbageCollector[key].decal,scale,col);
}

void olc::ViewPort::DrawShadowStringPropDecal(const olc::vf2d& pos, std::string_view sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const float width,const float shadowSizeFactor,const bool disableDynamicScaling){
	if(sText.length()==0)return;
	std::string key{"DSSPD"+std::string(pge->stripCol(sText))};
	key+=std::to_string(width);
	if(!disableDynamicScaling){
		key+=scale.str();
	}
	if(!pge->garbageCollector.count(key)||pge->garbageCollector[key].originalStr!=sText){ //If the text key already exists, don't have to recreate the decal, just update the expire time.
		vi2d imageSize=pge->GetWrappedTextSizeProp(sText,width,scale);
		if(imageSize.x<1||imageSize.y<1)return;
		Decal*newDecal=nullptr;
		if(!pge->garbageCollector.count(key)){
			newDecal=new Decal(new Sprite(imageSize.x/scale.x,imageSize.x/scale.x));
			pge->garbageCollector[key].decal=newDecal;
		}else{
			newDecal=pge->garbageCollector[key].decal;
		}
		pge->garbageCollector[key].originalStr=sText;
		pge->SetDrawTarget(newDecal->sprite);
		pge->Clear(BLANK);
		pge->DrawStringProp({0,0},sText,WHITE,1U,width/scale.x);
		newDecal->Update();
        vf2d adjustedShadowSizeFactor=vf2d{shadowSizeFactor,shadowSizeFactor}*4/scale;
		Decal*newShadowDecal=nullptr;
		if(!pge->garbageCollector.count(key+"_SHADOW")){
			newShadowDecal=new Decal(new Sprite((imageSize.x/scale.x*4)+adjustedShadowSizeFactor.x*2,(imageSize.x/scale.x*4)+adjustedShadowSizeFactor.y*2));
			pge->garbageCollector[key+"_SHADOW"].decal=newShadowDecal;
		}else{
			newShadowDecal=pge->garbageCollector[key+"_SHADOW"].decal;
		}
		pge->SetDrawTarget(newShadowDecal->sprite);
		pge->Clear(BLANK);
		for(float y=-adjustedShadowSizeFactor.y;y<=adjustedShadowSizeFactor.y+0.1;y+=adjustedShadowSizeFactor.y/2){
			for(float x=-adjustedShadowSizeFactor.x;x<=adjustedShadowSizeFactor.x+0.1;x+=adjustedShadowSizeFactor.x/2){
				if(x!=0||y!=0){
					pge->DrawStringProp(vf2d{x,y}+adjustedShadowSizeFactor, sText, WHITE,4U,width/scale.x*4);
				}
			}
		}
		pge->SetDrawTarget(nullptr);
		newShadowDecal->Update();
	}
	pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
	pge->garbageCollector[key+"_SHADOW"].expireTime=pge->GetRuntime()+120.0f;
	DrawDecal(pos-vf2d{shadowSizeFactor,shadowSizeFactor},pge->garbageCollector[key+"_SHADOW"].decal,scale/4,shadowCol);
	DrawDecal(pos,pge->garbageCollector[key].decal,scale,col);
}

void olc::ViewPort::DrawShadowStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale,const float shadowSizeFactor){
	if(sText.length()==0)return;
	std::u32string Ukey=U"DSSD_"+font.GetFontName()+U"_"+pge->stripCol(sText);
	std::string key=std::string(Ukey.begin(),Ukey.end());
	if(!pge->garbageCollector.count(key)||pge->garbageCollector[key].originalStr!=std::string(sText.begin(),sText.end())){ //If the text key already exists, don't have to recreate the decal, just update the expire time.
		if(pge->garbageCollector.count(key)){
			delete pge->garbageCollector[key].decal;
		}
		pge->garbageCollector[key].decal=font.RenderStringToDecal(sText,WHITE);
		pge->garbageCollector[key].originalStr=std::string(sText.begin(),sText.end());
	}
	pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
	std::erase_if(pge->garbageCollector,[&](auto&key){
		if(key.second.expireTime<pge->GetRuntime()){
			delete key.second.decal;
			return true;
		}
		return false;
	});
	for(float y=-shadowSizeFactor;y<=shadowSizeFactor+0.1;y+=shadowSizeFactor/2){
		for(float x=-shadowSizeFactor;x<=shadowSizeFactor+0.1;x+=shadowSizeFactor/2){
			if(x!=0||y!=0){
				DrawDecal(pos+vf2d{x,y},pge->garbageCollector[key].decal,scale/4,shadowCol);
			}
		}
	}
	DrawDecal(pos,pge->garbageCollector[key].decal,scale/4,col);
}

void olc::ViewPort::DrawDropShadowStringDecal(Font&font, const olc::vf2d& pos, const std::u32string& sText, const Pixel col, const Pixel shadowCol, const olc::vf2d& scale){
	if(sText.length()==0)return;
	std::u32string Ukey=U"DDSSD_"+font.GetFontName()+U"_"+pge->stripCol(sText);
	std::string key=std::string(Ukey.begin(),Ukey.end());
	if(!pge->garbageCollector.count(key)||pge->garbageCollector[key].originalStr!=std::string(sText.begin(),sText.end())){ //If the text key already exists, don't have to recreate the decal, just update the expire time.
		if(pge->garbageCollector.count(key)){
			delete pge->garbageCollector[key].decal;
		}
		pge->garbageCollector[key].decal=font.RenderStringToDecal(sText,WHITE);
		pge->garbageCollector[key].originalStr=std::string(sText.begin(),sText.end());
	}
	pge->garbageCollector[key].expireTime=pge->GetRuntime()+120.0f;
	DrawDecal(pos+vf2d{0,0.5f},pge->garbageCollector[key].decal,scale/4,shadowCol);
	DrawDecal(pos+vf2d{0.5f,0},pge->garbageCollector[key].decal,scale/4,shadowCol);
	DrawDecal(pos+vf2d{0.5f,0.5f},pge->garbageCollector[key].decal,scale/4,shadowCol);
	DrawDecal(pos,pge->garbageCollector[key].decal,scale/4,col);
}

void olc::ViewPort::DrawRectDecal(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col)const{
    FillRectDecal(pos,{size.x+1,1},col);
    FillRectDecal(pos+vf2d{0,size.y-1+1},{size.x+1,1},col);
    FillRectDecal(pos+vf2d{0,1},{1,size.y-1*2+1},col);
    FillRectDecal(pos+vf2d{size.x-1+1,1},{1,size.y-1*2+1},col);
}

#endif