// Copyright (C) 2003-2009 Dolphin Project.

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/


#include "TextureSampler.h"

#include "BPMemLoader.h"
#include "TextureDecoder.h"
#include "HW/Memmap.h"

#include <cmath>

#define ALLOW_MIPMAP 1

namespace TextureSampler
{

inline void WrapCoord(int &coord, int wrapMode, int imageSize)
{
    switch (wrapMode)
    {
        case 0: // clamp
            coord = (coord>imageSize)?imageSize:(coord<0)?0:coord;
            break;
        case 1: // wrap
            coord = coord % (imageSize + 1);
            coord = (coord<0)?imageSize+coord:coord;
            break;
        case 2: // mirror
            {
                int sizePlus1 = imageSize + 1;
                int div = coord / sizePlus1;
                coord = coord - (div * sizePlus1);
                coord = (coord<0)?-coord:coord;
                coord = (div&1)?imageSize - coord:coord;
            }
            break;
    }
}

inline void SetTexel(u8 *inTexel, u32 *outTexel, u32 fract)
{
    outTexel[0] = inTexel[0] * fract;
    outTexel[1] = inTexel[1] * fract;
    outTexel[2] = inTexel[2] * fract;
    outTexel[3] = inTexel[3] * fract;
}

inline void AddTexel(u8 *inTexel, u32 *outTexel, u32 fract)
{
    outTexel[0] += inTexel[0] * fract;
    outTexel[1] += inTexel[1] * fract;
    outTexel[2] += inTexel[2] * fract;
    outTexel[3] += inTexel[3] * fract;
}

void Sample(s32 s, s32 t, s32 lod, bool linear, u8 texmap, u8 *sample)
{
    int baseMip = 0;
	bool mipLinear = false;

#if (ALLOW_MIPMAP)
	FourTexUnits& texUnit = bpmem.tex[(texmap >> 2) & 1];
    TexMode0& tm0 = texUnit.texMode0[texmap & 3];

	s32 lodFract = lod & 0xf;

	if (lod > 0 && tm0.min_filter & 3)
	{
		// use mipmap
		baseMip = lod >> 4;
		mipLinear = (lodFract && tm0.min_filter & 2);

		// if using nearest mip filter and lodFract >= 0.5 round up to next mip
		baseMip += (lodFract >> 3) & (tm0.min_filter & 1);
	}

	if (mipLinear)
	{
		u8 sampledTex[4];
        u32 texel[4];

		SampleMip(s, t, baseMip, linear, texmap, sampledTex);
		SetTexel(sampledTex, texel, (16 - lodFract));

		SampleMip(s, t, baseMip + 1, linear, texmap, sampledTex);
		AddTexel(sampledTex, texel, lodFract);

		sample[0] = (u8)(texel[0] >> 4);
        sample[1] = (u8)(texel[1] >> 4);
        sample[2] = (u8)(texel[2] >> 4);
        sample[3] = (u8)(texel[3] >> 4);
	}
	else
#endif
	{
		SampleMip(s, t, baseMip, linear, texmap, sample);
	}	
}

void SampleMip(s32 s, s32 t, s32 mip, bool linear, u8 texmap, u8 *sample)
{
	FourTexUnits& texUnit = bpmem.tex[(texmap >> 2) & 1];
    u8 subTexmap = texmap & 3;

    TexMode0& tm0 = texUnit.texMode0[subTexmap];
    TexImage0& ti0 = texUnit.texImage0[subTexmap];
    TexTLUT& texTlut = texUnit.texTlut[subTexmap];

	u8 *imageSrc, *imageSrcOdd = NULL;
	if (texUnit.texImage1[subTexmap].image_type)
	{
		imageSrc = &texMem[texUnit.texImage1[subTexmap].tmem_even * TMEM_LINE_SIZE];
		if (ti0.format == GX_TF_RGBA8)
			imageSrcOdd = &texMem[texUnit.texImage2[subTexmap].tmem_odd * TMEM_LINE_SIZE];
	}
	else
	{
		u32 imageBase = texUnit.texImage3[subTexmap].image_base << 5;    
		imageSrc = Memory::GetPointer(imageBase);
	}

	int imageWidth = ti0.width;
	int imageHeight = ti0.height;

	int tlutAddress = texTlut.tmem_offset << 9;
	
	// reduce sample location and texture size to mip level
	// move texture pointer to mip location
	if (mip)
	{
		int mipWidth = imageWidth + 1;
		int mipHeight = imageHeight + 1;

		int fmtWidth = TexDecoder_GetBlockWidthInTexels(ti0.format);
		int fmtHeight = TexDecoder_GetBlockHeightInTexels(ti0.format);
		int fmtDepth = TexDecoder_GetTexelSizeInNibbles(ti0.format);

		imageWidth >>= mip;
		imageHeight >>= mip;
		s >>= mip;
		t >>= mip;

		while (mip)
		{
			mipWidth = max(mipWidth, fmtWidth);
			mipHeight = max(mipHeight, fmtHeight);
			u32 size = (mipWidth * mipHeight * fmtDepth) >> 1;

			imageSrc += size;
			mipWidth >>= 1;
			mipHeight >>= 1;
			mip--;
		}
	}

    if (linear)
    {
		// offset linear sampling
		s -= 64;
		t -= 64;

		// integer part of sample location
		int imageS = s >> 7;
		int imageT = t >> 7;

		// linear sampling
		int imageSPlus1 = imageS + 1;
        int fractS = s & 0x7f;
        
        int imageTPlus1 = imageT + 1;
        int fractT = t & 0x7f;

        u8 sampledTex[4];
        u32 texel[4];

        WrapCoord(imageS, tm0.wrap_s, imageWidth);
        WrapCoord(imageT, tm0.wrap_t, imageHeight);
        WrapCoord(imageSPlus1, tm0.wrap_s, imageWidth);
        WrapCoord(imageTPlus1, tm0.wrap_t, imageHeight);

		if (!(ti0.format == GX_TF_RGBA8 && texUnit.texImage1[subTexmap].image_type))
		{
			TexDecoder_DecodeTexel(sampledTex, imageSrc, imageS, imageT, imageWidth, ti0.format, tlutAddress, texTlut.tlut_format);
			SetTexel(sampledTex, texel, (128 - fractS) * (128 - fractT));

			TexDecoder_DecodeTexel(sampledTex, imageSrc, imageSPlus1, imageT, imageWidth, ti0.format, tlutAddress, texTlut.tlut_format);
			AddTexel(sampledTex, texel, (fractS) * (128 - fractT));

			TexDecoder_DecodeTexel(sampledTex, imageSrc, imageS, imageTPlus1, imageWidth, ti0.format, tlutAddress, texTlut.tlut_format);
			AddTexel(sampledTex, texel, (128 - fractS) * (fractT));

			TexDecoder_DecodeTexel(sampledTex, imageSrc, imageSPlus1, imageTPlus1, imageWidth, ti0.format, tlutAddress, texTlut.tlut_format);
			AddTexel(sampledTex, texel, (fractS) * (fractT));
		}
		else
		{
			TexDecoder_DecodeTexelRGBA8FromTmem(sampledTex, imageSrc, imageSrcOdd, imageS, imageT, imageWidth);
			SetTexel(sampledTex, texel, (128 - fractS) * (128 - fractT));

			TexDecoder_DecodeTexelRGBA8FromTmem(sampledTex, imageSrc, imageSrcOdd, imageSPlus1, imageT, imageWidth);
			AddTexel(sampledTex, texel, (fractS) * (128 - fractT));

			TexDecoder_DecodeTexelRGBA8FromTmem(sampledTex, imageSrc, imageSrcOdd, imageS, imageTPlus1, imageWidth);
			AddTexel(sampledTex, texel, (128 - fractS) * (fractT));

			TexDecoder_DecodeTexelRGBA8FromTmem(sampledTex, imageSrc, imageSrcOdd, imageSPlus1, imageTPlus1, imageWidth);
			AddTexel(sampledTex, texel, (fractS) * (fractT));
		}

        sample[0] = (u8)(texel[0] >> 14);
        sample[1] = (u8)(texel[1] >> 14);
        sample[2] = (u8)(texel[2] >> 14);
        sample[3] = (u8)(texel[3] >> 14);
    }
    else
    {
		// integer part of sample location
		int imageS = s >> 7;
		int imageT = t >> 7;

        // nearest neighbor sampling
		WrapCoord(imageS, tm0.wrap_s, imageWidth);
        WrapCoord(imageT, tm0.wrap_t, imageHeight);

		if (!(ti0.format == GX_TF_RGBA8 && texUnit.texImage1[subTexmap].image_type))
			TexDecoder_DecodeTexel(sample, imageSrc, imageS, imageT, imageWidth, ti0.format, tlutAddress, texTlut.tlut_format);   
		else
			TexDecoder_DecodeTexelRGBA8FromTmem(sample, imageSrc, imageSrcOdd, imageS, imageT, imageWidth);   
    }
}

}