
/****************************************************************************/
/*Copyright (c) 2011, Florent DEVILLE.                                      */
/*All rights reserved.                                                      */
/*                                                                          */
/*Redistribution and use in source and binary forms, with or without        */
/*modification, are permitted provided that the following conditions        */
/*are met:                                                                  */
/*                                                                          */
/* - Redistributions of source code must retain the above copyright         */
/*notice, this list of conditions and the following disclaimer.             */
/* - Redistributions in binary form must reproduce the above                */
/*copyright notice, this list of conditions and the following               */
/*disclaimer in the documentation and/or other materials provided           */
/*with the distribution.                                                    */
/* - The names of its contributors cannot be used to endorse or promote     */
/*products derived from this software without specific prior written        */
/*permission.                                                               */
/* - The source code cannot be used for commercial purposes without         */
/*its contributors' permission.                                             */
/*                                                                          */
/*THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS       */
/*"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT         */
/*LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS         */
/*FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE            */
/*COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       */
/*INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,      */
/*BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;          */
/*LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER          */
/*CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT        */
/*LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN         */
/*ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
/*POSSIBILITY OF SUCH DAMAGE.                                               */
/****************************************************************************/

using System.Windows.Forms;
using System.Xml;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using GE.Visualisation;

namespace GFXEditor
{
    public partial class wndAnimation : Form
    {
        enum eMode
        {
            eModeNothing,
            eModePlay,
            eModeStop,
            eModePlayFlipH,
            eModePlayFlipV,
            eModePlayReverse,
            eModeFrameByFrame
        }

        enum eRulesColor
        {
            Black,
            White
        }

        #region "Variables"

        /// <summary>
        /// The color of the rules
        /// </summary>
        eRulesColor _rulesColor;

        /// <summary>
        /// The overall offset to display the animation
        /// </summary>
        Vector2 _overallOffset;

        /// <summary>
        /// Flag indicates if the user is setting the rules
        /// </summary>
        bool _bSetRulesMode;

        /// <summary>
        /// Point throught which the vertical and horizontal line go.
        /// </summary>
        Vector2 _vec2Rules;

        /// <summary>
        /// Flag set to display the rules
        /// </summary>
        bool _bDisplayRules;

        /// <summary>
        /// Uniform scale used to zoom
        /// </summary>
        int _zoom;

        /// <summary>
        /// The user mode
        /// </summary>
        eMode _userMode;

        /// <summary>
        /// Timer used to refresh the screen
        /// </summary>
        Timer _refreshScreenTimer;

        /// <summary>
        /// Presentation parameter for the xna device
        /// </summary>
        PresentationParameters _param;

        /// <summary>
        /// XNA device
        /// </summary>
        GraphicsDevice _device;

        /// <summary>
        /// XNA sprite batch
        /// </summary>
        SpriteBatch _spriteBatch;

        /// <summary>
        /// Visualisation component
        /// </summary>
        Visu _visualisation;

        /// <summary>
        /// filename of the xml animation file
        /// </summary>
        string _xmlFilename;

        /// <summary>
        /// Current animation selected. Null if nothing selected.
        /// </summary>
        Animation _animCurrentSelected;

        /// <summary>
        /// Id of the current animation selected
        /// </summary>
        int _iIdCurrentAnimationSelected;

        /// <summary>
        /// Id of the current frame
        /// </summary>
        int _iIdCurrentFrame;

        /// <summary>
        /// Current time of the animations
        /// </summary>
        int _iCurrentTime;

        #endregion

        #region "Properties"

        /// <summary>
        /// Get or set the xml filename. Set also the window title bar.
        /// </summary>
        public string XmlFilename { get { return _xmlFilename; } set { _xmlFilename = value; Text = "Animations " + value; } }

        /// <summary>
        /// Get or set the overall offset on X
        /// </summary>
        public float OverallOffsetX { get { return _overallOffset.X; } set { _overallOffset.X = value; } }

        /// <summary>
        /// Get or set the voerall offset on Y
        /// </summary>
        public float OverallOffsetY { get { return _overallOffset.Y; } set { _overallOffset.Y = value; } }

        #endregion

        #region Constructor"

