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 System.IO;

namespace Sentience
{
    public partial class FormMain : Form
    {
        DisplayPanel mainDisplay;
        Map Map;
        bool mapLoaded = false;
        bool updatingGUI = false;

        Random randomNumber;

        public FormMain()
            : this(String.Empty) { }

        public FormMain(String loadFile)
        {
            InitializeComponent();

            mainDisplay = new DisplayPanel();
            mainDisplay.Name = "mainDisplay";
            mainDisplay.Paint += new PaintEventHandler(UpdateDisplay);
            mainDisplay.Dock = DockStyle.Fill;
            pnlDisplay.Controls.Add(mainDisplay);

            Map = new Map();
            randomNumber = new Random();

            UpdateGUI();

            if (!String.IsNullOrEmpty(loadFile))
                LoadMap(loadFile);
        }

        /// <summary>
        /// Applies the console update string to the control listbox.
        /// </summary>
        /// <param name="e">The console message.</param>
        void UpdateConsole(String e)
        {
            lstConsole.Items.Insert(0, Utils.GetTimeStamp(e));
        }

        /// <summary>
        /// Refreshes the display panel.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tmrRefresh_Tick(object sender, EventArgs e)
        {
            pnlDisplay.Controls[mainDisplay.Name].Refresh();
        }

        /// <summary>
        /// Updates the view controls and draws the map.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateDisplay(object sender, PaintEventArgs e)
        {
            Map.ShowConnections = mnuShowConnections.Checked;
            Map.ShowNodes = mnuShowNodes.Checked;
            Map.ShowPaths = mnuShowPaths.Checked;
            Map.ShowEntityData = mnuShowEntityData.Checked;
            Map.ShowViewRadius = mnuShowView.Checked;

            Map.Draw(e.Graphics);
        }

        /// <summary>
        /// Updates the data in the entity and team status tables.
        /// </summary>
        private void UpdateStatus()
        {
            for (int i = 0; i < lstStatus.Rows.Count; i++)
			{
                Entity thisEntity = Map.FindEntity(int.Parse(lstStatus.Rows[i].Cells["colID"].Value.ToString()));

                if (thisEntity != null)
                {
                    lstStatus.Rows[i].Cells["colLocation"].Value = thisEntity.GetLocationLabel();
                    lstStatus.Rows[i].Cells["colState"].Value = thisEntity.State;
                }
			}

            for (int i = 0; i < lstTeamStatus.Rows.Count; i++)
            {
                Team thisTeam = Map.FindTeam(lstTeamStatus.Rows[i].Cells["colName"].Value.ToString());

                lstTeamStatus.Rows[i].Cells["colScore"].Value = thisTeam.Score;

                lstTeamStatus.Rows[i].Cells["colTactic"].Value = thisTeam.Tactic;
                lstTeamStatus.Rows[i].Cells["colBehavior"].Value = thisTeam.Behavior.Name;

                lstTeamStatus.Rows[i].Cells["colColor"].Style.BackColor = thisTeam.Color;
                lstTeamStatus.Rows[i].Cells["colColor"].Style.SelectionBackColor = thisTeam.Color;
            }
        }

