using System;
using System.IO;
using System.Text;
using System.Windows.Forms;
using LibNDSFormats.NSBMD;
using LibNDSFormats.NSBTX;
using Microsoft.WindowsAPICodePack.Dialogs;
using Tao.OpenGl;
using static DSPRE.RomInfo;

namespace DSPRE {
    public partial class BuildingEditor : Form {
        #region Variables
        public static string temp_btxname = "BLDtexture.nsbtx";
        private readonly string folder;
        bool disableHandlers = new bool();
        readonly RomInfo rom;

        NSBMD currentNSBMD;
        byte[] currentModelData;

        readonly NSBMDGlRenderer renderer = new NSBMDGlRenderer();

        public static float ang = 0.0f;
        public static float dist = 12.8f;
        public static float elev = 50.0f;
        public static float tempAng = 0.0f;
        public static float tempDist = 0.0f;
        public static float tempElev = 0.0f;
        public float perspective = 45f;

        /* Bld Rotation vars */
        public bool lRot;
        public bool rRot;
        public bool uRot;
        public bool dRot;
        #endregion

        public BuildingEditor(RomInfo romInfo) {
            InitializeComponent();
            rom = romInfo;

            buildingOpenGLControl.InitializeContexts();
            buildingOpenGLControl.MakeCurrent();
            buildingOpenGLControl.MouseWheel += new MouseEventHandler(buildingOpenGLControl_MouseWheel);
            Gl.glEnable(Gl.GL_TEXTURE_2D);

            if (RomInfo.gameFamily == gFamEnum.HGSS) {
                interiorCheckBox.Enabled = true;
            }

            disableHandlers = true;
            FillListBox(false);
            FillTexturesBox();
            textureComboBox.SelectedIndex = 0;
            disableHandlers = false;
            buildingEditorBldListBox.SelectedIndex = 0;
        }