        /// <summary>
        /// Constructor
        /// </summary>
        public wndAnimation(string xmlFilename)
        {
            InitializeComponent();

            XmlFilename = "";
            _animCurrentSelected = null;
            _iIdCurrentFrame = -1;
            _iCurrentTime = -1;
            _iIdCurrentAnimationSelected = -1;
            _userMode = eMode.eModeNothing;
            _zoom = 1;
            _bDisplayRules = false;
            _vec2Rules = Vector2.Zero;
            _bSetRulesMode = false;
            _overallOffset = Vector2.Zero;
            tbOverallOffsetX.Text = "0";
            tbOverallOffsetY.Text = "0";
            tscbRulesColor.SelectedIndex = 0;
            _rulesColor = eRulesColor.Black;

            //create the presentation parameters
            _param = new PresentationParameters();
            _param.DeviceWindowHandle = panelAnimation.Handle;
            _param.IsFullScreen = false;

            //create a new device.
            _device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.HiDef, _param);
            _spriteBatch = new SpriteBatch(_device);

            //create the visualisation component
            _visualisation = new Visu();
            _visualisation.init(_device);

            //load the animations
            int res = _visualisation.loadAnimations(xmlFilename);
            if (res != 1) MessageBox.Show("Error : can't load " + xmlFilename);
            XmlFilename = xmlFilename;

            //fill the combobox
            for (int i = 0; i < _visualisation.AnimationsCount; i++)
            {
                cbAnimationList.Items.Add(_visualisation.getAnimation(i).Name);
            }

            //create the timer
            _refreshScreenTimer = new Timer();
            _refreshScreenTimer.Interval = 1000/60;
            _refreshScreenTimer.Tick += new System.EventHandler(_refreshScreenTimer_Tick);
            _refreshScreenTimer.Start();

        }

        #endregion

        #region "Tools"

        /// <summary>
        /// render in the xna viewport
        /// </summary>
        private void render()
        {
            _visualisation.preRender(false);

            //calculate the position
            Vector2 position = _overallOffset;
            
            switch (_userMode)
            {
                case eMode.eModePlay:
                    _visualisation.displayAnimation(_iIdCurrentAnimationSelected, position, Vector2.Zero, 0, new Vector2(_zoom), ref _iIdCurrentFrame, ref _iCurrentTime);
                    break;

                case eMode.eModePlayFlipH:
                    _visualisation.displayAnimation(_iIdCurrentAnimationSelected, position, ref _iIdCurrentFrame, ref _iCurrentTime, new Vector2(_zoom), SpriteEffects.FlipHorizontally);
                    break;
                case eMode.eModePlayFlipV:
                    _visualisation.displayAnimation(_iIdCurrentAnimationSelected, position, ref _iIdCurrentFrame, ref _iCurrentTime, new Vector2(_zoom), SpriteEffects.FlipVertically);
                    break;
                case eMode.eModePlayReverse:
                    _visualisation.displayReverseAnimation(_iIdCurrentAnimationSelected, position, new Vector2(_zoom), ref _iIdCurrentFrame, ref _iCurrentTime, SpriteEffects.None);
                    break;
                case eMode.eModeFrameByFrame:
                    if (_iIdCurrentFrame < 0 || _iIdCurrentFrame >= _animCurrentSelected.FrameCount) break;
                    position += _animCurrentSelected.getOffset(_iIdCurrentFrame);
                    _visualisation.displaySprite(_animCurrentSelected.indexTexture, _animCurrentSelected.getIndexSprite(_iIdCurrentFrame), position, Vector2.Zero, 0, new Vector2(_zoom));
                    break;
            }

            //display the rules
            if (_bDisplayRules)
            {
                Vector2[] line = new Vector2[2];
                line[0].X = 0;
                line[1].X = panelAnimation.Width;
                line[0].Y = line[1].Y = _vec2Rules.Y;

                if(_rulesColor == eRulesColor.Black)
                    _visualisation.displayBlackPolygon( line);
                else
                    _visualisation.displayWhitePolygon( line);

                line[0].Y = 0;
                line[1].Y = panelAnimation.Width;
                line[0].X = line[1].X = _vec2Rules.X;
                if (_rulesColor == eRulesColor.Black)
                    _visualisation.displayBlackPolygon( line);
                else
                    _visualisation.displayWhitePolygon( line);
            }

            _visualisation.postRender();

            _device.Present();
        }

