using System;
using System.Collections.Generic;
using System.Text;
using IrrlichtNetSwig;


namespace Quake3MapShader
{
    class Program
    {
        private static bool IRRLICHT_QUAKE3_ARENA = true;
        private static bool ORIGINAL_QUAKE3_ARENA = false;
        private static bool CUSTOM_QUAKE3_ARENA = false;
        private static bool SHOW_SHADER_NAME = true;
        private static string QUAKE3_STORAGE_1 = "";
        private static string QUAKE3_STORAGE_2 = "";
        private static string QUAKE3_MAP_NAME = "";

        static  bool QUAKE3_STORAGE_FORMAT(IFileSystem fileSystem, string fileName)
        {
            if(ORIGINAL_QUAKE3_ARENA)
            {
                return fileSystem.addFolderFileArchive(fileName);
            }
            if(IRRLICHT_QUAKE3_ARENA)
            {
                return fileSystem.addZipFileArchive(fileName);
            }
            return false;
        }

        static void Main(string[] args)
        {
            if (ORIGINAL_QUAKE3_ARENA)
            {
                //QUAKE3_STORAGE_FORMAT = addFolderFileArchive;
                QUAKE3_STORAGE_1 = "/baseq3/";
                if (CUSTOM_QUAKE3_ARENA)
                {
                    QUAKE3_STORAGE_2 = "/cf/";
                    QUAKE3_MAP_NAME = "maps/cf.bsp";
                }
                else
                {
                    QUAKE3_MAP_NAME = "maps/q3dm8.bsp";
                }
            }

            if (IRRLICHT_QUAKE3_ARENA)
            {
                //QUAKE3_STORAGE_FORMAT=addZipFileArchive;
                QUAKE3_STORAGE_1 = "../../media/map-20kdm2.pk3";
                QUAKE3_MAP_NAME = "maps/20kdm2.bsp";
            }



            /*
	        Like in the HelloWorld example, we create an IrrlichtDevice with
	        createDevice(). The difference now is that we ask the user to select
	        which hardware accelerated driver to use. The Software device would be
	        too slow to draw a huge Quake 3 map, but just for the fun of it, we make
	        this decision possible too.
	        */

            // ask user for driver

            E_DRIVER_TYPE driverType;

            Console.WriteLine(
                @"Please select the driver you want for this example:
(a) Direct3D 9.0c
(b) Direct3D 8.1
(c) OpenGL 1.5
(d) Software Renderer
(e) Burning's Software Renderer
(f) NullDevice
(otherKey) exit");

            var keyInfo = Console.ReadKey(true);

            switch (keyInfo.KeyChar)
            {
                case 'a':
                    driverType = E_DRIVER_TYPE.EDT_DIRECT3D9;
                    break;
                case 'b':
                    driverType = E_DRIVER_TYPE.EDT_DIRECT3D8;
                    break;
                case 'c':
                    driverType = E_DRIVER_TYPE.EDT_OPENGL;
                    break;
                case 'd':
                    driverType = E_DRIVER_TYPE.EDT_SOFTWARE;
                    break;
                case 'e':
                    driverType = E_DRIVER_TYPE.EDT_BURNINGSVIDEO;
                    break;
                case 'f':
                    driverType = E_DRIVER_TYPE.EDT_NULL;
                    break;
                default:
                    return;
            }

            // create device and exit if creation failed
            dimension2di videoDim = new dimension2di(800, 600);

            IrrlichtDevice device = IrrlichtNet.createDevice(driverType, videoDim, 32, false);

            if (device == null)
                return; // could not create selected driver.

            string mapname = "";
            if (args.Length > 2)
                mapname = args[2];
            else
                mapname = QUAKE3_MAP_NAME;

            /*
	        Get a pointer to the video driver and the SceneManager so that
	        we do not always have to write device.getVideoDriver() and
	        device.getSceneManager().
	        */
            IVideoDriver driver = device.getVideoDriver();
            ISceneManager smgr = device.getSceneManager();
            IGUIEnvironment gui = device.getGUIEnvironment();

            //! add our private media directory to the file system
            device.getFileSystem().addFolderFileArchive("../../media/");

            /*
	        To display the Quake 3 map, we first need to load it. Quake 3 maps
	        are packed into .pk3 files, which are nothing other than .zip files.
	        So we add the .pk3 file to our FileSystem. After it was added,
	        we are able to read from the files in that archive as they would
	        directly be stored on disk.
	        */
            if (args.Length > 2)
            {
                QUAKE3_STORAGE_FORMAT(device.getFileSystem(), args[1]);
            }
            else
            {
                QUAKE3_STORAGE_FORMAT(device.getFileSystem(), QUAKE3_STORAGE_1);
            }
            if (QUAKE3_STORAGE_2 != "")
            {
                QUAKE3_STORAGE_FORMAT(device.getFileSystem(), QUAKE3_STORAGE_2);
            }




            /*
	        Now we can load the mesh by calling getMesh(). We get a pointer returned
	        to a IAnimatedMesh. As you know, Quake 3 maps are not really animated,
	        they are only a huge chunk of static geometry with some materials
	        attached. Hence the IAnimated mesh consists of only one frame,
	        so we get the "first frame" of the "animation", which is our quake level
	        and create an OctTree scene node with it, using addOctTreeSceneNode().
	        The OctTree optimizes the scene a little bit, trying to draw only geometry
	        which is currently visible. An alternative to the OctTree would be a
	        AnimatedMeshSceneNode, which would draw always the complete geometry of
	        the mesh, without optimization. Try it out: Write addAnimatedMeshSceneNode
	        instead of addOctTreeSceneNode and compare the primitives drawed by the
	        video driver. (There is a getPrimitiveCountDrawed() method in the
	        IVideoDriver class). Note that this optimization with the Octree is only
	        useful when drawing huge meshes consisting of lots of geometry.
	        */
            IQ3LevelMesh mesh =
                IQ3LevelMesh.cast(smgr.getMesh(mapname));

            /*
		        add the geometry mesh to the Scene ( polygon & patches )
		        The Geometry mesh is optimised for faster drawing
	        */
            ISceneNode node = null;
            if (mesh != null)
            {
                IMesh geometry = mesh.getMesh((int) eQ3MeshIndex.E_Q3_MESH_GEOMETRY);
//		        node = smgr.addMeshSceneNode ( geometry );
                node = smgr.addOctTreeSceneNode(geometry, null, -1, 1024);
            }

            // create an event receiver for making screenshots
            CScreenShotFactory screenshotFactory = new CScreenShotFactory(device, mapname, node);
            device.setEventReceiver(screenshotFactory);

            /*
		        now construct SceneNodes for each Shader
		        The Objects are stored in the quake mesh E_Q3_MESH_ITEMS
		        and the Shader ID is stored in the MaterialParameters
		        mostly dark looking skulls and moving lava.. or green flashing tubes?
	        */
            if (mesh != null)
            {
                // the additional mesh can be quite huge and is unoptimized
                IMesh additional_mesh = mesh.getMesh((int) eQ3MeshIndex.E_Q3_MESH_ITEMS);

                uint count = 0;
                IGUIFont font = null;
                if (SHOW_SHADER_NAME)
                {
                    font = device.getGUIEnvironment().getFont("../../media/fontlucida.png");
                    count = 0;
                }

                for (uint i = 0; i != additional_mesh.getMeshBufferCount(); ++i)
                {
                    IMeshBuffer meshBuffer = additional_mesh.getMeshBuffer(i);
                    SMaterial material = meshBuffer.getMaterial();

                    //! The ShaderIndex is stored in the material parameter
                    uint shaderIndex = (uint) material.MaterialTypeParam2;

                    // the meshbuffer can be rendered without additional support, or it has no shader
                    SShader shader = mesh.getShader(shaderIndex);
                    if (null == shader)
                    {
                        continue;
                    }

                    // we can dump the shader to the console in its
                    // original but already parsed layout in a pretty
                    // printers way.. commented out, because the console
                    // would be full...
                    // quake3::dumpShader ( Shader );

                    if (!SHOW_SHADER_NAME)
                    {
                        smgr.addQuake3SceneNode(meshBuffer, shader);
                    }
                    else
                    {
                        // Now add the MeshBuffer(s) with the current Shader to the Manager
#if false
			            if (	shader.name != "textures/cf/window-decal") continue;
#endif
                        if (0 == count)
                        {
                            string s = "";
                            //quake3::dumpShader ( s, shader );
                            Console.WriteLine(s);
                        }
                        count += 1;

                        node = smgr.addQuake3SceneNode(meshBuffer, shader);

                        string name = node.getName();
                        node = smgr.addBillboardTextSceneNode(
                            font,
                            name,
                            node,
                            new dimension2df(80.0f, 8.0f),
                            new vector3df(0, 10, 0)
                            );
                    }
                }


                // original mesh is not needed anymore
                mesh.releaseMesh((int)eQ3MeshIndex.E_Q3_MESH_ITEMS);
            }

            /*
	        Now we only need a Camera to look at the Quake 3 map. And we want to
	        create a user controlled camera. There are some different cameras
	        available in the Irrlicht engine. For example the Maya Camera which can
	        be controlled compareable to the camera in Maya: Rotate with left mouse
	        button pressed, Zoom with both buttons pressed, translate with right
	        mouse button pressed. This could be created with
	        addCameraSceneNodeMaya(). But for this example, we want to create a
	        camera which behaves like the ones in first person shooter games (FPS).
	        */

            ICameraSceneNode camera = smgr.addCameraSceneNodeFPS();

            /*
		    so we need a good starting Position in the level.
		    we can ask the Quake3 Loader for all entities with class_name
		    "info_player_deathmatch"
		    we choose a random launch
    		
	        */
            if (mesh != null)
            {
                arrayQuake3SShader entityList = mesh.getEntityList();

                SShader search = new SShader();
                search.name = IrrStringc.Factory("info_player_deathmatch");
                int index = entityList.binary_search_const(search);
                if (index >= 0)
                {
                    SVarGroup group = new SVarGroup();
                    bool notEndList;
                    do
                    {
                        group = entityList.getItem((uint) index).getGroup(1);

                        uint parsepos = 0;
                        vector3df pos =
                            IrrlichtNet.getAsVector3df(group.get("origin"), ref parsepos);

                        parsepos = 0;
                        float angle = IrrlichtNet.getAsFloat(group.get("angle"), ref parsepos);

                        vector3df target = new vector3df(0.0f, 0.0f, 1.0f);
                        target.rotateXZBy(angle, new vector3df());

                        camera.setPosition(pos);
                        camera.setTarget(pos + target);

                        index += 1;
/*
				        notEndList = (	index < (s32) entityList.size () &&
								        entityList[index].name == search.name &&
								        (device.getTimer().getRealTime() >> 3 ) & 1
							        );
*/
                        notEndList = (index == 2);
                    } while (notEndList);
                }

            }

            /*
	        The mouse cursor needs not to be visible, so we make it invisible.
	        */

            device.getCursorControl().setVisible(false);

            // load the engine logo
            gui.addImage(driver.getTexture("irrlichtlogo2.png"),
                         new position2di(10, 10));

            // show the driver logo
            position2di pos2 = new position2di(videoDim.Width - 128, videoDim.Height - 64);

            switch (driverType)
            {
                case E_DRIVER_TYPE.EDT_BURNINGSVIDEO:
                    gui.addImage(driver.getTexture("burninglogo.png"), pos2);
                    break;
                case E_DRIVER_TYPE.EDT_OPENGL:
                    gui.addImage(driver.getTexture("opengllogo.png"), pos2);
                    break;
                case E_DRIVER_TYPE.EDT_DIRECT3D8:
                case E_DRIVER_TYPE.EDT_DIRECT3D9:
                    gui.addImage(driver.getTexture("directxlogo.png"), pos2);
                    break;
            }

            /*
	        We have done everything, so lets draw it. We also write the current
	        frames per second and the drawn primitives to the caption of the
	        window. The 'if (device.isWindowActive())' line is optional, but
	        prevents the engine render to set the position of the mouse cursor
	        after task switching when other program are active.
	        */
            int lastFPS = -1;

            while (device.run())
                if (device.isWindowActive())
                {
                    driver.beginScene(true, true, new SColor(255, 20, 20, 40));
                    smgr.drawAll();
                    gui.drawAll();

                    driver.endScene();

                    int fps = driver.getFPS();

                    //if (lastFPS != fps)
                    {
                        IAttributes attr = smgr.getParameters();

                        int calls = attr.getAttributeAsInt("calls");
                        int culled = attr.getAttributeAsInt("culled");

                        string str = "Q3 [";
                        str += driver.getName();
                        str += "] FPS:";
                        str += fps;
                        str += " Cull:";
                        str += calls;
                        str += "/";
                        str += culled;

                        device.setWindowCaption(str);
                        lastFPS = fps;
                    }
                }

            /*
	        In the end, delete the Irrlicht device.
	        */
            device.drop();

            return;
        }
    }
    //! produces a serie of screenshots
    class CScreenShotFactory : IEventReceiver
    {
        public CScreenShotFactory(IrrlichtDevice device, string templateName, ISceneNode node)
        {
            Device = device;
            Number = 0;
            FilenameTemplate = templateName;
            Node = node;
            FilenameTemplate = FilenameTemplate.Replace('/', '_');
            FilenameTemplate = FilenameTemplate.Replace('\\', '_');
        }

        public override bool OnEvent(SEvent ev)
        {
            // check if user presses the key F9
            if ((ev.EventType == EEVENT_TYPE.EET_KEY_INPUT_EVENT) &&
                    ev.KeyInput.PressedDown)
            {
                if (ev.KeyInput.Key == EKEY_CODE.KEY_F9)
                {
                    IImage image = Device.getVideoDriver().createScreenShot();
                    if (image != null)
                    {
                        string buf;
                        buf = string.Format("{0}_shot{1:D4}.jpg",
                                FilenameTemplate,
                                ++Number);
                        Device.getVideoDriver().writeImageToFile(image, buf, 85);
                        image.drop();
                    }
                }
                else
                    if (ev.KeyInput.Key == EKEY_CODE.KEY_F8)
                    {
                        Node.setDebugDataVisible((int)E_DEBUG_SCENE_TYPE.EDS_BBOX_ALL);
                    }
            }
            return false;
        }

        private IrrlichtDevice Device;
        private uint Number;
        private string FilenameTemplate;
        private ISceneNode Node;
    }

}
