using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using DGLE2;

namespace SuperTetris
{
    public partial class Form1 : Form
    {

        public const int SCREEN_WIDTH = 300;
        public const int SCREEN_HEIGHT = 600;
        public const int OFFSET_X = 362;
        public const int OFFSET_Y = 104;
        const int OBJ_SIZE = 30;

        #region CONST
        static int score = 0;
        const string APP_CAPTION = "2.33D Tetris";
        const string DLL_PATH = @"DGLE2.dll";
        static Random rand = new Random();
        static IMesh[] meshes = new IMesh[7];
        static ITexture[] textures = new ITexture[7];
        static TPoint3[][] maptypes = new TPoint3[7][]
        {
            new TPoint3[]{              //O
                new TPoint3(1,1,1),
                new TPoint3(2,1,1),
                new TPoint3(1,2,1),
                new TPoint3(2,2,1),
                new TPoint3(1,1,2),
                new TPoint3(2,1,2),
                new TPoint3(1,2,2),
                new TPoint3(2,2,2)
            },
            new TPoint3[]{              //I
                new TPoint3(2,0,2),
                new TPoint3(2,1,2),
                new TPoint3(2,2,1),
                new TPoint3(2,2,2),
                new TPoint3(2,2,3),
                new TPoint3(2,3,2)
            },
            new TPoint3[]{              //S
                new TPoint3(2,1,2),
                new TPoint3(1,2,2),
                new TPoint3(1,2,1),
                new TPoint3(2,2,2),
                new TPoint3(2,2,1),
                new TPoint3(1,3,2)
            },
            new TPoint3[]{              //Z
                new TPoint3(1,1,1),
                new TPoint3(1,1,2),
                new TPoint3(1,2,1),
                new TPoint3(2,2,1),
                new TPoint3(2,3,1),
                new TPoint3(2,3,0)
            },
            new TPoint3[]{              //J
                new TPoint3(2,1,1),
                new TPoint3(1,1,1),
                new TPoint3(1,2,1),
                new TPoint3(1,3,1),
                new TPoint3(1,1,2),
                new TPoint3(1,1,3),
                new TPoint3(1,2,3),
                new TPoint3(1,3,2),
                new TPoint3(1,3,3)
            },
            new TPoint3[]{              //L
                new TPoint3(1,1,2),
                new TPoint3(2,1,2),
                new TPoint3(2,2,2),
                new TPoint3(2,3,2),
                new TPoint3(2,2,1),
                new TPoint3(2,2,0)
            },
            new TPoint3[]{              //T
                new TPoint3(2,1,2),
                new TPoint3(2,2,2),
                new TPoint3(1,2,2),
                new TPoint3(2,3,2),
                new TPoint3(2,2,1)
            }
        };
        //Delegates
        static DSubscriber DInit = null;
        static DSubscriber DFree = null;
        static DSubscriber DProcess = null;
        static DSubscriber DRender = null;

        //Engine main interface
        static IEngineCore pEngineCore = null;

        //Subsystems interfaces
        static IResourceManager pResourceManager = null;
        static IRender pRender = null;
        static IInput pInput = null;
        static IBitmapFont pFntMain = null;

        public static IRender2D pRender2D = null;

        static uint uiCounter = 0;
        static bool bIsGame = false;


        static TField Field = new TField();
        static List<TObject> StaticObj = new List<TObject>();
        static TObject ActiveObj;

        static ITexture pTexBegin = null,
            pTexGame = null, pTexGameWindow=null;
        static ISoundSample pSndCrash = null, pSndDelete = null, pSndRotate = null;

        static IMusic pMusic = null;
        #endregion

        //Object type
        struct TObject
        {
            public bool alive, movingX, movingY, rotZ, rotX;
            public int _type;
            public float rY, rZ;
            public TPoint3[] map;
            public Point coord;
            public Point coordf;
            public TPoint3 dim;
            public float angle, frame;

