using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Drawing;
using System.Drawing.Drawing2D;

using CommonAI.RTS;
using CommonAI.Zone;
using CommonAI.Zone.Instance;
using CommonAI.RTS.Manhattan;

namespace CommonTest
{
    public class GameWorld : GameBase, InstanceZoneListener
    {
        private ZoneInfo mZoneInfo;
        private int mWidth;
        private int mHeight;
        private int mCellW;
        private int mCellH;
        private int mGridXCount;
        private int mGridYCount;


        private SceneLOL zone;
        private Dictionary<int, GameObject> units = new Dictionary<int, GameObject>();

        private List<DisplayLog> logs = new List<DisplayLog>();
        private float timer;

        private long mLastUpdateTime;
        private int mFPS = 0;
        
        private GameWorld(InitZoneInfo zinf)
        {
            this.mZoneInfo = zinf.Info;
            this.mGridXCount = RTSMath.roundMod(mZoneInfo.TotalWidth, mZoneInfo.GridCellW);
            this.mGridYCount = RTSMath.roundMod(mZoneInfo.TotalHeight, mZoneInfo.GridCellH);
            this.mWidth = mZoneInfo.TotalWidth;
            this.mHeight = mZoneInfo.TotalHeight;
            this.mCellW = mZoneInfo.GridCellW;
            this.mCellH = mZoneInfo.GridCellH;
            this.zone = new SceneLOL(this, mZoneInfo, SpaceDIV);
            zinf.initGame(zone);
        }
        //-----------------------------------------------------------------------------------------------
        // instructions
        //-----------------------------------------------------------------------------------------------

        #region TEST_________________________________



        public static GameWorld create3C()
        {
            InitZoneInfo zinfo = test_InitZoneInfo(null);
            test_init3C(zinfo);
            GameWorld gw = new GameWorld(zinfo);
            return gw;
        }

        public static GameWorld create4A()
        {
            InitZoneInfo zinfo = test_InitZoneInfo(null); 
            test_init4A(zinfo);
            GameWorld gw = new GameWorld(zinfo);
            return gw;
        }

        public static GameWorld createLOL(Bitmap mapmask)
        {
            InitZoneInfo zinfo = test_InitZoneInfo(mapmask);
            test_initLOL(zinfo);
            GameWorld gw = new GameWorld(zinfo);
            return gw;
        }

        public void test_AddActor(string name)
        {
            float x = random.Next(0, mWidth);
            float y = random.Next(0, mHeight);
            x = mWidth / 2;
            y = mHeight / 2;

            UnitInfo info = test_InitUnitInfo(SceneLOL.UNIT_FORCE_A, name, false, false);
            info.type = UnitInfo.UnitType.TYPE_PLAYER;
            info.force = SceneLOL.UNIT_FORCE_A;
            info.name = name;

            info.HealthPoint = 100000000;

            info.baseSkillID = SKILL_MELEE_ATTACK_4;
            //info.skills.Add(SKILL_ORBIT_CYCLE);
            /*
            info.skills.Add(SKILL_MELEE_ATTACK_4);
            info.skills.Add(SKILL_STRAIGHT);
            info.skills.Add(SKILL_AOE);
            info.skills.Add(SKILL_LASER);
            info.skills.Add(SKILL_BINDING);
            info.skills.Add(SKILL_MISSILE);
            info.skills.Add(SKILL_ACTOR_SLIP);
            info.skills.Add(SKILL_STRAIGHT_CLUSTER);
            */
            zone.pushAction(new AddUnitAction(info, x, y, 0));
        }

        public void test_AddUnit(string name)
        {
            int force = random.Next(0, 100) % 2;
            float x = 1;
            float y = 1;
            UnitInfo info = test_InitUnitInfo(force, name, random.Next()%2==0, false);
            info.type = UnitInfo.UnitType.TYPE_NPC;
            info.name = name;
            if (force == 0)
            {
                x = random.Next(0, mWidth / 2);
                y = random.Next(0, mHeight);
                info.force = SceneLOL.UNIT_FORCE_A;
            }
            else
            {
                x = random.Next(mWidth / 2, mWidth);
                y = random.Next(0, mHeight);
                info.force = SceneLOL.UNIT_FORCE_B;
            }
            zone.pushAction(new AddUnitAction(info, x, y, 0));
        }