        #region Subroutines
        private void CreateEmbeddedTexturesFile(int modelID, bool interior) {
            string readingPath = folder + rom.GetBuildingModelsDirPath(interior) + "\\" + modelID.ToString("D4");

            byte[] txFile = File.ReadAllBytes(readingPath);
            byte[] texData = DSUtils.GetTexturesFromTexturedNSBMD(txFile);

            if (texData.Length <= 4) {
                Console.WriteLine("No textures found");
                return;
            }
            DSUtils.WriteToFile(Path.GetTempPath() + temp_btxname, texData, fmode: FileMode.Create);
        }
        private void FillListBox(bool interior) {
            int modelCount = Directory.GetFiles(folder + rom.GetBuildingModelsDirPath(interior)).Length;
            for (int currentIndex = 0; currentIndex < modelCount; currentIndex++) {
                string filePath = folder + rom.GetBuildingModelsDirPath(interior) + "\\" + currentIndex.ToString("D4");

                using (DSUtils.EasyReader reader = new DSUtils.EasyReader(filePath, 0x14)) {
                    string nsbmdName = DSUtils.ReadNSBMDname(reader);
                    buildingEditorBldListBox.Items.Add("[" + currentIndex.ToString("D3") + "] " + nsbmdName);
                }
            }
        }
        private void FillTexturesBox() {
            int texturesCount = Directory.GetFiles(folder + RomInfo.gameDirs[DirNames.buildingTextures].unpackedDir).Length;
            textureComboBox.Items.Add("Embedded textures");

            for (int i = 0; i < texturesCount; i++) {
                textureComboBox.Items.Add("Texture " + i);
            }
        }
        private void LoadModelTextures(int fileID) {
            string path;
            if (fileID > -1) {
                path = folder + RomInfo.gameDirs[RomInfo.DirNames.buildingTextures].unpackedDir + "\\" + fileID.ToString("D4");
            } else {
                path = Path.GetTempPath() + temp_btxname; // Load Embedded textures if the argument passed to this function is -1
            }

            try {
                currentNSBMD.materials = NSBTXLoader.LoadNsbtx(new MemoryStream(File.ReadAllBytes(path)), out currentNSBMD.Textures, out currentNSBMD.Palettes);
                currentNSBMD.MatchTextures();
            } catch { }
        }
        private void RenderModel() {
            MKDS_Course_Editor.NSBTA.NSBTA.NSBTA_File bta = new MKDS_Course_Editor.NSBTA.NSBTA.NSBTA_File();
            MKDS_Course_Editor.NSBTP.NSBTP.NSBTP_File btp = new MKDS_Course_Editor.NSBTP.NSBTP.NSBTP_File();
            MKDS_Course_Editor.NSBCA.NSBCA.NSBCA_File bca = new MKDS_Course_Editor.NSBCA.NSBCA.NSBCA_File();
            int[] aniframeS = new int[0];

            buildingOpenGLControl.Invalidate(); // Invalidate drawing surface
            SetupRenderer(ang, dist, elev, perspective); // Adjust rendering settings

            /* Render the building model */
            renderer.Model = currentNSBMD.models[0];
            Gl.glScalef(currentNSBMD.models[0].modelScale / 32, currentNSBMD.models[0].modelScale / 32, currentNSBMD.models[0].modelScale / 32);
            renderer.RenderModel("", bta, aniframeS, aniframeS, aniframeS, aniframeS, aniframeS, bca, false, -1, 0.0f, 0.0f, dist, elev, ang, true, btp, currentNSBMD);
        }
        private void SetupRenderer(float ang, float dist, float elev, float perspective) {
            Gl.glEnable(Gl.GL_RESCALE_NORMAL);
            Gl.glEnable(Gl.GL_COLOR_MATERIAL);
            Gl.glEnable(Gl.GL_DEPTH_TEST);
            Gl.glEnable(Gl.GL_NORMALIZE);
            Gl.glDisable(Gl.GL_CULL_FACE);
            Gl.glFrontFace(Gl.GL_CCW);
            Gl.glClearDepth(1);
            Gl.glEnable(Gl.GL_ALPHA_TEST);
            Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
            Gl.glEnable(Gl.GL_BLEND);
            Gl.glAlphaFunc(Gl.GL_GREATER, 0f);
            Gl.glClearColor(51f / 255f, 51f / 255f, 51f / 255f, 1f);
            float aspect;
            Gl.glViewport(0, 0, buildingOpenGLControl.Width, buildingOpenGLControl.Height);
            aspect = buildingOpenGLControl.Width / buildingOpenGLControl.Height;//(vp[2] - vp[0]) / (vp[3] - vp[1]);
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();
            Glu.gluPerspective(perspective, aspect, 0.2f, 500.0f);//0.02f, 32.0f);
            Gl.glTranslatef(0, 0, -dist);
            Gl.glRotatef(elev, 1, 0, 0);
            Gl.glRotatef(ang, 0, 1, 0);
            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();
            Gl.glTranslatef(0, 0, -dist);
            Gl.glRotatef(elev, 1, 0, 0);
            Gl.glRotatef(-ang, 0, 1, 0);
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, new float[] { 1, 1, 1, 0 });
            Gl.glLightfv(Gl.GL_LIGHT1, Gl.GL_POSITION, new float[] { 1, 1, 1, 0 });
            Gl.glLightfv(Gl.GL_LIGHT2, Gl.GL_POSITION, new float[] { 1, 1, 1, 0 });
            Gl.glLightfv(Gl.GL_LIGHT3, Gl.GL_POSITION, new float[] { 1, 1, 1, 0 });
            Gl.glLoadIdentity();
            Gl.glBindTexture(Gl.GL_TEXTURE_2D, 0);
            Gl.glColor3f(1.0f, 1.0f, 1.0f);
            Gl.glDepthMask(Gl.GL_TRUE);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
        }
        #endregion

        private void buildingOpenGLControl_MouseWheel(object sender, MouseEventArgs e) { // Zoom In/Out
            float val = (float)e.Delta / 200;
            
            if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift) {
                dist += val;
            } else {
                dist -= val;
            }