        /// <summary>
        /// Updates all the controls in the application.
        /// </summary>
        private void UpdateGUI()
        {
            updatingGUI = true;

            if (mapLoaded)
            {
                txtMapName.Text = Map.Name;

                // Set the minimum and maximum mapsize controls.
                numWidth.Minimum = Defaults.MinMapSize.Width;
                numHeight.Minimum = Defaults.MinMapSize.Height;

                numWidth.Maximum = Defaults.MaxMapSize.Width;
                numHeight.Maximum = Defaults.MaxMapSize.Height;

                // Set Designer team button availiblity.
                btnEditTeam.Enabled = false;
                btnRemoveTeam.Enabled = false;
                btnAddTeam.Enabled = (Map.Teams.Count < Defaults.MaxTeams);

                // Refresh list of teams in Designer.
                lstTeams.Rows.Clear();

                foreach (Team Team in Map.Teams)
                {
                    String memberCount = Team.Members + " Member";

                    if (Team.Members != 1)
                        memberCount += "s";

                    // Add team to list in Designer.
                    lstTeams.Rows.Add("", Team.Name, memberCount, Team.Behavior.Name);

                    lstTeams[0, lstTeams.Rows.Count - 1].Style.BackColor = Team.Color;
                    lstTeams[0, lstTeams.Rows.Count - 1].Style.SelectionBackColor = Team.Color;

                    btnEditTeam.Enabled = true;
                    btnRemoveTeam.Enabled = true;
                }

                // Add map blocks to block selector.
                cmbCellBlocks.Items.Clear();

                foreach (IBlock Block in Map.Blocks)
                    cmbCellBlocks.Items.Add(Block.Label);

                if (cmbCellBlocks.Items.Count > 0)
                    cmbCellBlocks.SelectedIndex = 0;

                // Set the Designer grid to the correct size.
                mapGrid.Rows.Clear();
                mapGrid.Columns.Clear();

                for (int i = 0; i < Map.Size.Width; i++)
                    mapGrid.Columns.Add(i.ToString(), "");

                mapGrid.Rows.Add(Map.Size.Height);

                // Set the mapsize controls to the current map size.
                numWidth.Value = Map.Size.Width;
                numHeight.Value = Map.Size.Height;

                // Add the map data to the Designer grid.
                int colNumber = 0;
                int rowNumber = 0;

                for (int i = 0; i < Map.Graph.Nodes.Count; i++)
                {
                    mapGrid[colNumber, rowNumber].Style.BackColor = Map.Graph.Nodes[i].Data.Color;

                    if (mapGrid[colNumber, rowNumber].Style.BackColor == Color.Empty)
                        mapGrid[colNumber, rowNumber].Style.BackColor = Color.White;

                    if (Properties.Settings.Default.ShowBlockLabels)
                        foreach (IBlock Block in Map.Blocks)
                            if (mapGrid[colNumber, rowNumber].Style.BackColor == Block.Color)
                                mapGrid[colNumber, rowNumber].Value = Block.ID.ToUpper();

                    colNumber++;

                    if (colNumber >= Map.Size.Width)
                    {
                        colNumber = 0;
                        rowNumber++;
                    }
                }

                // Set the display panel size.
                pnlDisplayContainer.Height = Map.Size.Height * Defaults.DefaultNodeSize + (Defaults.DefaultNodeOffset.Height*2);
                grpMapDisplay.Width = Map.Size.Width * Defaults.DefaultNodeSize + (Defaults.DefaultNodeOffset.Width);

                // Update entity status table.
                int selectedRow = 0;

                if (lstStatus.SelectedRows.Count > 0)
                    selectedRow = lstStatus.SelectedRows[0].Index;

                lstStatus.Rows.Clear();

                foreach (Entity Entity in Map.Entities)
                    lstStatus.Rows.Add(Entity.ID, Entity.Team.Name, Entity.GetLocationLabel(), Entity.State);

                if (lstStatus.Rows.Count > selectedRow)
                    if (lstStatus.SelectedRows.Count > 0)
                        lstStatus.Rows[selectedRow].Selected = true;

                // Update team status table.
                selectedRow = 0;

                if (lstTeamStatus.SelectedRows.Count > 0)
                    selectedRow = lstTeamStatus.SelectedRows[0].Index;

                lstTeamStatus.Rows.Clear();

                foreach (Team Team in Map.Teams)
                    lstTeamStatus.Rows.Add("", Team.Name, Team.Score, Team.Behavior.Name, "");

                if (lstTeamStatus.Rows.Count > selectedRow)
                    if (lstTeamStatus.SelectedRows.Count > 0)
                        lstTeamStatus.Rows[selectedRow].Selected = true;

                // Update tactics selector.
                cmbTactics.Items.Clear();

                String[] tacticNames = Enum.GetNames(typeof(Tactic));

                for (int i = 0; i < tacticNames.Length; i++)
                    cmbTactics.Items.Add(tacticNames[i]);

                if (cmbTactics.Items.Count > 0)
                    cmbTactics.SelectedIndex = 0;

                // Set accessibility of tactic selector controls.
                cmbTactics.Enabled = (Map.Teams.Count != 0);
                btnSetTactic.Enabled = (Map.Teams.Count != 0);
                chkApplyAll.Enabled = (Map.Teams.Count != 0);
                btnResetScores.Enabled = (Map.Teams.Count != 0);
            }

            // Disable these controls if there is no map loaded.
            mnuSaveMap.Enabled = mapLoaded;
            mnuReloadMap.Enabled = mapLoaded;
            mnuSaveMapAs.Enabled = mapLoaded;
            mnuClose.Enabled = mapLoaded;
            mnuShowConnections.Enabled = mapLoaded;
            mnuShowNodes.Enabled = mapLoaded;
            mnuShowPaths.Enabled = mapLoaded;
            mnuShowEntityData.Enabled = mapLoaded;
            mnuShowView.Enabled = mapLoaded;

            // Show or hide the console.
            cnrStatus.Panel2Collapsed = !Properties.Settings.Default.ShowConsole;

            mnuSaveMap.Enabled = (Map.File != null);
            tabMain.Visible = mapLoaded;
            updatingGUI = false;
        }