        public void test_UnitMove(int unit_id, float x, float y)
        {
            zone.pushAction(new UnitMoveAction(unit_id, x, y, false));
        }

        public void test_UnitSlip(int unit_id, float x, float y)
        {
            zone.pushAction(new UnitSlipAction(unit_id, x, y));
        }

        public void test_UnitTransport(int unit_id, float x, float y)
        {
            zone.pushAction(new UnitTransportAction(unit_id, x, y));
        }

        public void test_UnitAttack(int unit_id, int skillID)
        {
            zone.pushAction(new UnitLaunchSkillAction(unit_id, skillID));
        }

        #endregion

        public int Width { get { return mWidth; } }
        public int Height { get { return mHeight; } }
        public int CellW { get { return mCellW; } }
        public int CellH { get { return mCellH; } }
        public int UnitCount { get { return units.Count; } }
        public int FPS { get { return mFPS; } }


        //-----------------------------------------------------------------------------------------------------------------
        // windows
        #region WINDOW

        private RectangleF mWindow = new RectangleF(0, 0, 512, 512);

        private float cameraX = 0;
        private float cameraY = 0;
        private float cameraScale = 1;

        public void setWindow(RectangleF window)
        {
            this.mWindow.X = window.X;
            this.mWindow.Y = window.Y;
            this.mWindow.Width = window.Width;
            this.mWindow.Height = window.Height;
        }

        public float screenToWorldSize(float s)
        {
            return s / cameraScale;
        }
        public float worldToScreenSize(float s)
        {
            return s * cameraScale;
        }

        public float screenToWorldX(float x)
        {
            return (x - mWindow.Width / 2) / cameraScale + cameraX;
        }
        public float screenToWorldY(float y)
        {
            return (y - mWindow.Height / 2) / cameraScale + cameraY;
        }
        public float worldToScreenX(float x)
        {
            return (x - cameraX) * cameraScale + mWindow.Width / 2;
        }
        public float worldToScreenY(float y)
        {
            return (y - cameraY) * cameraScale + mWindow.Height / 2;
        }

        public void setCamera(float x, float y)
        {
            this.cameraX = x;
            this.cameraY = y;
        }

        public void setCameraScale(float scale)
        {
            cameraScale = scale;
            cameraScale = Math.Max(cameraScale, 0.01f);
        }

        public float getCameraScale()
        {
            return cameraScale;
        }

        #endregion

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

        #region RENDER 

        public void renderWorld(Graphics g)
        {
            GraphicsState gs = g.Save();
            {
                g.TranslateTransform(
                   mWindow.Width / 2,
                   mWindow.Height / 2);
                g.ScaleTransform(
                    cameraScale,
                    cameraScale);
                g.TranslateTransform(
                    -cameraX,
                    -cameraY);
                renderWorld(g, new RectangleF(
                    screenToWorldX(0),
                    screenToWorldY(0),
                    screenToWorldSize(mWindow.Width),
                    screenToWorldSize(mWindow.Height)));
            }
            g.Restore(gs);
        }

        public void renderHUD(Graphics g, GameUnit unit)
        {
            Brush brush = new SolidBrush(Color.White);
            GraphicsState gs = g.Save();
            {
                g.DrawString("FPS=" + FPS + " TotalUnit=" + UnitCount,
                        global_font, brush, 1, 1, StringFormat.GenericDefault);

                renderUnitStatusHUD(g, unit);
            }
            g.Restore(gs);
        }