            public TObject(int type)
            {
                dim = new TPoint3(1f, 1f, 1f);
                rY = 0;
                rZ = 0;
                _type = type;
                rotX = false;
                rotZ = false;
                movingX = false;
                movingY = false;
                //_type = 6;
                map = new TPoint3[maptypes[_type].Count()];
                maptypes[_type].CopyTo(map,0);
                coord = new Point(rand.Next(6), -5);
                coordf = new Point(OFFSET_X + coord.X * OBJ_SIZE, OFFSET_Y + coord.Y * OBJ_SIZE);
                alive = true;
                frame = 0;
                angle = 0;
            }
            public void Draw()
            {
                if (map == null)
                    return;
                TPoint3 p3_2;
                p3_2 = new TPoint3(0f, rY, rZ);

                TRectF rect = new TRectF(coordf.X, coordf.Y, OBJ_SIZE * 4, OBJ_SIZE * 4);
                TColor4 color = new TColor4(150, 150, 0, 150);
                TPoint2 point = new TPoint2(coordf.X + 60, coordf.Y + 60);
                pRender2D.DrawMesh(meshes[_type], textures[_type],ref point, ref dim, ref p3_2, angle, false, 90.0f, E_EFFECT2D_FLAGS.EF_DEFAULT);
                for (int i = 0; i < map.Count(); i++)
                {
                    point = new TPoint2(coordf.X + (int)map[i].x * OBJ_SIZE, coordf.Y + (int)map[i].y * OBJ_SIZE);
                    rect = new TRectF(point.x, point.y, OBJ_SIZE, OBJ_SIZE); 
                }
            }
            public void DoStaticStep()
            {
                if (rotX)
                {
                    angle += 3;
                    if (angle % 90 == 0)
                        rotX = false;
                    return;
                }
                if (rotZ)
                {
                    angle += 3;
                    if (angle % 90 == 0)
                        rotZ = false;
                    return;
                }

                if (rY == 1 || angle%360 == 0)
                {
                    switch ((int)angle % 180)
                    {
                        case 90:
                            if (dim.x > 0.2)
                                dim.x -= 0.07f;
                            else
                            {
                                AddObj();
                                alive = false;
                            }
                            break;
                        case 0:
                            if (dim.z > 0.2)
                                dim.z -= 0.07f;
                            else
                            {
                                AddObj();
                                alive = false;
                            }
                            break;
                        default:
                            break;
                    }
                }
                else 
                {
                    if (dim.z > 0.2)
                        dim.z -= 0.07f;
                    else
                    {
                        AddObj();
                        alive = false;
                    }
                }
            }
            public void DoStep()
            {
                if (coordf.X - OFFSET_X> coord.X * OBJ_SIZE)
                    coordf.X -= 10;
                else if (coordf.X - OFFSET_X < coord.X * OBJ_SIZE)
                    coordf.X += 10;
                else
                    movingX = false;
                if (coordf.Y - OFFSET_Y < coord.Y * OBJ_SIZE)
                    coordf.Y += 1;
                else
                    movingY = false;

                if (rotX)
                {
                    angle += 3;
                    if (angle % 90 == 0)
                        rotX = false; 
                }
                if (rotZ)
                {
                    angle += 3;
                    if (angle % 90 == 0)
                        rotZ = false;
                }
                
            }
            public bool GoLeft()
            {
                coord.X--;
                if (Collision(0))
                {
                    coord.X++;
                    return false;
                }
                movingX = true;
                return true;
            }
            public bool GoRight()
            {
                coord.X++;
                if (Collision(1))
                {
                    coord.X--;
                    return false;
                }
                movingX = true;
                return true;
            }
            public bool GoDown()
            {
                coord.Y++;
                if (GoStop())
                {
                    coord.Y--;
                    if (coord.Y < 0)
                        EndGame();
                    coordf = new Point(OFFSET_X + coord.X * OBJ_SIZE, OFFSET_Y + coord.Y * OBJ_SIZE); 
                    StaticObj.Add(this);
                    alive = false;
                    pSndCrash.Play();
                    return false;
                }
                movingY = true;
                return true;
            }
            public bool RotateX()
            {
                if (rY != 0 &&  angle % 360 != 0)
                    return false;
                TPoint3 newpoint; 
                TPoint3[] buf = new TPoint3[map.Count()];
                ActiveObj.map.CopyTo(buf, 0);
                for (int i = 0; i < ActiveObj.map.Count(); i++)
                {
                    newpoint.x = 3 - map[i].y;//(map[i].y * 3) % 4;
                    newpoint.y = map[i].x;
                    newpoint.z = map[i].z;
                    map[i] = newpoint;
                }
                if (Collision(0) || Collision(1))
                {
                    buf.CopyTo(map, 0);
                    return false;
                }
                rotX = true;
                rY = 0;
                rZ = 1;
                pSndRotate.Play();
                return true;
            }
            public bool RotateZ()
            {
                if (rZ != 0 &&angle % 360 != 0)
                    return false;

                TPoint3 newpoint;
                TPoint3[] buf = new TPoint3[map.Count()];
                ActiveObj.map.CopyTo(buf, 0);
                for (int i = 0; i < ActiveObj.map.Count(); i++)
                {
                    newpoint.x = map[i].z;//(map[i].y * 3) % 4;
                    newpoint.y = map[i].y;
                    newpoint.z = 3 - map[i].x;
                    map[i] = newpoint;
                }
                if (Collision(0) || Collision(1))
                {
                    buf.CopyTo(map, 0);
                    return false;
                }

                rotZ = true;
                rY = 1;
                rZ = 0;
                pSndRotate.Play();
                return true;
            }
            public bool GoStop()
            {
                for (int i = 0; i < map.Count(); i++)
                {
                    if (coord.Y + map[i].y > 19)
                        return true;
                    for (int j = 0; j < StaticObj.Count(); j++)
                        for (int k = 0; k < StaticObj[j].map.Count(); k++)
                            if (coord.X + map[i].x == StaticObj[j].coord.X + StaticObj[j].map[k].x &&
                                coord.Y + map[i].y == StaticObj[j].coord.Y + StaticObj[j].map[k].y)
                                return true;
                }
                return false;
            }
            public bool Collision(int side)
            {
                for (int i = 0; i < map.Count(); i++)
                {
                    if (side == 0 && coord.X + map[i].x < 0
                        || side == 1 && coord.X + map[i].x > (SCREEN_WIDTH - OBJ_SIZE) / OBJ_SIZE)
                        return true;
                    for (int j = 0; j < StaticObj.Count(); j++)
                        for (int k = 0; k < StaticObj[j].map.Count(); k++)
                            if (coord.Y + map[i].y == StaticObj[j].coord.Y + StaticObj[j].map[k].y &&
                                (side == 0 && coord.X + map[i].x == StaticObj[j].coord.X + StaticObj[j].map[k].x
                                || side == 1 && coord.X + map[i].x == StaticObj[j].coord.X + StaticObj[j].map[k].x))
                                return true;
                }
                return false;
            }

