using System;
using CommonUI.Display;
using CommonLang;

namespace CommonUI.Cell.Game
{
    public class CSpriteMeta
    {
        public const byte CD_TYPE_MAP = 0;
        public const byte CD_TYPE_ATK = 1;
        public const byte CD_TYPE_DEF = 2;
        public const byte CD_TYPE_EXT = 3;

        private SpriteSet data;
        private CAnimates animates;
        private CCollides collides;

        private int animCount;
        private int[] frameCounts;

        //frameAnimate[animate id][frame id] = canimate frame index.
        private string[] AnimateNames;
        //
        private int[][] FrameAnimate;
        // frameAnimate[animate id][frame id] = ccollide frame index.
        private int[][] FrameCDMap;
        // frameAnimate[animate id][frame id] = ccollide frame index.
        private int[][] FrameCDAtk;
        // frameAnimate[animate id][frame id] = ccollide frame index.
        private int[][] FrameCDDef;
        // frameAnimate[animate id][frame id] = ccollide frame index.
        private int[][] FrameCDExt;

        private float[][] FrameAlpha;


        public CAnimates Animates { get { return animates; } }
        public CCollides Collides { get { return collides; } }
        public SpriteSet Data { get { return data; } }
        public CPJAtlas Atlas { get { return animates.tiles; } }

        public CSpriteMeta(SpriteSet tsprite, CPJAtlas tiles)
        {
            this.data = tsprite;
            {
                //scene parts
                int scenePartCount = tsprite.PartTileID.Length;	// --> u16
                CAnimates animates = new CAnimates(scenePartCount, tiles);
                for (int i = 0; i < scenePartCount; i++)
                {
                    animates.setPart(i,
                        tsprite.PartX[i],
                        tsprite.PartY[i],
                        tsprite.PartTileID[i],
                        tsprite.PartTileTrans[i],
                        tsprite.PartAlpha[i],
                        tsprite.PartRotate[i],
                        tsprite.PartScaleX[i],
                        tsprite.PartScaleY[i]);
                }
                //scene frames
                animates.setFrames(tsprite.Parts);

                //cd parts
                int cdCount = tsprite.BlocksMask.Length;	// --> u16
                CCollides collides = new CCollides(cdCount);
                for (int i = 0; i < cdCount; i++)
                {
                    collides.setCDRect(i,
                        tsprite.BlocksMask[i],
                        tsprite.BlocksX1[i],
                        tsprite.BlocksY1[i],
                        tsprite.BlocksW[i],
                        tsprite.BlocksH[i]);
                }
                //cd frames
                collides.setFrames(tsprite.Blocks);

                this.animates = animates;
                this.collides = collides;
            }
            {
                animCount = tsprite.AnimateCount;
                frameCounts = new int[animCount];

                AnimateNames = tsprite.AnimateNames;

                FrameAnimate = new int[animCount][];
                FrameCDMap = new int[animCount][];
                FrameCDAtk = new int[animCount][];
                FrameCDDef = new int[animCount][];
                FrameCDExt = new int[animCount][];
                FrameAlpha = new float[animCount][];

                for (int a = 0; a < animCount; a++)
                {
                    int fc = tsprite.FrameAnimate[a].Length;
                    frameCounts[a] = fc;

                    FrameAnimate[a] = new int[fc];
                    FrameCDMap[a] = new int[fc];
                    FrameCDAtk[a] = new int[fc];
                    FrameCDDef[a] = new int[fc];
                    FrameCDExt[a] = new int[fc];
                    FrameAlpha[a] = new float[fc];
                    for (int f = 0; f < fc; f++)
                    {
                        FrameAnimate[a][f] = tsprite.FrameAnimate[a][f];
                        FrameCDMap[a][f] = tsprite.FrameCDMap[a][f];
                        FrameCDAtk[a][f] = tsprite.FrameCDAtk[a][f];
                        FrameCDDef[a][f] = tsprite.FrameCDDef[a][f];
                        FrameCDExt[a][f] = tsprite.FrameCDExt[a][f];
                        FrameAlpha[a][f] = tsprite.FrameAlpha[a][f];
                    }
                }
            }
        }