        private void changeCurrentFrame()
        {
            tbOffsetX.Text = _animCurrentSelected.getOffsetWithoutScale(_iIdCurrentFrame).X.ToString();
            tbOffsetY.Text = _animCurrentSelected.getOffsetWithoutScale(_iIdCurrentFrame).Y.ToString();
            tstbCurrentFrame.Text = _iIdCurrentFrame.ToString();
        }

        private void save()
        {
            XmlTextWriter writer = new XmlTextWriter(XmlFilename, null);
            writer.WriteStartDocument();

            //root
            writer.WriteStartElement("Animations");

            //go throught all animations
            for (int idAnimation = 0; idAnimation < _visualisation.AnimationsCount; idAnimation++)
            {
                writer.WriteStartElement("Animation");

                //name of the animation
                writer.WriteStartAttribute("name");
                writer.WriteString(_visualisation.getAnimation(idAnimation).Name);
                writer.WriteEndAttribute();

                //tileset xml file
                writer.WriteStartAttribute("tileset");
                int idTexture = _visualisation.getAnimation(idAnimation).indexTexture;
                string textureName = _visualisation.getTextureEx(idTexture).Name;
                int idLastSlash = textureName.LastIndexOf('\\');
                textureName = textureName.Substring(idLastSlash + 1);
                textureName = textureName.Replace(".png", ".xml");
                writer.WriteString(textureName);
                writer.WriteEndAttribute();

                //time per frame
                writer.WriteStartAttribute("timePerFrame");
                writer.WriteString(_visualisation.getAnimation(idAnimation).offsetTime.ToString());
                writer.WriteEndAttribute();

                Animation newAnimation = _visualisation.getAnimation(idAnimation);

                //go through every frame
                for (int idFrame = 0; idFrame < newAnimation.FrameCount; idFrame++)
                {
                    //frame
                    writer.WriteStartElement("frame");

                    //sprite name
                    writer.WriteStartAttribute("spriteName");
                    writer.WriteString(_visualisation.getTextureEx(newAnimation.indexTexture).getSpriteName(newAnimation.getIndexSprite(idFrame)));
                    writer.WriteEndAttribute();

                    //offset
                    writer.WriteString(newAnimation.getOffsetWithoutScale(idFrame).X.ToString() + " " + newAnimation.getOffsetWithoutScale(idFrame).Y.ToString());

                    //</frame>
                    writer.WriteEndElement();
                }

                //</Animation>
                writer.WriteEndElement();
            }

            //root </Animations>
            writer.WriteEndDocument();
            writer.Close();

            //message
            MessageBox.Show("Animations saved");
        }

        private void saveEx()
        {
            //create the save file dialog
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Title = "Save Animations";
            sfd.Filter = "Xml files (*.xml)|*.xml";

            //open the dialog
            DialogResult res = sfd.ShowDialog();
            if (res != DialogResult.OK) return;

            //save the animations
            XmlFilename = sfd.FileName;
            save();
        }

        #endregion

        #region "Events"

        void _refreshScreenTimer_Tick(object sender, System.EventArgs e)
        {
            render();
        }

        /// <summary>
        /// Change the selected animation
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cbAnimationList_SelectedIndexChanged(object sender, System.EventArgs e)
        {
            //get the animation
            _iIdCurrentAnimationSelected = cbAnimationList.SelectedIndex;
            _animCurrentSelected = _visualisation.getAnimation(_iIdCurrentAnimationSelected);
            _animCurrentSelected.OffsetScale = _zoom;

            //set the value of the display
            _iIdCurrentFrame = 0;
            tstbCurrentFrame.Text = _iIdCurrentFrame.ToString();
            tslFrameCount.Text = " of " + (_animCurrentSelected.FrameCount-1).ToString();
            tbTime.Text = _animCurrentSelected.offsetTime.ToString();
            tbOffsetX.Text = _animCurrentSelected.getOffsetWithoutScale(_iIdCurrentFrame).X.ToString();
            tbOffsetY.Text = _animCurrentSelected.getOffsetWithoutScale(_iIdCurrentFrame).Y.ToString();

        }

        /// <summary>
        /// Resize the window
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void wndAnimation_Resize(object sender, System.EventArgs e)
        {
            if (WindowState == FormWindowState.Minimized) return;

            if (panelAnimation.Height == 0 || panelAnimation.Width == 0) return;
            //resize the device
            _param.BackBufferHeight = panelAnimation.Height;
            _param.BackBufferWidth = panelAnimation.Width;
            _device.Reset(_param);
        }