        private void renderUnitStatusHUD(Graphics g, GameUnit unit)
        {
            if (unit != null)
            {
                Pen pen_B = new Pen(Color.Gray);
                Brush brush_C = new SolidBrush(Color.FromArgb(0xFF, 0x40, 0x40, 0x40));
                Brush brush_CD = new SolidBrush(Color.FromArgb(0x80, 0xFF, 0xFF, 0xFF));
                Brush brush_T = new SolidBrush(Color.Blue);

                float sw = 50;
                float sh = 50;
                float sx = 0;
                float sy = mWindow.Height - sh;
                ICollection<GameUnit.SkillState> status = unit.getSkillStatus();
                foreach (GameUnit.SkillState ss in status) 
                {
                    float x = sx + 2;
                    float y = sy + 2;
                    float w = sw - 4;
                    float h = sh - 4;

                    g.SetClip(new RectangleF(x, y, w, h));
                    g.FillRectangle(brush_C, x, y, w, h);
                    g.DrawRectangle(pen_B, x, y, w-1, h-1);
                    if (ss.isCD())
                    {
                        g.FillPie(brush_CD, x - w, y - h, w * 3, h * 3, -90, 360 * ss.getCDPercent());
                    }
                    g.DrawString("" + ss.Data.name, global_font, brush_T, x, y, StringFormat.GenericDefault);
                    g.ResetClip();
                    sx += sw;
                }
            }
        }

        private void renderWorld(Graphics g, RectangleF rect)
        {
            renderTerrain(g, rect);

            renderSpaceDiv(g, rect);

            renderUnits(g, rect);

            renderLogs(g);
        }


        private void renderTerrain(Graphics g, RectangleF rect)
        {
            int sx = (int)(rect.X / mCellW);
            int sy = (int)(rect.Y / mCellH);
            int sw = (int)(rect.Width / CellW) + 1;
            int sh = (int)(rect.Height / CellH) + 1;
            int lw = (int)(rect.Width + CellW * 2);
            int lh = (int)(rect.Height + CellH * 2);

            Brush bru_0 = new SolidBrush(Color.FromArgb(0xff, 0x20, 0x20, 0x20));
            Brush bru_1 = new SolidBrush(Color.FromArgb(0xff, 0x20, 0x40, 0x20));
            for (int x = sx + sw; x >= sx; --x)
            {
                for (int y = sy + sh; y >= sy; --y)
                {
                    if (x < mGridXCount && x >= 0 && y < mGridYCount && y >= 0)
                    {
                        if (mZoneInfo.TerrainMatrix[x, y] != 0)
                        {
                            g.FillRectangle(bru_1, x * mCellW, y * mCellH, mCellW, mCellH);
                        }
                        else
                        {
                            g.FillRectangle(bru_0, x * mCellW, y * mCellH, mCellW, mCellH);
                        }
                    }
                }
            }
        }

        private void renderSpaceDiv(Graphics g, RectangleF rect)
        {
            int sx = (int)(rect.X / SpaceDIV);
            int sy = (int)(rect.Y / SpaceDIV);
            int sw = (int)(rect.Width / SpaceDIV) + 1;
            int sh = (int)(rect.Height / SpaceDIV) + 1;
            int lw = (int)(rect.Width + SpaceDIV * 2);
            int lh = (int)(rect.Height + SpaceDIV * 2);

            Pen pen = new Pen(Color.FromArgb(0x10, 0xff, 0xff, 0xff));
            for (int x = sx + sw; x >= sx; --x)
            {
                g.DrawLine(pen, x * SpaceDIV, sy * SpaceDIV, x * SpaceDIV, sy * SpaceDIV + lh);
            }
            for (int y = sy + sh; y >= sy; --y)
            {
                g.DrawLine(pen, sx * SpaceDIV, y * SpaceDIV, sx * SpaceDIV + lw, y * SpaceDIV);
            }
        }

        private void renderUnits(Graphics g, RectangleF rect)
        {
            foreach (GameObject u in units.Values)
            {
                if (rect.IntersectsWith(u.Bounds))
                {
                    GraphicsState state = g.Save();
                    g.TranslateTransform(u.pos.x, u.pos.y);
                    u.render(g);
                    g.Restore(state);
                }
            }
        }

        private void renderLogs(Graphics g)
        {
            for (int i = logs.Count - 1; i >= 0; --i)
            {
                DisplayLog u = logs[i];
                GraphicsState state = g.Save();
                g.TranslateTransform(u.pos.x, u.pos.y);
                u.render(g);
                g.Restore(state);
                if (!u.Alive)
                {
                    logs.RemoveAt(i);
                }
            }
        }

        #endregion

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