            public void AddObj()
            {
                for (int i = 0; i < map.Count(); i++)
                    Field.field[coord.X + (int)map[i].x, coord.Y + (int)map[i].y] = _type + 1;

                Field.CheckLine();
            }

        };

        struct TField
        {
            public int[,] field;
            public void Draw()
            {
                for (int j = 0; j < 20; j++)
                    for (int i = 0; i < 10; i++)
                        if(field[i,j] != 0)
                            textures[field[i, j]-1].Draw2D(OFFSET_X + i * OBJ_SIZE, OFFSET_Y + j * OBJ_SIZE, OBJ_SIZE, OBJ_SIZE, 0, 0);

            }
            public void CheckLine()
            {
                for (int j = 0; j < 20; j++)
                    for (int i = 0; i < 10; i++)
                    {
                        if (field[i, j] == 0)
                            break;
                        if (i == 9)
                            DeleteLine(j);
                    }
            }
            public void DeleteLine(int line)
            {
                for (int i = 0; i < 10; i++)
                    field[i, line] = 0;
                for (int j = 0; j < StaticObj.Count(); j++)
                    for (int k = 0; k < StaticObj[j].map.Count(); k++)
                        if (StaticObj[j].coord.Y + StaticObj[j].map[k].y == line)
                            StaticObj[j].map[k].y += 1000;

                for (int y = line; y >= 1; y--)
                {
                    for (int i = 0; i < 10; i++)
                        field[i, y] = field[i, y - 1];
                    for (int j = 0; j < StaticObj.Count(); j++)
                        for (int k = 0; k < StaticObj[j].map.Count(); k++)
                            if (StaticObj[j].coord.Y + StaticObj[j].map[k].y == y)
                                StaticObj[j].map[k].y++;
                }
                pSndDelete.Play();
                score += 100;
            }
        }
        

              
       



        static void EndGame()
        {
            score = 0;
            bIsGame = false;
            Field.field = new int[20, 20];
            StaticObj.Clear();
            ActiveObj.coordf = new Point(10000, 1000);
            ActiveObj.alive = false;
        }
        
        static void ProcessTitle()
        {           
            TMouseStates mstate;
            pInput.GetMouseStates(out mstate);
            if (mstate.bLeftButton && mstate.iX > 820 && mstate.iX < 1015
                && mstate.iY > 285 && mstate.iY < 366)
                {
                    EndGame();
                    bIsGame = true;
                }
            else if (mstate.bLeftButton && mstate.iX > 842 && mstate.iX < 1017
                    && mstate.iY > 0 && mstate.iY < 90)
            {
                Application.Exit();
            }
        }

        static void RenderTitle()
        {
            pTexBegin.Draw2D(0, 0, 1024, 768, 0, 0);

        }

