/**************************************************************************\
* 
* Copyright (c) 1998  Microsoft Corporation
*
* Module Name:
*
*   XBezier.hpp
*
* Abstract:
*
*   Interface of GpXBezier and its DDA classes
*
* Revision History:
*
*   11/05/1999 ikkof
*       Created it.
*
\**************************************************************************/

#ifndef _XBEZIER_HPP
#define _XBEZIER_HPP

#define FLATNESS_LIMIT      0.25
#define DISTANCE_LIMIT      2.0
#define BZ_BUFF_SIZE    32

class GpXPoints
{
friend class GpXPath;
friend class GpXBezier;

public:

    INT Dimension;
    INT Count;  // Number of Points
    REALD* Data;

protected:
    BOOL IsDataAllocated;

public:

    GpXPoints()
    {
        Initialize();
    }

    // When XPoints is created from a given data with copyData = FALSE,
    // the caller is responsible for deleting the data after XPoints is no longer
    // used.

    GpXPoints(REALD* data, INT dimension, INT count, BOOL copyData = TRUE)
    {
        Initialize();
        SetData(data, dimension, count, copyData);
    }

    GpXPoints(GpPointF* points, INT count)
    {
        Initialize();

        if(points && count > 0)
        {
            Data = (REALD*) GpMalloc(2*count*sizeof(REALD));
            if(Data)
            {
                INT i = 0, j = 0;

                while(j < count)
                {
                    Data[i++] = points[j].X;
                    Data[i++] = points[j].Y;
                    j++;
                }
                Dimension = 2;
                Count = count;
                IsDataAllocated = TRUE;
            }
        }
    }

    GpXPoints(GpPointD* points, INT count)
    {
        Initialize();

        if(points && count > 0)
        {
            Data = (REALD*) GpMalloc(2*count*sizeof(REALD));
            if(Data)
            {
                INT i = 0, j = 0;

                while(j < count)
                {
                    Data[i++] = points[j].X;
                    Data[i++] = points[j].Y;
                    j++;
                }
                Dimension = 2;
                Count = count;
                IsDataAllocated = TRUE;
            }
        }
    }

    REALD* GetData() {return Data;}

    // When XPoints is created from a given data with copyData = FALSE,
    // the caller is responsible for deleting the data after XPoints is no longer
    // used.

    GpStatus
    SetData(REALD* data, INT dimension, INT count, BOOL copyData = TRUE)
    {
        GpStatus status = Ok;

        if(data && dimension > 0 || count > 0)
        {
            REALD* newData = NULL;

            if(copyData)
            {
               INT totalSize = dimension*count*sizeof(REALD);
               if(IsDataAllocated)
                   newData = (REALD*) GpRealloc(Data, totalSize);
               else
                   newData = (REALD*) GpMalloc(totalSize);

                if(newData)
                {
                    GpMemcpy(newData, data, totalSize);
                    IsDataAllocated;
                }
                else
                    status = OutOfMemory;
            }
            else
            {
                if(Data && IsDataAllocated)
                    GpFree(Data);
                newData = data;
                IsDataAllocated = FALSE;
            }

            if(status == Ok)
            {
                Dimension = dimension;
                Count = count;
                Data = newData;
            }
        }
        else
            status = InvalidParameter;

        return status;
    }

    GpStatus Transform(const GpMatrix* matrix);

    BOOL AreEqualPoints(INT index1, INT index2)
    {
        if(index1 < 0 || index1 >= Count
            || index2 < 0 || index2 >= Count || Data == NULL)
            return FALSE;   // either index is out of the range or no data.

        BOOL areEqual = TRUE;
        if(index1 != index2)
        {
            REALD* data1 = Data + index1*Dimension;
            REALD* data2 = Data + index2*Dimension;
            INT k = 0;
            while(k < Dimension && areEqual)
            {
                if(*data1++ != *data2++)
                    areEqual = FALSE;
                k++;
            }
        }

        return areEqual;
    }
            
    static GpStatus
    GpXPoints::TransformPoints(
        const GpMatrix* matrix,
        REALD* data,
        INT dimension,
        INT count
        );

    ~GpXPoints()
    {
        if(Data && IsDataAllocated)
            GpFree(Data);
    }

protected:
    VOID Initialize()
    {
        Dimension = 0;
        Count = 0;
        Data = NULL;
        IsDataAllocated = FALSE;
    }
};

//********************************************************
// GpXBezierDDA class
//********************************************************

class GpXBezierConstants
{
friend class GpXBezierDDA;

private:
    REALD   H[7][7];    // Half step
    REALD   D[7][7];    // Double step
    REALD   S[7][7];    // One step
    REALD   F[7][7];    // Polynomical transform.
    REALD   H6[7][7];   // Poly to Bez transform in 6th order.
    REALD   G6[7][7];   // Bez to Poly transform in 6th order.

public:
    GpXBezierConstants();
};

