#include "Image.h"

#include "Color.h"
#include "Grid.h"

#ifdef _WIN32
#include "Win32WicImage.h"
#endif

Image::Image(unsigned width, unsigned height, DataType dataType)
    :   mWidth(width),
        mHeight(height),
        mDataType(dataType)
{
    initialize();
}

Image::~Image()
{

}

std::unique_ptr<Image> Image::Create(unsigned width, unsigned height, DataType dataType)
{
    return std::make_unique<Image>(width, height, dataType);
}

void Image::initialize()
{
    Bounds bounds(0.0, mWidth, 0.0, mHeight);
    if (mDataType == DataType::UCHAR)
    {
        mData = std::make_unique<Grid<unsigned char> >(bounds, mWidth, mHeight, mNumChannels);
    }
    else
    {
        mData = std::make_unique<Grid<uint8_t> >(bounds, mWidth, mHeight, mNumChannels);
    }
}

void Image::setPixelValues(const Indices& indices, const std::vector<Color>& colors)
{
    if (!mData)
    {
        initialize();
    }

    if (mDataType == DataType::UCHAR)
    {
        auto grid = getGridT<unsigned char>();
        for (std::size_t idx=0; idx< indices.size(); idx++)
        {
            auto id = indices[idx];
            auto color = colors[idx];
            grid->setItem(id.first, id.second, 0, color.getR());
            grid->setItem(id.first, id.second, 1, color.getG());
            grid->setItem(id.first, id.second, 2, color.getB());
        }
    }
}

unsigned Image::getBytesPerRow() const
{
    const auto bitsPerEntry = mBitDepth <= 8 ? 1 : 2;
    return mWidth * mNumChannels * bitsPerEntry;
}

unsigned Image::getWidth() const
{
    return mWidth;
}

unsigned Image::getHeight() const
{
    return mHeight;
}

unsigned Image::getBitDepth() const
{
    return mBitDepth;
}

PlatformImage* Image::getPlatformImage()
{
    if (!mPlatformImage)
    {
#ifdef _WIN32
        mPlatformImage = std::make_unique<Win32WicImage>(this);
#endif
    }
    return mPlatformImage.get();
}

AbstractGrid* Image::getGrid() const
{
    return mData.get();
}

template<typename T>
Grid<T>* Image::getGridT() const
{
    return dynamic_cast<Grid<T>*>(this->mData.get());
}

Image::DataType Image::getType() const
{
    return mDataType;
}

unsigned Image::getNumChannels() const
{
    return mNumChannels;
}

void Image::setWidth(unsigned width)
{
    mWidth = width;
}

void Image::setHeight(unsigned height)
{
    mHeight = height;
}

void Image::setBitDepth(unsigned bitDepth)
{
    mBitDepth = bitDepth;
}

void Image::setNumChannels(unsigned numChannels)
{
    mNumChannels = numChannels;
}