        //	------------------------------------------------------------------------------------------


        public int getFrameImageCount(int anim, int frame = 0)
        {
            return animates.getComboFrameCount(FrameAnimate[anim][frame]);
        }

        public int getFrameTileID(int anim, int frame = 0, int sub = 0)
        {
            return animates.getFrameTileID(FrameAnimate[anim][frame], sub);
        }

        public float getFrameImageX(int anim, int frame = 0, int sub = 0)
        {
            return animates.getFrameX(FrameAnimate[anim][frame], sub);
        }

        public float getFrameImageY(int anim, int frame = 0, int sub = 0)
        {
            return animates.getFrameY(FrameAnimate[anim][frame], sub);
        }

        public float getFrameImageWidth(int anim, int frame = 0, int sub = 0)
        {
            return animates.getFrameW(FrameAnimate[anim][frame], sub);
        }

        public float getFrameImageHeight(int anim, int frame = 0, int sub = 0)
        {
            return animates.getFrameH(FrameAnimate[anim][frame], sub);
        }

        public Trans getFrameImageTransform(int anim, int frame = 0, int sub = 0)
        {
            return animates.getFrameTransform(FrameAnimate[anim][frame], sub);
        }

        public int getAvaliableTileID()
        {
            for (int anim = 0; anim < getAnimateCount(); anim++)
            {
                for (int frame = 0; frame < getFrameCount(anim); frame++)
                {
                    for (int sub = 0; sub < getFrameImageCount(anim, frame); sub++)
                    {
                        int tid = getFrameTileID(anim, frame, sub);
                        if (!Atlas.IsNullTile(tid))
                        {
                            return tid;
                        }
                    }
                }
            }
            return 0;
        }

        //	------------------------------------------------------------------------------------------

        public int[][] getCDAnimates(byte type)
        {
            switch (type)
            {
                case CD_TYPE_MAP:
                    return FrameCDMap;
                case CD_TYPE_ATK:
                    return FrameCDAtk;
                case CD_TYPE_DEF:
                    return FrameCDDef;
                case CD_TYPE_EXT:
                    return FrameCDExt;
            }
            return null;
        }

        public int getFrameCDCount(int anim, int frame, byte type)
        {
            int[][] out_animates = getCDAnimates(type);
            if (out_animates != null)
            {
                return collides.getComboFrameCount(out_animates[anim][frame]);
            }
            return -1;
        }

        public CCD getFrameCD(int anim, int frame, byte type, int sub)
        {
            int[][] out_animates = getCDAnimates(type);
            if (out_animates != null)
            {
                return collides.getFrameCD(out_animates[anim][frame], sub);
            }

            return null;
        }

        //	------------------------------------------------------------------------------------------

        public CCD getVisibleBounds()
        {
            return animates.getAllBounds();
        }

        public CCD getVisibleBounds(int anim, int frame)
        {
            if (anim < animCount)
            {
                if (frame < frameCounts[anim])
                {
                    CCD out_bounds = new CCD();
                    out_bounds.X1 = float.MaxValue;
                    out_bounds.X2 = float.MinValue;
                    out_bounds.Y1 = float.MaxValue;
                    out_bounds.Y2 = float.MinValue;
                    int frameid = FrameAnimate[anim][frame];
                    int count = animates.getComboFrameCount(frameid);
                    for (int i = 0; i < count; i++)
                    {
                        out_bounds.X1 = Math.Min(out_bounds.X1, animates.getFrameX(frameid, i));
                        out_bounds.Y1 = Math.Min(out_bounds.Y1, animates.getFrameY(frameid, i));
                        out_bounds.X2 = Math.Max(out_bounds.X2, animates.getFrameX(frameid, i) + animates.getFrameW(frameid, i));
                        out_bounds.Y2 = Math.Max(out_bounds.Y2, animates.getFrameY(frameid, i) + animates.getFrameH(frameid, i));
                    }
                    return out_bounds;
                }
            }

            return null;
        }