        /// <summary>
        /// Resets the GUI controls.
        /// </summary>
        private void ResetGUI()
        {
            cmbCellBlocks.Items.Clear();
            lstTeams.Rows.Clear();

            mapGrid.Rows.Clear();
            mapGrid.Columns.Clear();
        }

        /// <summary>
        /// Loads a map from a specified path or asks the user for file to load.
        /// </summary>
        /// <param name="readPath">The path to the file.</param>
        private void LoadMap(String readPath)
        {
            mapLoaded = false;
            UpdateGUI();

            // Get user input on a file path if none is provided.
            if (String.IsNullOrEmpty(readPath))
                readPath = Utils.OpenFile("Sentience Map Files | *.snp");

            if (readPath == null)
                return;

            UpdateConsole(String.Format("Loading Map File '{0}'...", Path.GetFileName(readPath)));
            Map loadedMap;

            // Load and test the map to make sure it is valid.
            try
            {
                loadedMap = Map.Load(readPath);

                loadedMap.Console += new ConsoleHandler(UpdateConsole);
                loadedMap.Create();
                Map.Checker(loadedMap);
            }
            catch (Exception ex)
            {
                MessageBox.Show("There was an error detected in the file '" + Path.GetFileName(readPath) + "' and\nthe map could not be loaded. The error found was..\n\n" + ex.Message,
                    "Error Loading Map",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
                return;
            }

            // Apply the new loaded map.
            Map = loadedMap;
            UpdateConsole("Map Loaded Successfully");

            mapLoaded = true;
            UpdateGUI();
        }

        /// <summary>
        /// Load a new map.
        /// </summary>
        private void mnuLoadMap_Click(object sender, EventArgs e)
        {
            LoadMap(null);
        }

        /// <summary>
        /// Paints the selected blocks as the block selector item.
        /// </summary>
        private void btnSetBlock_Click(object sender, EventArgs e)
        {
            DataGridViewSelectedCellCollection selectedCells = mapGrid.SelectedCells;

            IBlock setBlock = Map.Blocks[cmbCellBlocks.SelectedIndex];

            for (int i = 0; i < selectedCells.Count; i++)
            {
                selectedCells[i].Style.BackColor = setBlock.Color;
                selectedCells[i].Value = setBlock.ID.ToUpper();
            }

            Map.ApplyDesign(mapGrid);
        }

        /// <summary>
        /// Makes the Designer grid selection work with single and multi-select blocks.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmbCellBlocks_SelectedIndexChanged(object sender, EventArgs e)
        {
            IBlock setBlock = Map.Blocks[cmbCellBlocks.SelectedIndex];

            if (setBlock.MultiSelect != mapGrid.MultiSelect)
            {
                DataGridViewCell newSelection = mapGrid.SelectedCells[0];
                mapGrid.ClearSelection();
                mapGrid.MultiSelect = !mapGrid.MultiSelect;

                mapGrid.CurrentCell = newSelection;
                mapGrid.CurrentCell.Selected = true;
            }
        }

        /// <summary>
        /// Disable the block set button if there are no cells selected.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mapGrid_SelectionChanged(object sender, EventArgs e)
        {
            if (mapGrid.SelectedCells.Count <= 0)
                btnSetBlock.Enabled = false;
            else
                btnSetBlock.Enabled = true;
        }

        /// <summary>
        /// Save the map.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuSaveMap_Click(object sender, EventArgs e)
        {
            Map.Write(Map, Map.File);
        }
        
        /// <summary>
        /// Save the map as a perticular file.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuSaveMapAs_Click(object sender, EventArgs e)
        {
            String writePath =  Utils.SaveFile("Sentience Map Files | *.snp");

            if (writePath == null)
                return;

            Map.Write(Map, writePath);
        }

        /// <summary>
        /// Modify the Designer grid and map size.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void numSize_ValueChanged(object sender, EventArgs e)
        {
            if (!updatingGUI)
            {
                // Some strange things occur with the DataGridView control if columns are added when there are
                // non-blank blocks in the grid, so the map has to be blank to stop these effects from occuring.
                if (!Map.IsMapBlank())
                    if (MessageBox.Show(this, "Modifying the map size will erase any block data.\nAre you sure you want to continue?", "Warning",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No)
                    {
                        updatingGUI = true;

                        numWidth.Value = Map.Size.Width;
                        numHeight.Value = Map.Size.Height;

                        updatingGUI = false;
                        return;
                    }

                Map.Size = new Size((int)numWidth.Value, (int)numHeight.Value);
                Map.Create();

                UpdateGUI();
            }

            
        }

        /// <summary>
        /// Creates a new map.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuNewMap_Click(object sender, EventArgs e)
        {
            Map = new Map();
            Map.Console += new ConsoleHandler(UpdateConsole);
            Map.Create();

            mapLoaded = true;
            UpdateGUI();
        }

        /// <summary>
        /// Adds a team.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnAddTeam_Click(object sender, EventArgs e)
        {
            FormTeam formTeam = new FormTeam(Map.Teams.Count, null, Map.Behaviors, Map.Teams);

            if (formTeam.ShowDialog(this) != DialogResult.Cancel)
            {
                Map.Teams.Add(formTeam.Team);
                Map.Blocks.Add(new SpawnBlock(Map, formTeam.Team));
                Map.UpdateEntities();
                UpdateGUI();
            }
        }

        /// <summary>
        /// Edits an existing team.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnEditTeam_Click(object sender, EventArgs e)
        {
            Team editTeam = Map.Teams[lstTeams.SelectedRows[0].Index];

            FormTeam formTeam = new FormTeam(editTeam.ID, editTeam, Map.Behaviors, Map.Teams);

            if (formTeam.ShowDialog(this) != DialogResult.Cancel)
            {
                Map.UpdateEntities();
                UpdateGUI();
            }
        }

        /// <summary>
        /// Removes an existing team.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRemoveTeam_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show(FormMain.ActiveForm, "Removing a team will erase all related entities\nand spawn points and cannot be undone.\nAre you sure you want to continue?", "Warning",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
            {
                Map.RemoveTeam(Map.Teams[lstTeams.SelectedCells[0].RowIndex]);
                Map.UpdateEntities();
                UpdateGUI();
            }
        }

        /// <summary>
        /// Shows the About window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuAbout_Click(object sender, EventArgs e)
        {
            FormAbout formAbout = new FormAbout();
            formAbout.ShowDialog(this);
        }

        /// <summary>
        /// Updates the status on the timer tick interval.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tmrStatus_Tick(object sender, EventArgs e)
        {
            UpdateStatus();
        }

        /// <summary>
        /// Sets the tactic of the selected teams from the tactic selector.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSetTactic_Click(object sender, EventArgs e)
        {
            Tactic newTactic = (Tactic)Enum.Parse(typeof(Tactic), cmbTactics.SelectedItem.ToString());

            if (!chkApplyAll.Checked)
            {
                foreach (DataGridViewRow Row in lstTeamStatus.SelectedRows)
                    Map.FindTeam(Row.Cells["colName"].Value.ToString()).Tactic = newTactic;

                return;
            }

            foreach (DataGridViewRow Row in lstTeamStatus.Rows)
                Map.FindTeam(Row.Cells["colName"].Value.ToString()).Tactic = newTactic; 
        }

        /// <summary>
        /// Quits the application.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Closes the current map.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuClose_Click(object sender, EventArgs e)
        {
            ResetGUI();
            mapLoaded = false;
            UpdateGUI();
        }

        /// <summary>
        /// Shows the Options window.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuOptions_Click(object sender, EventArgs e)
        {
            FormOptions formOptions = new FormOptions();
            formOptions.ShowDialog(this);

            if (mapLoaded)
                Map.ComputeConnections();

            UpdateGUI();
        }

        /// <summary>
        /// Reloads the currently open map.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mnuReloadMap_Click(object sender, EventArgs e)
        {
            LoadMap(Map.File);
        }

        /// <summary>
        /// Resets the team scores to zero.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnResetScores_Click(object sender, EventArgs e)
        {
            foreach (Team Team in Map.Teams)
                Team.Score = 0;
        }

    }
}
