/**
 * =========================================================================
 * File        : AlphaMapCalculator.cp
 * Project     : 0 A.D.
 * Description : Determine which alpha blend map fits a given shape.
 * =========================================================================
 */

#include "precompiled.h"

#include "AlphaMapCalculator.h"
#include <string.h>
#include <stdio.h>

///////////////////////////////////////////////////////////////////////////////
// CAlphaMapCalculator: functionality for calculating which alpha blend map
// fits a given shape
namespace CAlphaMapCalculator { 

///////////////////////////////////////////////////////////////////////////////
// Blend4: structure mapping a blend shape for N,E,S,W to a particular map
struct Blend4 {
    Blend4(BlendShape4 shape,int alphamap) : m_Shape(shape), m_AlphaMap(alphamap) {}

    BlendShape4 m_Shape;
    int m_AlphaMap;
};

///////////////////////////////////////////////////////////////////////////////
// Blend8: structure mapping a blend shape for N,NE,E,SE,S,SW,W,NW to a 
// particular map
struct Blend8 {
    Blend8(BlendShape8 shape,int alphamap) : m_Shape(shape), m_AlphaMap(alphamap) {}

    BlendShape8 m_Shape;
    int m_AlphaMap;
};

///////////////////////////////////////////////////////////////////////////////
// Data tables for mapping between shapes and blend maps 
///////////////////////////////////////////////////////////////////////////////

const Blend4 Blends1Neighbour[] = 
{ 
    Blend4(BlendShape4(1,0,0,0), 12)
};


const Blend4 Blends2Neighbour[] = 
{ 
    Blend4(BlendShape4(0,1,1,0), 7), 
    Blend4(BlendShape4(1,0,1,0), 10) 
};

const Blend8 Blends2Neighbour8[] = 
{ 
    Blend8(BlendShape8(1,1,0,0,0,0,0,0), 12), 
    Blend8(BlendShape8(1,0,0,0,0,1,0,0), 12), 
    Blend8(BlendShape8(0,1,0,1,0,0,0,0), 0) ,
    Blend8(BlendShape8(0,1,0,0,0,1,0,0), 0) 
};

const Blend4 Blends3Neighbour[] = 
{ 
    Blend4(BlendShape4(1,1,1,0), 4) 
};

const Blend8 Blends3Neighbour8[] = 
{ 
    Blend8(BlendShape8(1,1,0,0,1,0,0,0), 10),
    Blend8(BlendShape8(1,1,0,0,0,0,0,1), 12),
    Blend8(BlendShape8(1,1,1,0,0,0,0,0), 1), 
    Blend8(BlendShape8(0,1,1,0,1,0,0,0), 7),
    Blend8(BlendShape8(0,0,1,0,1,0,1,0), 4),
    Blend8(BlendShape8(1,1,0,0,0,1,0,0), 12),
    Blend8(BlendShape8(1,1,0,1,0,0,0,0), 12),
    Blend8(BlendShape8(0,0,1,0,1,0,0,1), 7),
    Blend8(BlendShape8(1,0,0,1,0,1,0,0), 12),
    Blend8(BlendShape8(0,1,0,1,0,1,0,0), 0)
};

const Blend8 Blends4Neighbour8[] = 
{ 
    Blend8(BlendShape8(1,1,0,0,1,0,0,1), 10),
    Blend8(BlendShape8(1,1,0,1,1,0,0,0), 10),
    Blend8(BlendShape8(1,1,0,0,1,1,0,0), 10),
    Blend8(BlendShape8(1,1,0,1,0,0,0,1), 12),
    Blend8(BlendShape8(0,1,1,0,1,1,0,0), 7),
    Blend8(BlendShape8(1,1,1,1,0,0,0,0), 1),
    Blend8(BlendShape8(1,1,1,0,1,0,0,0), 3),
    Blend8(BlendShape8(0,0,1,0,1,1,0,1), 7),
    Blend8(BlendShape8(1,0,1,0,1,1,0,0), 4),
    Blend8(BlendShape8(1,1,1,0,0,1,0,0), 1),
    Blend8(BlendShape8(1,1,0,1,0,1,0,0), 12),
    Blend8(BlendShape8(0,1,0,1,0,1,0,1), 0)
};

const Blend8 Blends5Neighbour8[] = 
{ 
    Blend8(BlendShape8(1,1,1,1,1,0,0,0), 2),
    Blend8(BlendShape8(1,1,1,1,0,0,0,1), 1),
    Blend8(BlendShape8(1,1,1,0,1,0,0,1), 3),
    Blend8(BlendShape8(1,1,1,0,1,0,1,0), 11),
    Blend8(BlendShape8(1,1,1,0,0,1,0,1), 1),
    Blend8(BlendShape8(1,1,0,1,1,1,0,0), 10),
    Blend8(BlendShape8(1,1,1,0,1,1,0,0), 3),
    Blend8(BlendShape8(1,0,1,0,1,1,0,1), 4),
    Blend8(BlendShape8(1,1,0,1,0,1,0,1), 12),
    Blend8(BlendShape8(0,1,1,0,1,1,0,1), 7)
};

const Blend8 Blends6Neighbour8[] = 
{ 
    Blend8(BlendShape8(1,1,1,1,1,1,0,0), 2),
    Blend8(BlendShape8(1,1,1,1,1,0,1,0), 8),
    Blend8(BlendShape8(1,1,1,1,0,1,0,1), 1),
    Blend8(BlendShape8(1,1,1,0,1,1,1,0), 6),
    Blend8(BlendShape8(1,1,1,0,1,1,0,1), 3),
    Blend8(BlendShape8(1,1,0,1,1,1,0,1), 10)
};

const Blend8 Blends7Neighbour8[] = 
{ 
    Blend8(BlendShape8(1,1,1,1,1,1,0,1), 2),
    Blend8(BlendShape8(1,1,1,1,1,1,1,0), 9)
};

///////////////////////////////////////////////////////////////////////////////



///////////////////////////////////////////////////////////////////////////////
// MatchBlendShapeFlipped: test if the given shape can be made to fit the 
// template in either unflipped state, or by flipping the shape in U or V
template<class T>
bool MatchBlendShapeFlipped(const T& templateshape,const T& shape,unsigned int& flags)
{
    // test unrotated shape
    if (shape==templateshape) {
        return true;
    } 

    // test against shape flipped in U
    T tstShape;
    templateshape.FlipU(tstShape);
    if (shape==tstShape) {
        flags|=BLENDMAP_FLIPU;
        return true;
    } 

    // test against shape flipped in V
    templateshape.FlipV(tstShape);
    if (shape==tstShape) {
        flags|=BLENDMAP_FLIPV;
        return true;
    }
    
    // no joy; no match by flipping
    return false;
}

///////////////////////////////////////////////////////////////////////////////
// MatchBlendShape: try and find a matching blendmap, and the required flip/
// rotation flags, to fit the given shape to the template
template<class T>
int MatchBlendShape(const T& templateshape,const T& shape,unsigned int& flags)
{
    // try matching unrotated shape first using just flipping
    if (MatchBlendShapeFlipped(templateshape,shape,flags)) {
        return true;
    }

    // now try iterating through rotations of 90,180,270 degrees
    T tstShape;
    templateshape.Rotate90(tstShape);
    if (MatchBlendShapeFlipped(tstShape,shape,flags)) {
        // update flags - note if we've flipped in u or v, we need to rotate in 
        // the opposite direction
        flags|=flags ? BLENDMAP_ROTATE270 : BLENDMAP_ROTATE90;
        return true;
    }
    
    templateshape.Rotate180(tstShape);
    if (MatchBlendShapeFlipped(tstShape,shape,flags)) {
        flags|=BLENDMAP_ROTATE180;
        return true;
    }

    templateshape.Rotate270(tstShape);
    if (MatchBlendShapeFlipped(tstShape,shape,flags)) {
        // update flags - note if we've flipped in u or v, we need to rotate in 
        // the opposite direction
        flags|=flags ? BLENDMAP_ROTATE90 : BLENDMAP_ROTATE270;
        return true;
    }

    return false;
}

///////////////////////////////////////////////////////////////////////////////
// LookupBlend: find and return the blendmap fitting the given shape by 
// iterating through the given data table and testing each shape in flipped and
// rotated forms until a match is found
template<class S,class T>
int LookupBlend(int tableSize,const S* table,const T& shape,unsigned int& flags)
{
    // iterate through known blend shapes
    for (int b=0;b<tableSize;b++) {
        const S& blend=table[b];
        if (MatchBlendShape(blend.m_Shape,shape,flags)) {
            return blend.m_AlphaMap;
        }
    }

    // eh? shouldn't get here if we've correctly considered all possible cases; 
    // keep the compiler happy, and, while we're still debugging possible shapes, 
    // return bad blend to highlight suspect alphamap logic
    return 13;
}


///////////////////////////////////////////////////////////////////////////////
// Calculate: return the index of the blend map that fits the given shape,
// and the set of flip/rotation flags to get the shape correctly oriented
int Calculate(BlendShape8 shape,unsigned int& flags)
{   
    // assume we're not going to require flipping or rotating
    flags=0;

    // count number of neighbours
    int count=0;
    for (int i=0;i<8;i++) {
        if (shape[i]) count++;
    }

    if (count==0) {
        // no neighbours, just the centre tile has the given texture; use blend circle
        return 0;
    } else if (count==8) {
        // all neighbours have same texture; return code to signal no alphamap required
        return -1;
    } else {
        if (count<=4) {
            // check if we can consider this a BlendShape4 - ie are any of the diagonals (NE,SE,SW,NW) set?
            if (!shape[1] && !shape[3] && !shape[5] && !shape[7]) {
                // ok, build a BlendShape4 and use that
                BlendShape4 shape4;
                shape4[0]=shape[0];
                shape4[1]=shape[2];
                shape4[2]=shape[4];
                shape4[3]=shape[6];

                switch (count) {
                    case 1:
                        return LookupBlend(sizeof(Blends1Neighbour)/sizeof(Blend4),Blends1Neighbour,shape4,flags);      

                    case 2:
                        return LookupBlend(sizeof(Blends2Neighbour)/sizeof(Blend4),Blends2Neighbour,shape4,flags);      

                    case 3:
                        return LookupBlend(sizeof(Blends3Neighbour)/sizeof(Blend4),Blends3Neighbour,shape4,flags);      

                    case 4:
                        // N,S,E,W have same texture, NE,SE,SW,NW don't; use a blend 4 corners
                        return 5;
                }
            }
        }


        // we've got this far, so now we've got to consider the remaining choices, all containing 
        // diagonal elements
        switch (count) {
            case 1:
                // trivial case - just return a circle blend
                return 0;

            case 2:
                return LookupBlend(sizeof(Blends2Neighbour8)/sizeof(Blend8),Blends2Neighbour8,shape,flags);     

            case 3:
                return LookupBlend(sizeof(Blends3Neighbour8)/sizeof(Blend8),Blends3Neighbour8,shape,flags);     

            case 4:
                return LookupBlend(sizeof(Blends4Neighbour8)/sizeof(Blend8),Blends4Neighbour8,shape,flags);     

            case 5: 
                return LookupBlend(sizeof(Blends5Neighbour8)/sizeof(Blend8),Blends5Neighbour8,shape,flags);     

            case 6:
                return LookupBlend(sizeof(Blends6Neighbour8)/sizeof(Blend8),Blends6Neighbour8,shape,flags);     

            case 7:
                return LookupBlend(sizeof(Blends7Neighbour8)/sizeof(Blend8),Blends7Neighbour8,shape,flags);     
        }

    }

    // Shouldn't get here if we've correctly considered all possible cases; 
    // keep the compiler happy, and, while we're still debugging possible shapes, 
    // return bad blend to highlight suspect alphamap logic
    return 13;
}

} // end of namespace