        static void ProcessGame()
        {
            if (!ActiveObj.alive)
                ActiveObj = new TObject(rand.Next(6));

            bool key_pressed;
            if (uiCounter % 5 == 0)
            {

                pInput.GetKey(E_KEYBOARD_KEY_CODES.KEY_LEFT, out key_pressed);
                if (key_pressed)
                    ActiveObj.GoLeft();

                pInput.GetKey(E_KEYBOARD_KEY_CODES.KEY_RIGHT, out key_pressed);
                if (key_pressed)
                    ActiveObj.GoRight();

                pInput.GetKey(E_KEYBOARD_KEY_CODES.KEY_UP, out key_pressed);
                if (key_pressed && !ActiveObj.rotX)
                {
                    ActiveObj.RotateX();
                }

                pInput.GetKey(E_KEYBOARD_KEY_CODES.KEY_SPACE, out key_pressed);
                if (key_pressed && !ActiveObj.rotZ)
                    ActiveObj.RotateZ();

                if (!ActiveObj.movingY)
                    ActiveObj.GoDown();
            }
            ActiveObj.DoStep();
            for (int i = 0; i < StaticObj.Count; i++)
                if (StaticObj[i].alive)
                {
                    TObject buf = StaticObj[i];
                    buf.DoStaticStep();
                    StaticObj[i] = buf;
                }
            pInput.GetKey(E_KEYBOARD_KEY_CODES.KEY_DOWN, out key_pressed);
            if (key_pressed)
                ActiveObj.coordf.Y += 5;
        }

        static void RenderGame()
        {
            TColor4 c = TColor4.White;

            c = TColor4.White;
            pRender2D.SetColorMix(ref c);

            pRender2D.SetBlendMode(E_EFFECT2D_BLENDING_FLAGS.EBF_NORMAL);
            pTexGame.Draw2D(0, 0, 1024, 768, 0, 0);
            Field.Draw();
            ActiveObj.Draw();
            foreach (TObject obj in StaticObj)
                if(obj.alive)
                    obj.Draw();
            pTexGameWindow.Draw2D(0, 0, 1024, 768, 0, 0);

            pFntMain.SetScale(1f);
            pFntMain.Draw2D(750, 320, score.ToString(), ref c, 0, false);
            pFntMain.SetScale(0.75f);  
            if (ActiveObj.rY == 0 || ActiveObj.angle % 360 == 0)
                pFntMain.Draw2D(720, 200, "Space - for Y-rotation", ref c, 0, false);
            if (ActiveObj.rZ == 0 || ActiveObj.angle % 360 == 0)
                pFntMain.Draw2D(720, 240, "Up - for Z-rotation", ref c, 0, false);

            
        
        }



