#ifndef __MODEL_SHAPE_H__
#define __MODEL_SHAPE_H__

#include <deque>
#include <string>
#include <iterator> //for back_inserter

#include <foundation/Math/Point.hpp>

#include "foundation/SharedArray.h"

namespace Tn {
    
namespace openGLMap {
    
//Assumed to be RGBA for now
class ClientTexture
{
public:
    ClientTexture(Tn::Foundation::shared_array<uint8_t> const & data_,
                  int width_, int height_) 
    : data(data_),
      width(width_),
      height(height_)
    {
    }
    
    ClientTexture() : width(0), height(0) {}
    
    Tn::Foundation::shared_array<uint8_t> const & Data() const { return data; }
    int Width()  const { return width; }
    int Height() const { return height; }
private:
    Tn::Foundation::shared_array<uint8_t> data;
    int width, height;
};
    
class ShapeAttributes
{
public:
    ShapeAttributes(std::string const & style) : shapeStyle(style),
        colorR(255), colorG(255), colorB(255), colorA(255), colored(false), 
        lineWidth(1.0f), textureResourceWidth(64), textureResourceHeight(64),
        stretchFactor(1.0f),
        patterned(false),
        useLinearTextureFiltering(true)
    {}
    
    //also all the setters/getters here... color, blah, blah, blah
    ShapeAttributes & SetColor(uint8_t rByte, uint8_t gByte, uint8_t bByte, uint8_t aByte = 255)
    {
        colored = true;//book-keeping to know a color has been set
        colorR = rByte; colorG = gByte; colorB = bByte; colorA = aByte;
        return *this;
    }
    
    ShapeAttributes & SetAlpha(uint8_t aByte)
    {
        colored = true;
        colorA  = aByte;
        return *this;
    }
    
    ShapeAttributes & SetTextureName(std::string const & textureName)
    {
        textureResourceName = textureName;
        return *this;
    }
    
	ShapeAttributes & SetTextureWidth(uint32_t const & textureWidth)
	{
		textureResourceWidth = textureWidth;
		return *this;
	}

	ShapeAttributes & SetTextureHeight(uint32_t const & textureHeight)
	{
		textureResourceHeight = textureHeight;
		return *this;
	}

    ShapeAttributes & SetLineWidth(float lineWidth_)
    {
        lineWidth = lineWidth_;
        return *this;
    }
    
    ShapeAttributes & SetStretchFactor(float stretchFactor_)
    {
        stretchFactor = stretchFactor_;
        return *this;
    }

    ShapeAttributes & SetClientTexture(ClientTexture const & texture)
    {
        clientTexture = texture;
        return *this;
    }
    
    ShapeAttributes & SetPatterned(bool isPatterned)
    {
        patterned = isPatterned;
        return *this;
    }
    
    ShapeAttributes & SetLinearTextureFiltering(bool enable)
    {
        useLinearTextureFiltering = enable;
        return *this;
    }
    
    ClientTexture const & GetClientTexture() const { return clientTexture; }
    
    //Should point to a loaded json configuration
    std::string const & Style() const { return shapeStyle; }
    
    uint8_t RedColor()   const { return colorR; }
    uint8_t GreenColor() const { return colorG; }
    uint8_t BlueColor()  const { return colorB; }
    uint8_t AlphaColor() const { return colorA; }
    
    bool Colored() const  { return colored; }
    
    bool ClientTextured() const { return clientTexture.Data().get(); }
    bool Textured() const { return !textureResourceName.empty() || ClientTextured(); }
    
    std::string const & TextureName() const { return textureResourceName; }
	uint32_t TextureWidth() const { return !textureResourceName.empty() ? textureResourceWidth
                                                                        : clientTexture.Width(); }
	uint32_t TextureHeight() const { return !textureResourceName.empty() ? textureResourceHeight
                                                                         : clientTexture.Height(); }
    float LineWidth() const { return lineWidth; }
    
    float StretchFactor() const { return stretchFactor; }
    
    bool Patterned() const { return patterned; }
    
    bool UseLinearTextureFiltering() const { return useLinearTextureFiltering; }

private:
    std::string const shapeStyle;
    
    uint8_t colorR, colorG, colorB, colorA;
    bool colored;

    float lineWidth;
    
    //We'll either use this...
    std::string textureResourceName;
    uint32_t textureResourceWidth;
	uint32_t textureResourceHeight;
    float stretchFactor;

    //or this
    ClientTexture clientTexture;
    
    bool patterned;
    
    bool useLinearTextureFiltering; //Turn this off to use "Nearest" texture sampling (as opposed to the default of "Linear")
};
    
enum eCoordType
{
    eCoordType_LAT_LON,
    eCoordType_LAT_LON_ALT_METERS,
    eCoordType_LAT_LON_DELTA_METERS,
    eCoordType_X_Y_Z_METERS,
    eCoordType_UNDEFINED
};
    
struct LatLonAlt
{
    LatLonAlt(double latitude, double longitude, double altitudeMeters = 0.0) :
    latitude(latitude), longitude(longitude), altitudeMeters(altitudeMeters) {}
    
    double latitude, longitude, altitudeMeters;
    static eCoordType CoordinateType() { return eCoordType_LAT_LON_ALT_METERS; }
};
    
struct LatLonDelta
{
    double latitude, longitude, aboveGroundMeters;
    static eCoordType CoordinateType() { return eCoordType_LAT_LON_DELTA_METERS; }
};
    
struct XYZMetered
{
    double x,y,z;
    static eCoordType CoordinateType() { return eCoordType_X_Y_Z_METERS; }
};
    
enum eShapeType
{
    eShapeType_POLYLINE,
    eShapeType_POLYGON,
    eShapeType_TEXTURED_QUAD
};
        
template <typename COORD>
class Shape
{
public:
    
    virtual ~Shape() {}
    
    //so we can switch containers more easily
    //currently a deque of 3d float points
    typedef std::deque< COORD > CoordinateContainer;
    
    /*void AddCoordinate(COORD const & coord)
    {
        m_rawCoords.push_back(coord);
    }*/
    
    //Declared virtual so it can be made private for some shapes
    virtual void SetCoordinates(CoordinateContainer const & coords)
    {
        //Clear container in case of old data
        m_rawCoords.clear();
        
        std::copy(coords.begin(), coords.end(), std::back_inserter(m_rawCoords));
        //Fast copy, only if container is vector
        //memcpy(&m_rawCoords[0], &coords[0], coords.size());
    }
    
    //Access vertices for building/tesselating/etc...
    CoordinateContainer const & RawCoordinates() const 
    {
        return m_rawCoords;
    }
    
    //Access shape attributes
    ShapeAttributes const & Attributes() const
    {
        return m_attributes;
    }
    
    virtual eShapeType Type() const = 0;
    
protected:
    Shape(ShapeAttributes const & attributes) : m_attributes(attributes) {}
    
private: //No copy
    Shape& operator = (const Shape& other);
	Shape (const Shape & other);            
    
protected:
    
    CoordinateContainer m_rawCoords;
    
    //Descriptor for shape
    ShapeAttributes m_attributes;
};
    
}// namespace openGLMap
    
}// namespace Tn

#endif // __MODEL_SHAPE_H__