        public CCD getVisibleBounds(int anim)
        {
            if (anim < animCount)
            {
                CCD out_bounds = new CCD();
                out_bounds.X1 = float.MaxValue;
                out_bounds.X2 = float.MinValue;
                out_bounds.Y1 = float.MaxValue;
                out_bounds.Y2 = float.MinValue;
                for (int f = getFrameCount(anim) - 1; f >= 0; --f)
                {
                    int frameid = FrameAnimate[anim][f];
                    int count = animates.getComboFrameCount(frameid);
                    for (int i = 0; i < count; i++)
                    {
                        out_bounds.X1 = Math.Min(out_bounds.X1, animates.getFrameX(frameid, i));
                        out_bounds.Y1 = Math.Min(out_bounds.Y1, animates.getFrameY(frameid, i));
                        out_bounds.X2 = Math.Max(out_bounds.X2, animates.getFrameX(frameid, i) + animates.getFrameW(frameid, i));
                        out_bounds.Y2 = Math.Max(out_bounds.Y2, animates.getFrameY(frameid, i) + animates.getFrameH(frameid, i));
                    }
                }
                return out_bounds;
            }
            return null;
        }



        public CCD getCDBounds()
        {
            return collides.getAllBounds();
        }

        public int getAnimateIndex(string animate_name)
        {
            for (int i = animCount - 1; i >= 0; --i)
            {
                if (string.Equals(AnimateNames[i], animate_name))
                {
                    return i;
                }
            }
            return -1;
        }

        public string getAnimateName(int anim)
        {
            return AnimateNames[anim];
        }


        public int getAnimateCount()
        {
            return animCount;
        }

        public int getFrameCount(int anim)
        {
            return frameCounts[anim];
        }

        public void render(Graphics g, int anim, int frame, float dx = 0, float dy = 0)
        {
            if ((anim < animCount) && (frame < frameCounts[anim]))
            {
                float olda = g.getAlpha();
                g.addAlpha(FrameAlpha[anim][frame]);
                animates.render(g, FrameAnimate[anim][frame], dx, dy);
                g.setAlpha(olda);
            }
        }

        public void addVertex(VertexBuffer v, int anim, int frame, float dx, float dy)
        {
            if ((anim < animCount) && (frame < frameCounts[anim]))
            {
                animates.addVertex(v, FrameAnimate[anim][frame], dx, dy);
            }
        }
        public void beginImage(Graphics g)
        {
            animates.beginImage(g);
        }
    }

    public struct TSpriteMetaAnimateFrame
    {
        public CSpriteMeta sprite;
        public int anim, frame;
    }

    /// <summary>
    /// CPJSprite 控制类
    /// </summary>
    public class CSpriteController : ICellSpriteController
    {
        private readonly CSpriteMeta mMeta;
        private int mCurAnimate = 0;
        private int mCurFrame = 0;
        private int mPlayTimes = -1;
        private int mCurrentPlayTimes = 0;
        public bool IsAutoPlay = true;

        public CSpriteController(CSpriteMeta src)
        {
            mMeta = src;
        }
        public bool Update()
        {
            if (IsAutoPlay)
            {
                NextCycFrame();
                PlayTimesTick();
                return true;
            }
            return false;
        }
        public void Dispose()
        {
            IsAutoPlay = false;
            m_FinishCallback = null;
        }