        private void tsbPlay_Click(object sender, System.EventArgs e)
        {
            _userMode = eMode.eModePlay;
            _iCurrentTime = -1;
            _iIdCurrentFrame = -1;
        }

        private void tsbPlayFlipH_Click(object sender, System.EventArgs e)
        {
            _userMode = eMode.eModePlayFlipH;
            _iCurrentTime = -1;
            _iIdCurrentFrame = -1;
        }

        private void tsbPlayFlipV_Click(object sender, System.EventArgs e)
        {
            _userMode = eMode.eModePlayFlipV;
            _iCurrentTime = -1;
            _iIdCurrentFrame = -1;
        }

        private void tsbPlayReverse_Click(object sender, System.EventArgs e)
        {
            _userMode = eMode.eModePlayReverse;
            _iCurrentTime = -1;
            _iIdCurrentFrame = -1;
        }

        private void tsbStop_Click(object sender, System.EventArgs e)
        {
            _userMode = eMode.eModeStop;
            _iCurrentTime = -1;
            _iIdCurrentFrame = 0;
                    
        }

        private void tstbCurrentFrame_KeyDown(object sender, KeyEventArgs e)
        {
            //if enter pressed, display the frame
            if (e.KeyData == Keys.Enter)
            {
                bool res = int.TryParse(tstbCurrentFrame.Text, out _iIdCurrentFrame);
                if (!res) return;

                _userMode = eMode.eModeFrameByFrame;
                changeCurrentFrame();
            }
        }