class GpXBezierDDA
{
protected:
    GpXBezierConstants C;

protected:
    REALD   T;
    REALD   Dt;
    REALD   Q[16];
    REALD   P[16];
    INT     NthOrder;
    INT     Dimension;
    INT     NSteps;
    REAL    FlatnessLimit;
    REAL    DistanceLimit;

public:

public:

    GpXBezierDDA() { Initialize(); }

    GpXBezierDDA(
        const GpXPoints& xpoints,
        REAL flatnessLimit = FLATNESS_LIMIT,
        REAL distanceLimit = DISTANCE_LIMIT
        )
    {
        Initialize();
        SetBezier(xpoints, flatnessLimit, distanceLimit);
    }

    VOID
    SetBezier(
        const GpXPoints& xpoints,
        REAL flatnessLimit = FLATNESS_LIMIT,
        REAL distanceLimit = DISTANCE_LIMIT
        );

    INT  GetSteps() { return NSteps; }

    VOID InitDDA(GpPointF* pt);
    VOID HalveStepSize();
    VOID DoubleStepSize();
    VOID FastShrinkStepSize(INT shift);
    VOID TakeStep();
    BOOL NeedsSubdivide(REAL itsFlatnessLimit);
    BOOL GetNextPoint(GpPointF* pt);
    VOID MoveForward();
    INT GetControlPoints(GpXPoints* xpoints);

protected:

    VOID Initialize();
    VOID SetPolynomicalCoefficients();
    VOID TakeConvergentStep();
    BOOL Get2DDistanceVector(REALD* dx, REALD* dy, INT from, INT to);
};

//************************************
// XBezier class
//************************************

#define NthOrderMax     6

class GpXBezier 
{
private:
    // We now use an ObjectTag to determine if the object is valid
    // instead of using a BOOL.  This is much more robust and helps
    // with debugging.  It also enables us to version our objects
    // more easily with a version number in the ObjectTag.
    ObjectTag           Tag;    // Keep this as the 1st value in the object!

protected:
    VOID SetValid(BOOL valid)
    {
        Tag = valid ? ObjectTagGpBezier : ObjectTagInvalid;
    }

public:
    BOOL IsValid() const
    {
        ASSERT((Tag == ObjectTagGpBezier) || (Tag == ObjectTagInvalid)); 
    #if DBG
        if (Tag == ObjectTagInvalid)
        {
            WARNING1("Invalid GpBezier");
        }
    #endif

        return (Tag == ObjectTagGpBezier);
    }

    GpXBezier()
    {
        Initialize();
    }

    GpXBezier(INT order, const GpPointF* points, INT count)
    {        
        Initialize();
        SetValid(SetBeziers(order, points, count));
    }

    GpXBezier(INT order, const GpXPoints& xpoints)
    {
        Initialize();
        SetValid(SetBeziers(order, xpoints));
    }

    ~GpXBezier();

    GpStatus SetBeziers(INT order, const GpPointF* points, INT count);

    GpStatus SetBeziers(INT order, const GpXPoints& xpoints);

    virtual INT GetControlCount() {return Count;}
    virtual VOID GetBounds(GpMatrix* matrix, GpRect* bounds);
    virtual VOID Transform(GpMatrix* matrix);
    virtual GpStatus Flatten(
                        DynPointFArray* flattenPts,
                        const GpMatrix* matrix);

protected:

    VOID Initialize()
    {
        NthOrder = 0;
        Dimension = 0;
        Count = 0;
        Data = NULL;
        FlatnessLimit = FLATNESS_LIMIT;
        DistanceLimit = DISTANCE_LIMIT;
        SetValid(TRUE);
    }

    GpStatus
    FlattenEachBezier(
        DynPointFArray* flattenPts,
        GpXBezierDDA& dda,
        BOOL isFirstBezier,
        const GpMatrix* matrix,
        const REALD* bezierData
        );

    GpStatus
    Get2DPoints(
        GpPointF* points,
        INT count,
        const REALD* dataPoints,
        const GpMatrix* matrix = NULL);

    GpStatus CheckInputData(const GpPointF* points, INT count)
    {
        GpStatus status = InvalidParameter;
        if(NthOrder > 0)
        {
            if(count > NthOrder)
            {
                INT reminder = count % NthOrder;
                if(reminder == 1 && points !=NULL)
                    status = Ok;
            }
        }
        else    // NthOrder <= 0
        {
            if(count > 1 && points != NULL)
            {
                if(count <= NthOrderMax + 1)
                {
                    NthOrder = count - 1;
                    status = Ok;
                }
            }
        }

        return status;
    }

protected:  // GDI+ INTERNAL
    // Following are the two values to determin the flatness.
    REAL            FlatnessLimit;  // The maximum flateness.
    REAL            DistanceLimit;  // The minimum distance.

private:    // GDI+ INTERNAL
    INT NthOrder;
    INT Dimension;
    INT Count;
    REALD*  Data;
};


#endif