        ~Form1()
        {
            Engine.FreeEngine();
        }
        public Form1()
        {
            InitializeComponent();

            Engine.GetEngine(DLL_PATH, out pEngineCore);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            if (pEngineCore != null)
            {
                TEngWindow window = new TEngWindow((UInt32)dpPanel1.Width, (UInt32)dpPanel1.Height, false, false, 1, E_ENG_WINDOW_FLAGS.EWF_DEFAULT);
                pEngineCore.InitializeEngine(dpPanel1.Handle, APP_CAPTION, ref window, 20,
#if DEBUG
 E_ENGINE_INIT_FLAGS.EIF_DEFAULT
#else      
                    E_ENGINE_INIT_FLAGS.EIF_CATCH_UNHANDLED
#endif
);

                DInit = Init;
                DFree = Free;
                DProcess = Process;
                DRender = Render;

                pEngineCore.AddProcedure(E_ENGINE_PROCEDURE_TYPE.EPT_INIT, DInit, this.Handle);
                pEngineCore.AddProcedure(E_ENGINE_PROCEDURE_TYPE.EPT_FREE, DFree, this.Handle);
                pEngineCore.AddProcedure(E_ENGINE_PROCEDURE_TYPE.EPT_PROCESS, DProcess, this.Handle);
                pEngineCore.AddProcedure(E_ENGINE_PROCEDURE_TYPE.EPT_RENDER, DRender, this.Handle);

                pEngineCore.StartEngine(); //Entering engine loop

            }
        }
        static void Init(IntPtr pParam)
        {
            //Get subsystems
            IEngineSubSystem p_sub_sys = null;

            pEngineCore.GetSubSystem(E_ENGINE_SUB_SYSTEM.ESS_RESOURCE_MANAGER, out p_sub_sys);
            pResourceManager = (IResourceManager)p_sub_sys;

            pEngineCore.GetSubSystem(E_ENGINE_SUB_SYSTEM.ESS_RENDER, out p_sub_sys);
            pRender = (IRender)p_sub_sys;

            pEngineCore.GetSubSystem(E_ENGINE_SUB_SYSTEM.ESS_INPUT, out p_sub_sys);
            pInput = (IInput)p_sub_sys;

            pInput.Configure(E_INPUT_CONFIGURATION_FLAGS.ICF_HIDE_CURSOR);

            pRender.GetRender2D(out pRender2D);

            //Load resources
            const string RESOURCE_PATH = @"Data.zip|";

            IEngBaseObj p_base_obj = null;



            pResourceManager.Load(RESOURCE_PATH+"lightBlue1.dmd", out p_base_obj, 0);
            meshes[0] = (IMesh)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH + "kirpich0.jpg", out p_base_obj, 0);
            //pResourceManager.Load(RESOURCE_PATH + "lightBlue1.tga", out p_base_obj, 0);
            textures[0] = (ITexture)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH+"red1.dmd", out p_base_obj, 0);
            meshes[1] = (IMesh)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH + "kirpich1.jpg", out p_base_obj, 0);
            //pResourceManager.Load(RESOURCE_PATH+"red1.tga", out p_base_obj, 0);
            textures[1] = (ITexture)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH+"blue1.dmd", out p_base_obj, 0);
            meshes[2] = (IMesh)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH + "kirpich2.jpg", out p_base_obj, 0);
            //pResourceManager.Load(RESOURCE_PATH+"blue1.tga", out p_base_obj, 0);
            textures[2] = (ITexture)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH+"green1.dmd", out p_base_obj, 0);
            meshes[3] = (IMesh)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH + "kirpich3.jpg", out p_base_obj, 0);
            //pResourceManager.Load(RESOURCE_PATH+"green1.tga", out p_base_obj, 0);
            textures[3] = (ITexture)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH+"yellow1.dmd", out p_base_obj, 0);
            meshes[4] = (IMesh)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH + "kirpich4.jpg", out p_base_obj, 0);
            //pResourceManager.Load(RESOURCE_PATH+"yellow1.tga", out p_base_obj, 0);
            textures[4] = (ITexture)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH+"purple1.dmd", out p_base_obj, 0);
            meshes[5] = (IMesh)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH + "kirpich5.jpg", out p_base_obj, 0);
            //pResourceManager.Load(RESOURCE_PATH+"purple1.tga", out p_base_obj, 0);
            textures[5] = (ITexture)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH+"orange1.dmd", out p_base_obj, 0);
            meshes[6] = (IMesh)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH + "kirpich6.jpg", out p_base_obj, 0);
            //pResourceManager.Load(RESOURCE_PATH+"orange1.tga", out p_base_obj, 0);
            textures[6] = (ITexture)p_base_obj;


            pResourceManager.Load(RESOURCE_PATH+"Start Screen.png", out p_base_obj, 0);
            pTexBegin = (ITexture)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH+"Play Screen.png", out p_base_obj, 0);
            pTexGame = (ITexture)p_base_obj;
            pResourceManager.Load(RESOURCE_PATH+"Play Screen Window.png", out p_base_obj, 0);
            pTexGameWindow = (ITexture)p_base_obj;



            pResourceManager.Load(RESOURCE_PATH+"crack.wav", out p_base_obj, 0);
            pSndCrash = (ISoundSample)p_base_obj;

            pResourceManager.Load(RESOURCE_PATH+"rotate.wav", out p_base_obj, 0);
            pSndRotate = (ISoundSample)p_base_obj;

            pResourceManager.Load(RESOURCE_PATH+"delete.wav", out p_base_obj, 0);
            pSndDelete = (ISoundSample)p_base_obj;

            pResourceManager.Load(RESOURCE_PATH+"01.mp3", out p_base_obj, 0);
            pMusic = (IMusic)p_base_obj;

            pResourceManager.Load(RESOURCE_PATH+"Bradley Hand ITC_28.dft", out p_base_obj, 0);
            pFntMain = (IBitmapFont)p_base_obj;

            //Start playing music
            pMusic.Play(true);
            pMusic.SetVolume(100);
        }

        static void Free(IntPtr pParam)
        {
        }

        static void Process(IntPtr pParam)
        {
            if (bIsGame)
                ProcessGame();
            else
                ProcessTitle();

            bool key_pressed;
            
            pInput.GetKey(E_KEYBOARD_KEY_CODES.KEY_ESCAPE, out key_pressed);
            if (key_pressed) 
                pEngineCore.QuitEngine();

            uiCounter++;
        }

        static void Render(IntPtr pParam)
        {
            pRender2D.Begin2D();

            if (bIsGame)
                RenderGame();
            else
                RenderTitle();

            pRender2D.End2D();
        }
    }
}