        private void tbTime_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter)
            {
                int newTime = 0;
                bool res = int.TryParse(tbTime.Text, out newTime);
                if (!res) return;
                _animCurrentSelected.offsetTime = newTime;
            }
        }

        private void tsbFirst_Click(object sender, System.EventArgs e)
        {
            _userMode = eMode.eModeFrameByFrame;
            _iIdCurrentFrame = 0;
            changeCurrentFrame();
        }

        private void tsbPrevious_Click(object sender, System.EventArgs e)
        {
            previousFrame();
            //_userMode = eMode.eModeFrameByFrame;
            //_iIdCurrentFrame--;
            //if (_iIdCurrentFrame < 0) _iIdCurrentFrame = _iIdCurrentFrame = _animCurrentSelected.FrameCount - 1;
            //changeCurrentFrame();
        }

        private void tsbNext_Click(object sender, System.EventArgs e)
        {
            nextFrame();
            //_userMode = eMode.eModeFrameByFrame;
            //_iIdCurrentFrame++;
            //if (_iIdCurrentFrame >= _animCurrentSelected.FrameCount) _iIdCurrentFrame = 0;
            //changeCurrentFrame();
        }

        private void tsbLast_Click(object sender, System.EventArgs e)
        {
            _userMode = eMode.eModeFrameByFrame;
            _iIdCurrentFrame = _animCurrentSelected.FrameCount - 1;
            changeCurrentFrame();
        }

        private void tbOffsetX_KeyDown(object sender, KeyEventArgs e)
        {
            if (_userMode != eMode.eModeFrameByFrame) return;

            //if enter pressed, set the new offset
            if (e.KeyData == Keys.Enter)
            {
                int offsetX = 0;
                int offsetY = 0;
                bool res = int.TryParse(tbOffsetX.Text, out offsetX);
                if (!res) return;
                res = int.TryParse(tbOffsetY.Text, out offsetY);
                if (!res) return;

                //offsetX /= _zoom;
                //offsetY /= _zoom;
                _animCurrentSelected.setOffset(_iIdCurrentFrame, new Vector2(offsetX, offsetY));
            }
        }

        private void tbOffsetY_KeyDown(object sender, KeyEventArgs e)
        {
            if (_userMode != eMode.eModeFrameByFrame) return;

            //if enter pressed, set the new offset
            if (e.KeyData == Keys.Enter)
            {
                int offsetX = 0;
                int offsetY = 0;
                bool res = int.TryParse(tbOffsetX.Text, out offsetX);
                if (!res) return;
                res = int.TryParse(tbOffsetY.Text, out offsetY);
                if (!res) return;

                //offsetX /= _zoom;
                //offsetY /= _zoom;
                _animCurrentSelected.setOffset(_iIdCurrentFrame, new Vector2(offsetX, offsetY));
            }
        }

        private void tsbSave_Click(object sender, System.EventArgs e)
        {
            save();
        }     

        private void tsbSaveAs_Click(object sender, System.EventArgs e)
        {
            saveEx();
        }

        private void tsbZoomIn_Click(object sender, System.EventArgs e)
        {
            if(_zoom < 8) _zoom *= 2;
            _animCurrentSelected.OffsetScale = _zoom;

            //if (_userMode == eMode.eModeFrameByFrame)
            //{
            //    tbOffsetX.Text = _animCurrentSelected.getOffsetWithoutScale(_iIdCurrentFrame).X.ToString();
            //    tbOffsetY.Text = _animCurrentSelected.getOffset(_iIdCurrentFrame).Y.ToString();
            //}
        }

        private void tsbZoomOut_Click(object sender, System.EventArgs e)
        {
            if (_zoom > 1) _zoom /= 2;
            _animCurrentSelected.OffsetScale = _zoom;

            //if (_userMode == eMode.eModeFrameByFrame)
            //{
            //    tbOffsetX.Text = _animCurrentSelected.getOffset(_iIdCurrentFrame).X.ToString();
            //    tbOffsetY.Text = _animCurrentSelected.getOffset(_iIdCurrentFrame).Y.ToString();
            //}
        }

        private void tsbDisplayRules_Click(object sender, System.EventArgs e)
        {
            _bDisplayRules = !_bDisplayRules;
        }

        private void tsbSetRules_Click(object sender, System.EventArgs e)
        {
            _bSetRulesMode = true;
            _bDisplayRules = true;
            tsbDisplayRules.Checked = true;
        }

        private void panelAnimation_MouseMove(object sender, MouseEventArgs e)
        {
            if (!_bSetRulesMode) return;

            System.Drawing.Point p = panelAnimation.PointToClient(MousePosition);
            _vec2Rules.X = p.X;
            _vec2Rules.Y = p.Y;

        }

        private void panelAnimation_MouseClick(object sender, MouseEventArgs e)
        {
            if (!_bSetRulesMode) return;
            _bSetRulesMode = false;
        }

        private void tscbRulesColor_SelectedIndexChanged(object sender, System.EventArgs e)
        {
            switch (tscbRulesColor.SelectedIndex)
            {
                case 0:
                    _rulesColor = eRulesColor.Black;
                    break;
                case 1:
                    _rulesColor = eRulesColor.White;
                    break;
            }
        }

        private void tbOverallOffsetX_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                int newX = 0;
                bool res = int.TryParse(tbOverallOffsetX.Text, out newX);
                if (!res) return;

                _overallOffset.X = newX;
            }
        }
       
        private void tbOverallOffsetY_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                int newY = 0;
                bool res = int.TryParse(tbOverallOffsetY.Text, out newY);
                if (!res) return;

                _overallOffset.Y = newY;
            }
        }

        #endregion

        private void previousFrame()
        {
            if (_animCurrentSelected == null) return;

            _userMode = eMode.eModeFrameByFrame;
            _iIdCurrentFrame--;
            if (_iIdCurrentFrame < 0) _iIdCurrentFrame = _iIdCurrentFrame = _animCurrentSelected.FrameCount - 1;
            changeCurrentFrame();
        }

        private void nextFrame()
        {
            if (_animCurrentSelected == null) return;

            _userMode = eMode.eModeFrameByFrame;
            _iIdCurrentFrame++;
            if (_iIdCurrentFrame >= _animCurrentSelected.FrameCount) _iIdCurrentFrame = 0;
            changeCurrentFrame();
        }

        private void wndAnimation_KeyDown(object sender, KeyEventArgs e)
        {
            //page down
            if (e.KeyCode == (Keys.RButton | Keys.Space))
            {
                previousFrame();
                e.Handled = true;
            }

            //page up
            if (e.KeyCode == (Keys.LButton | Keys.Space))
            {
                nextFrame();
                e.Handled = true;
            }

            if (e.KeyCode == Keys.S && e.Modifiers == Keys.Control)
            {
                save();
                e.Handled = true;
            }
        }

    }

}