        public CSpriteMeta Meta
        {
            get { return mMeta; }
        }
        public int CurrentFrame
        {
            get { return mCurFrame; }
        }
        public int CurrentAnimate
        {
            get { return mCurAnimate; }
        }
        public bool IsEndFrame
        {
            get
            {
                if (mCurFrame + 1 >= mMeta.getFrameCount(mCurAnimate))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        public void SetCurrentAnimate(string anim_name)
        {
            int anim = mMeta.getAnimateIndex(anim_name);
            if (anim >= 0)
            {
                SetCurrentAnimate(anim);
            }
        }

        public void SetCurrentAnimate(int anim)
        {
            mCurAnimate = anim;
            mCurAnimate = CMath.cycNum(mCurAnimate, 0, mMeta.getAnimateCount());
            mCurFrame = CMath.cycNum(mCurFrame, 0, mMeta.getFrameCount(mCurAnimate));
        }

        public void SetCurrentFrame(int anim, int index)
        {
            mCurAnimate = CMath.cycNum(anim, 0, mMeta.getAnimateCount());
            mCurFrame = CMath.cycNum(index, 0, mMeta.getFrameCount(mCurAnimate));
        }


        public bool NextFrame()
        {
            mCurFrame++;
            int acount = mMeta.getFrameCount(mCurAnimate);
            if (mCurFrame >= acount)
            {
                mCurFrame = acount - 1;
                return true;
            }
            else
            {
                return false;
            }
        }

        public void NextCycFrame()
        {
            mCurFrame++;
            int acount = mMeta.getFrameCount(mCurAnimate);
            if (acount > 0)
            {
                mCurFrame %= acount;
            }
        }

        public void NextCycFrame(int restart)
        {
            mCurFrame++;
            int acount = mMeta.getFrameCount(mCurAnimate);
            if (mCurFrame < acount)
            {
            }
            else
            {
                if (acount > 0)
                {
                    mCurFrame = (restart % acount);
                }
            }
        }

        public bool PrewFrame()
        {
            mCurFrame--;
            if (mCurFrame < 0)
            {
                mCurFrame = 0;
                return true;
            }
            else
            {
                return false;
            }
        }


        public void PrewCycFrame()
        {
            mCurFrame--;
            if (mCurFrame < 0)
            {
                mCurFrame = mMeta.getFrameCount(mCurAnimate) - 1;
            }
        }

        public void PrewCycFrame(int restart)
        {
            mCurFrame--;
            if (mCurFrame < 0)
            {
                mCurFrame = (restart % mMeta.getFrameCount(mCurAnimate));
            }
        }



        /// <summary>
        /// 播放动画:anim动画名、times次数(-1=无限).
        /// </summary>
        /// <param name="anim"></param>
        /// <param name="times"></param>
        /// <param name="callBack"></param>
        public void PlayAnimate(int anim, int times, CSpriteEventHandler callBack)
        {
            mPlayTimes = times;
            m_FinishCallback = callBack;
            SetCurrentAnimate(anim);
            mCurFrame = 0;
            IsAutoPlay = true;
        }

        /// <summary>
        /// 播放动画:anim_name动画名、times次数(-1=无限).
        /// </summary>
        /// <param name="anim_name"></param>
        /// <param name="times"></param>
        /// <param name="callBack"></param>
        public void PlayAnimate(string anim_name, int times, CSpriteEventHandler callBack)
        {
            int anim = mMeta.getAnimateIndex(anim_name);
            if (anim >= 0)
            {
                PlayAnimate(anim, times, callBack);
            }
        }

        public void StopAnimate(bool needCallBack)
        {
            IsAutoPlay = false;
            if (m_FinishCallback != null && needCallBack)
            {
                m_FinishCallback(this);
            }
            m_FinishCallback = null;
        }

        private void PlayTimesTick()
        {
            if (mPlayTimes < 0) { return; }
            if (!IsEndFrame) { return; }

            mCurrentPlayTimes++;

            if (mCurrentPlayTimes >= mPlayTimes)
            {
                IsAutoPlay = false;
                if (m_FinishCallback != null)
                {
                    CSpriteEventHandler temp = m_FinishCallback;
                    m_FinishCallback = null;
                    temp(this);
                    temp = null;
                }
            }
        }

        //---------------------------------------------------------------------------------------------------

        private CSpriteEventHandler m_FinishCallback;
        public event CSpriteEventHandler Finish { add { m_FinishCallback += value; } remove { m_FinishCallback -= value; } }

        //---------------------------------------------------------------------------------------------------

    }

    /// <summary>
    /// 动画编辑器控制类
    /// </summary>
    public interface ICellSpriteController
    {
        int CurrentFrame { get; }
        int CurrentAnimate { get; }
        bool IsEndFrame { get; }

        void SetCurrentAnimate(string anim);
        void SetCurrentAnimate(int anim);
        void SetCurrentFrame(int anim, int index);

        bool NextFrame();
        void NextCycFrame();
        void NextCycFrame(int restart);

        bool PrewFrame();
        void PrewCycFrame();
        void PrewCycFrame(int restart);
    }

    public delegate void CSpriteEventHandler(ICellSpriteController sender);

}