/*
 * Licensed under BSD license.  See LICENCE.TXT  
 *
 * Produced by:	Jeff Lait
 *
 *      	1812 Development
 *
 * NAME:        texture.cpp ( Jacob's Matrix, C++ )
 *
 * COMMENTS:
 *		Shared texture map.  Supports COW mechanics.
 */

#pragma once

#include "mygba.h"
#include <memory>

template <typename T>
class TEXTURE_T
{
public:
    TEXTURE_T<T>()
    {
	reset();
    }
    explicit TEXTURE_T<T>(int w, int h)
    {
	calloc(w, h);
    }

    void	reset()
    {
	myData.reset();
	myWidth = myHeight = 0;
	myConstantVal = T{};
    }

    void	calloc(int w, int h)
    {
	myData.reset();
	memset((char *) &myConstantVal, 0, sizeof(T));

	myWidth = w;
	myHeight = h;
    }

    // Does *not* clear if it is same size!
    void	resize(int w, int h)
    {
	if (width() != w || height() != h)
	{
	    calloc(w, h);
	}
    }

    int		height() const { return myHeight; }
    int		width() const { return myWidth; }

    T		get(int x, int y) const
    {
	J_ASSERT(x >= 0 && x < width());
	J_ASSERT(y >= 0 && y < height());

	if (!myData)
	    return myConstantVal;
	return myData.get()[x + y * width()];
    }
    T		wrapget(int x, int y) const
    {
	wrap(x, y);
	return get(x, y);
    }
    void		wrap(int &x, int &y) const
    { x = x % width();  if (x < 0) x += width();
      y = y % height(); if (y < 0) y += height(); }
    T		operator()(int x, int y) const { return get(x, y); }
    T		&operator()(int x, int y)
    {
	J_ASSERT(isUnique());
	densify();
	J_ASSERT(x >= 0 && x < width());
	J_ASSERT(y >= 0 && y < height());
	return myData.get()[x + y * width()];
    }
    void	set(int x, int y, T val)
    {
	J_ASSERT(isUnique());
	densify();
	J_ASSERT(x >= 0 && x < width());
	J_ASSERT(y >= 0 && y < height());
	myData.get()[x + y * width()] = val;
    }

    void	clear(T val)
    {
	J_ASSERT(isUnique());

	myConstantVal = val;
	myData.reset();
    }

    bool	isUnique() const
    {
	return !myData || (myData.use_count() == 1);
    }
    void	makeUnique()
    {
	if (!myData)
	{
	    // COnstant, so trivially unique.
	    return;
	}
	J_ASSERT(myData);
	if (!isUnique())
	{
	    std::shared_ptr<T>	newdata = std::shared_ptr<T>(new T[width()*height()]);
	    memcpy(newdata.get(), myData.get(), sizeof(T) * width() * height());
	    myData = newdata;
	    newdata.reset();

	    J_ASSERT(isUnique());
	}
    }
    void	densify()
    {
	if (!myData)
	{
	    int		w = width();
	    int		h = height();

	    if (!w || !h)
		return;

	    myData = std::shared_ptr<T>(new T[w * h]);
	    bool		constantbyte = true;
	    u8			*rawcval = (u8*)&myConstantVal;
	    u8			 cvalbyte = *rawcval;

	    for (int i = 1; i < sizeof(T); i++)
	    {
		if (rawcval[i] != cvalbyte)
		    constantbyte = false;
	    }
	    // Allow filling of -1 and 0
	    if (constantbyte)
	    {
		memset((char *) myData.get(), cvalbyte, sizeof(T) * w * h);
	    }
	    else
	    {
		for (int i = 0; i < w*h; i++)
		    myData.get()[i] = myConstantVal;
	    }
	}
	// Already dense.
    }

    const T 	*data() const
    {
	const_cast<TEXTURE_T<T>*>(this)->densify();
	return myData.get();
    }

    T 	*writeableData()
    {
	J_ASSERT(isUnique());
	densify();
	return myData.get();
    }

protected:
    // Null means constant.
    std::shared_ptr<T>	myData;

    T			myConstantVal;

    int			myWidth, myHeight;
};

using TEXTURE8 = TEXTURE_T<u8>;
using TEXTURE16 = TEXTURE_T<u16>;
using TEXTUREI = TEXTURE_T<int>;
using TEXTUREF = TEXTURE_T<float>;