        public void update()
        {
            zone.update();

            foreach (GameObject u in units.Values)
            {
                u.update();
            }

            timer++;

            long curTime = CommonLang.CUtils.CurrentTimeMS();
            if (curTime != mLastUpdateTime)
            {
                mFPS = (int)(1000 / (curTime - mLastUpdateTime));
            }
            mLastUpdateTime = curTime;
        }


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

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

        public GameUnit getUnit(int unitID)
        {
            GameObject go;
            if (units.TryGetValue(unitID, out go))
            {
                if (go is GameUnit)
                {
                    return go as GameUnit;
                }
            }
            return null;
        }

        public GameUnit getUnitByName(string name)
        {
            foreach (GameObject u in units.Values)
            {
                if (u is GameUnit)
                {
                    if (name.Equals((u as GameUnit).Info.name))
                    {
                        return u as GameUnit;
                    }
                }
            }
            return null;
        }

        //-----------------------------------------------------------------------------------------------
        // events
        //-----------------------------------------------------------------------------------------------

        public void onEventHandler(Event e)
        {
            if (e is AddUnitEvent)
            {
                doAddUnitEvent(e as AddUnitEvent);
            }
            else if (e is AddSpellEvent)
            {
                doAddSpellEvent(e as AddSpellEvent);
            }
            else if (e is RemoveObjectEvent)
            {
                doRemoveObjectEvent(e as RemoveObjectEvent);
            }
            else if (e is SyncPosEvent)
            {
                doSyncPosEvent(e as SyncPosEvent);
            }
            else if (e is ObjectEvent)
            {
                ObjectEvent oe = e as ObjectEvent;
                GameObject gu;
                if (units.TryGetValue(oe.object_id, out gu))
                {
                    gu.doEvent(oe);
                }
            }
        }

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

        private void doAddUnitEvent(AddUnitEvent e)
        {
            GameUnit gu = null;
            if (e.info.force == 0)
            {
                gu = new GameUnitActor(this, e.info, e.unit_id);
            }
            else
            {
                gu = new GameUnitNpc(this, e.info, e.unit_id);
            }
            gu.pos.x = e.x;
            gu.pos.y = e.y;
            gu.direction = e.direction;
            units[gu.ID] = gu;
        }

        private void doAddSpellEvent(AddSpellEvent e)
        {
            SpellTemplate sp = TemplateManager.getInstance().getSpell(e.spell_template_id);
            if (sp != null)
            {
                GameSpellObject gs = new GameSpellObject(
                    this, sp, e.spell_id, 
                    getUnit(e.launcher_unit_id), 
                    null);
                gs.pos.x = e.x;
                gs.pos.y = e.y;
                gs.direction = e.direction; 
                units[gs.ID] = gs;
            }
        }

        private void doRemoveObjectEvent(RemoveObjectEvent e)
        {
            units.Remove(e.object_id);
        }

        private void doSyncPosEvent(SyncPosEvent e)
        {
            GameObject gu;
            for (int i = 0; i < e.unitsID.Length; ++i)
            {
                if (units.TryGetValue(e.unitsID[i], out gu))
                {
                    gu.syncPos(e.unitsX[i], e.unitsY[i], e.unitsD[i]);
                }
            }
        }

        public void showLog(string text, float x, float y)
        {
            logs.Add(new DisplayLog(text, x, y));
        }

        public class DisplayLog
        {
            static private Pen pen = new Pen(Color.Black);
            static private Brush brush = new SolidBrush(Color.White);

            public Vector2 pos = new Vector2();

            private int time = 20;
            private string text = "";
            private SizeF size;

            public DisplayLog(string text, float x, float y)
            {
                this.text = text;
                this.time = 20;
                this.pos.x = x;
                this.pos.y = y;
            }

            public void render(Graphics g)
            {
                size = g.MeasureString(text, global_font);
                g.DrawString(
                    text,
                    global_font,
                    brush,
                    -size.Width / 2,
                    -size.Height / 2 - 20 + time,
                    StringFormat.GenericDefault);
                time--;
            }

            public bool Alive
            {
                get { return time > 0; }
            }


        }

    }

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

}