            RenderModel();
        }
        private void buildingEditorListBox_SelectedIndexChanged(object sender, EventArgs e) {
            if (disableHandlers || buildingEditorBldListBox.SelectedIndex < 0) {
                return;
            }

            string path = folder + rom.GetBuildingModelsDirPath(interiorCheckBox.Checked) + "\\" + buildingEditorBldListBox.SelectedIndex.ToString("D4");

            currentModelData = File.ReadAllBytes(path);
            currentNSBMD = NSBMDLoader.LoadNSBMD(new MemoryStream(currentModelData));

            CreateEmbeddedTexturesFile(buildingEditorBldListBox.SelectedIndex, interiorCheckBox.Checked);
            LoadModelTextures(textureComboBox.SelectedIndex - 1);
            RenderModel();
        }
        private void exportButton_Click(object sender, EventArgs e) {
            SaveFileDialog em = new SaveFileDialog {
                Filter = "NSBMD model (*.nsbmd)|*.nsbmd",
                FileName = buildingEditorBldListBox.SelectedItem.ToString()
            };
            if (em.ShowDialog(this) != DialogResult.OK) {
                return;
            }

            File.Copy(folder + rom.GetBuildingModelsDirPath(interiorCheckBox.Checked) + "\\" + buildingEditorBldListBox.SelectedIndex.ToString("D4"), em.FileName, true);
        }
        private void importButton_Click(object sender, EventArgs e) {
            OpenFileDialog im = new OpenFileDialog {
                Filter = "NSBMD model (*.nsbmd)|*.nsbmd"
            };
            if (im.ShowDialog(this) != DialogResult.OK) {
                return;
            }

            using (DSUtils.EasyReader reader = new DSUtils.EasyReader(im.FileName)) {
                if (reader.ReadUInt32() != NSBMD.NDS_TYPE_BMD0) {
                    MessageBox.Show("Please select an NSBMD file.", "Invalid File", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                } else {
                    int currentIndex = buildingEditorBldListBox.SelectedIndex;

                    File.Copy(im.FileName, folder + rom.GetBuildingModelsDirPath(interiorCheckBox.Checked) + "\\" + currentIndex.ToString("D4"), true);
                    buildingEditorBldListBox.Items[currentIndex] = "[" + currentIndex.ToString("D3") + "] " + DSUtils.ReadNSBMDname(reader, 0x14);
                    buildingEditorListBox_SelectedIndexChanged(null, null);
                }
            }
        }
        private void interiorCheckBox_CheckedChanged(object sender, EventArgs e) {
            disableHandlers = true;

            buildingEditorBldListBox.Items.Clear();
            FillListBox(interiorCheckBox.Checked);

            disableHandlers = false;

            buildingEditorBldListBox.SelectedIndex = 0;
        }
        private void textureComboBox_SelectedIndexChanged(object sender, EventArgs e) {
            if (disableHandlers) {
                return;
            }
            LoadModelTextures(textureComboBox.SelectedIndex - 1);
            RenderModel();
        }
        private void buildingOpenGLControl_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) {
            byte multiplier = 2;
            if (e.Modifiers == Keys.Shift) {
                multiplier = 1;
            } else if (e.Modifiers == Keys.Control) {
                multiplier = 4;
            }

            switch (e.KeyCode) {
                case Keys.Right:
                    rRot = true;
                    lRot = false;
                    break;
                case Keys.Left:
                    rRot = false;
                    lRot = true;
                    break;
                case Keys.Up:
                    dRot = false;
                    uRot = true;
                    break;
                case Keys.Down:
                    dRot = true;
                    uRot = false;
                    break;
            }

            if (rRot ^ lRot) {
                if (rRot) {
                    ang += 1 * multiplier;
                } else if (lRot) {
                    ang -= 1 * multiplier;
                }
            }

            if (uRot ^ dRot) {
                if (uRot) {
                    elev -= 1 * multiplier;
                } else if (dRot) {
                    elev += 1 * multiplier;
                }
            }
            RenderModel();
        }

        private void bldExportDAEbutton_Click(object sender, EventArgs e) {
            DSUtils.ModelToDAE(
                modelName: buildingEditorBldListBox.SelectedItem.ToString().TrimEnd('\0'), 
                modelData: currentModelData, 
                textureData: textureComboBox.SelectedIndex < 1 ? null : File.ReadAllBytes(RomInfo.gameDirs[DirNames.buildingTextures].unpackedDir + "\\" + (textureComboBox.SelectedIndex - 1).ToString("D4"))
            );
        }

        private void buildingOpenGLControl_KeyUp(object sender, KeyEventArgs e) {
            switch (e.KeyCode) {
                case Keys.Right:
                    rRot = false;
                    break;
                case Keys.Left:
                    lRot = false;
                    break;
                case Keys.Up:
                    uRot = false;
                    break;
                case Keys.Down:
                    dRot = false;
                    break;
            }
        }
    }
}
