/*
 * SettingsEx.cs
 * Copyright (c) 2007-2009 kbinani
 *
 * This file is part of LipSync.
 *
 * LipSync is free software; you can redistribute it and/or
 * modify it under the terms of the BSD License.
 *
 * LipSync is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Runtime.Serialization;

using Boare.Lib.AppUtil;
using Boare.Lib.Vsq;

namespace LipSync {

    public delegate void CommandExecutedEventHandler( TimeTableType command_target, CommandType command_type );

    [Serializable]
    public class SettingsEx : IDisposable, ICloneable {
        const double tpq_sec = 480000000.0;

        public TimeTableGroup m_group_vsq;                  //ok
        public List<TimeTableGroup> m_groups_character;     //ok
        public TimeTableGroup m_group_another;              //ok
        public TimeTableGroup m_group_plugin;               //ok
        public int m_screenWidth = 512;                     //ok
        public int m_screenHeight = 384;                    //ok
        public float m_totalSec = 0.0f;                     //ok
        public Size m_movieSize; // = new Size( 512, 384 );     //ok
        public List<PluginConfig> m_plugins_config;         //ok
        public string m_audioFile = "";                     //ok
        [OptionalField]
        public Color CANVAS_BACKGROUND; // = Color.White;       //ok
        [OptionalField]
        public float REPEAT_START;                          //ok
        [OptionalField]
        public float REPEAT_END;                            //ok
        [OptionalField]
        public List<TimeSigTableEntry> m_timesig_ex;
        [OptionalField]
        public List<TempoTableEntry> m_tempo;
        [OptionalField]
        public int m_base_tempo;
        [OptionalField]
        public uint m_dwRate = 30;
        [OptionalField]
        public uint m_dwScale = 1;
        [NonSerialized]
        private float m_fps_buffer = 30f;
        [NonSerialized]
        public PluginInfo[] m_plugins;
        [OptionalField]
        public List<Telop> m_telop_ex2 = new List<Telop>();
        [OptionalField]
        public bool TelopListFolded = false;
        /// <summary>
        /// 描画順序で格納された描画物のリスト
        /// </summary>
        [NonSerialized]
        public List<ZorderItem> m_zorder = new List<ZorderItem>();
        [OptionalField]
        public string VersionMarker = AppManager.VERSION;// "2.4.4";

        public static event CommandExecutedEventHandler CommandExecuted;

        private static string _( string s ) {
            return Messaging.GetMessage( s );
        }

        public void DrawTo( Graphics g, Size mSize, float now, bool is_transparent ) {
            DrawTo( g, mSize, now, is_transparent, "", 0 );
        }

        public void DrawTo( Graphics g, Size mSize, float now, bool is_transparent, string mouth, int mouth_force_group_index ) {
            if ( mouth.Length == 0 ) {
                mouth_force_group_index = -1;
            }
#if DEBUG
            //Common.DebugWriteLine( "SettingsEx+DrawTo(Graphics,Size,float,bool)" );
            int chrpos = 10;
#endif
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            
            if ( is_transparent ) {
                g.Clear( Color.Transparent );
            } else {
                g.Clear( CANVAS_BACKGROUND );
            }

            ColorMatrix cm = new ColorMatrix();
            cm.Matrix00 = 1;
            cm.Matrix11 = 1;
            cm.Matrix22 = 1;
            cm.Matrix33 = 1f;
            cm.Matrix44 = 1;

            for ( int z = 0; z < m_zorder.Count; z++ ) {
                ZorderItemType type = m_zorder[z].Type;
                int index = m_zorder[z].Index;
                
                Bitmap tmp = null;
                PointF position = new PointF();
                float scale = 1f;
                float alpha = 1f;
                float rotate = 0f;
                Size size = new Size();

                if ( type == ZorderItemType.another ) {
                    if ( !m_group_another[index].IsAviMode ) {
                        if ( m_group_another[index].Image == null ) {
                            continue;
                        }
                        int start_entry = m_group_another[index].Value;
                        for ( int entry = start_entry; entry < m_group_another[index].Count; entry++ ) {
                            if ( m_group_another[index][entry].begin <= now && now <= m_group_another[index][entry].end ) {
                                m_group_another[index].Value = entry;
                                tmp = m_group_another[index].Image;
                                break;
                            }
                        }
                    } else {
                        tmp = m_group_another[index].GetImage( now );
                    }
                    position = m_group_another[index].GetPosition( now );
                    size = m_group_another[index].ImageSize;
                    scale = m_group_another[index].GetScale( now );
                    alpha = m_group_another[index].GetAlpha( now );
                    rotate = m_group_another[index].GetRotate( now );
                } else if ( type == ZorderItemType.character ) {
                    if ( m_groups_character[index].Character.Type == CharacterType.def ) {
                        int[] draw;
                        if ( mouth_force_group_index == index ) {
                            draw = m_groups_character[index].GetDrawObjectIndex( now, mouth );
                        } else {
                            draw = m_groups_character[index].GetDrawObjectIndex( now, "" );
                        }
                        position = m_groups_character[index].GetPosition( now );
                        size = m_groups_character[index].Character.Size;
                        scale = m_groups_character[index].GetScale( now );
                        float tscale = Math.Abs( scale );
                        if ( tscale == 0.0f ) {
                            continue;
                        }
                        alpha = m_groups_character[index].GetAlpha( now );
                        if ( alpha <= 0.0f ) {
                            continue;
                        }
                        rotate = m_groups_character[index].GetRotate( now );
#if DEBUG
                        chrpos += 10;
                        g.DrawString( "character: position=" + position + ", m_groups_character[group].Position=" + m_groups_character[index].Position, AppManager.Config.Font.GetFont(), Brushes.Black, new PointF( 0, chrpos ) );
#endif
                        tmp = m_groups_character[index].Character.Face( draw );
                    } else {
                        string[] draw = m_groups_character[index].GetDrawObjectNames( now );
                        int target_plugin = -1;
                        for ( int i = 0; i < m_plugins_config.Count; i++ ) {
                            if ( m_plugins_config[i].ID == m_groups_character[index].Character.PluginConfig.ID ) {
                                target_plugin = i;
                                break;
                            }
                        }
                        if ( target_plugin >= 0 ) {
                            string s = "";
                            bool first = true;
                            for ( int i = 0; i < draw.Length; i++ ) {
                                if ( first ) {
                                    s += draw[i];
                                    first = false;
                                } else {
                                    s += "\n" + draw[i];
                                }
                            }
                            m_plugins[target_plugin].Instance.Render( g, mSize, now, s, "" );
                        }
                        continue;
                    }
                } else if ( type == ZorderItemType.plugin ) {
                    int start_entry = m_group_plugin[index].Value;
                    for ( int entry = start_entry; entry < m_group_plugin[index].Count; entry++ ) {
                        if ( m_group_plugin[index][entry].begin <= now && now <= m_group_plugin[index][entry].end ) {
                            m_group_plugin[index].Value = entry;
#if DEBUG
                            chrpos += 10;
                            g.DrawString( "plugin", AppManager.Config.Font.GetFont(), Brushes.Black, new PointF( 0, chrpos ) );
#endif
                            tmp = new Bitmap( mSize.Width, mSize.Height );
                            TimeTableEntry tmptt = m_group_plugin[index][entry];
                            m_plugins[index].Instance.Apply( ref tmp, now, tmptt.begin, tmptt.end, ref tmptt.body );
                            m_group_plugin[index][entry] = tmptt;
                            size = mSize;
                            position = new PointF( 0f, 0f );
                            alpha = 1f;
                            scale = 1f;
                            rotate = 0f;
                            break;
                        }
                    }
                } else {
                    continue;
                }

                if ( tmp != null ) {
                    float tscale = Math.Abs( scale );
                    if ( scale < 0 ) {
                        tmp.RotateFlip( RotateFlipType.RotateNoneFlipX );
                    }
                    if ( scale != 0.0f ) {
                        g.ResetTransform();
                        float half_width = size.Width / 2f;
                        float half_height = size.Height / 2f;
                        g.TranslateTransform( position.X + half_width * tscale, position.Y + half_height * tscale );
                        if ( scale != 1f ) {
                            g.ScaleTransform( tscale, tscale );
                        }
                        InterpolationMode im = g.InterpolationMode;
                        SmoothingMode sm = g.SmoothingMode;
                        if ( scale == 1f && rotate == 0f ) {
                            g.InterpolationMode = InterpolationMode.Default;
                            g.SmoothingMode = SmoothingMode.Default;
                        }
                        if ( rotate != 0f ) {
                            g.RotateTransform( rotate );
                        }
                        if ( 0 < alpha && alpha < 1f ) {
                            cm.Matrix33 = alpha;
                            ImageAttributes ia = new ImageAttributes();
                            ia.SetColorMatrix( cm );
                            g.DrawImage( tmp,
                                         new PointF[]{ new PointF( -half_width, - half_height ),
                                                           new PointF( half_width, -half_height ),
                                                           new PointF( -half_width, half_height ) },
                                         new RectangleF( 0f, 0f, size.Width, size.Height ),
                                         GraphicsUnit.Pixel,
                                         ia );
                        } else if ( alpha != 0f ) {
                            g.DrawImage( tmp, -half_width, -half_height, (float)size.Width, (float)size.Height );
                        }
                        g.InterpolationMode = im;
                        g.SmoothingMode = sm;
                        g.ResetTransform();
                    }
                }
            }

            //最後に字幕を入れる
            foreach ( Telop telop in m_telop_ex2 ) {
                if ( telop.Start <= now && now <= telop.End ) {
                    float alpha = 1f;
                    if ( telop.FadeIn ) {
                        float diff = now - telop.Start;
                        if ( 0f <= diff && diff <= telop.FadeInSpan ) {
                            alpha = 1.0f / telop.FadeInSpan * diff;
                        }
                    }
                    if ( telop.FadeOut ) {
                        float diff = telop.End - now;
                        if ( 0f <= diff && diff <= telop.FadeOutSpan ) {
                            alpha = 1.0f / telop.FadeOutSpan * diff;
                        }
                    }
                    PointF position = telop.GetPosition( now );
                    Size size = telop.ImageSize;
                    float scale = telop.GetScale( now );
                    float talpha = telop.GetAlpha( now );
                    float rotate = telop.GetRotate( now );
                    g.ResetTransform();
                    if ( scale != 0.0f ) {
                        g.TranslateTransform( position.X + size.Width / 2f * scale, position.Y + size.Height / 2f * scale );
                        if ( scale != 1f ) {
                            g.ScaleTransform( scale, scale );
                        }
                        if ( rotate != 0f ) {
                            g.RotateTransform( rotate );
                        }
                        g.DrawString( telop.Text,
                                      telop.Font,
                                      new SolidBrush( Color.FromArgb( (int)(talpha * alpha * 255), telop.Color ) ),
                                      (int)(-size.Width / 2),
                                      (int)(-size.Height / 2) );
                        g.ResetTransform();
                    }
                }
            }
            //}
            //return bmp;
        }

        
        /// <summary>
        /// Settingsからのコンバート
        /// </summary>
        /// <param name="s"></param>
        public SettingsEx( Settings s ) {
#if DEBUG
            Common.DebugWriteLine( "SettingsEx..ctor(Settings)" );
#endif
            m_group_vsq = (TimeTableGroup)s.m_group_vsq.Clone();
            m_groups_character = new List<TimeTableGroup>();
            for( int i = 0; i < s.m_groups_character.Count; i++ ){
                m_groups_character.Add( (TimeTableGroup)s.m_groups_character[i].Clone() );
            }
            m_group_another = (TimeTableGroup)s.m_group_another.Clone();
            m_group_plugin = (TimeTableGroup)s.m_group_plugin.Clone();
            m_screenWidth = s.m_screenWidth;
            m_screenHeight = s.m_screenHeight;
            m_totalSec = s.m_totalSec;
            m_movieSize = s.m_movieSize;
            m_plugins_config = new List<PluginConfig>();
            for( int i = 0; i < s.m_plugins_config.Count; i++ ){
                m_plugins_config.Add( s.m_plugins_config[i].Clone() );
            }
            m_audioFile = s.m_audioFile;
            CANVAS_BACKGROUND = s.CANVAS_BACKGROUND;
            REPEAT_START = s.REPEAT_START;
            REPEAT_END = s.REPEAT_START;
            m_timesig_ex = new List<TimeSigTableEntry>();
            for( int i = 0; i < s.m_timesig_ex.Count; i++ ){
                if ( s.m_timesig_ex[i].Numerator == 0 || s.m_timesig_ex[i].Denominator == 0 ) {
                    m_timesig_ex.Clear();
                    break;
                }
                m_timesig_ex.Add( (TimeSigTableEntry)s.m_timesig_ex[i].Clone() );
            }
            m_tempo = new List<TempoTableEntry>();
            for( int i = 0; i < s.m_tempo.Count; i++ ){
                m_tempo.Add( (TempoTableEntry)s.m_tempo[i].Clone() );
            }
            m_base_tempo = s.m_base_tempo;
            m_dwRate = s.m_dwRate;
            m_dwScale = s.m_dwScale;
            m_fps_buffer = m_dwRate / (float)m_dwScale;
            m_telop_ex2 = new List<Telop>();
            for ( int i = 0; i < s.m_telop_ex2.Count; i++ ) {
                m_telop_ex2.Add( (Telop)s.m_telop_ex2[i].Clone() );
            }
        }

        public Telop this[int id] {
            get {
                foreach ( Telop item in m_telop_ex2 ) {
                    if ( item.ID == id ) {
                        return item;
                    }
                }
                return null;
            }
            set {
                for ( int i = 0; i < m_telop_ex2.Count; i++ ) {
                    if ( m_telop_ex2[i].ID == id ) {
                        m_telop_ex2[i] = value;
                        break;
                    }
                }
            }
        }
        
        public float FrameRate {
            get {
                return m_fps_buffer;
            }
        }

        public uint DwRate {
            get {
                return m_dwRate;
            }
            set {
                m_dwRate = value;
                m_fps_buffer = (float)m_dwRate / (float)m_dwScale;
            }
        }

        public uint DwScale {
            get {
                return m_dwScale;
            }
            set {
                m_dwScale = value;
                m_fps_buffer = (float)m_dwRate / (float)m_dwScale;
            }
        }

        public object Clone() {
            SettingsEx res = new SettingsEx();
            res.m_group_vsq = (TimeTableGroup)m_group_vsq.Clone();
            res.m_groups_character = new List<TimeTableGroup>();
            for ( int i = 0; i < m_groups_character.Count; i++ ) {
                res.m_groups_character.Add( (TimeTableGroup)m_groups_character[i].Clone() );
            }
            res.m_group_another = (TimeTableGroup)m_group_another.Clone();
            res.m_group_plugin = (TimeTableGroup)m_group_plugin.Clone();
            //res.fps = fps;
            res.m_screenWidth = m_screenWidth;
            res.m_screenHeight = m_screenHeight;
            res.m_totalSec = m_totalSec;
            res.m_movieSize = m_movieSize;
            res.m_plugins_config = new List<PluginConfig>();
            for ( int i = 0; i < m_plugins_config.Count; i++ ) {
                res.m_plugins_config.Add( m_plugins_config[i].Clone() );
            }
            res.m_audioFile = m_audioFile;
            res.CANVAS_BACKGROUND = CANVAS_BACKGROUND;
            res.REPEAT_START = REPEAT_START;
            res.REPEAT_END = REPEAT_END;
            res.m_telop_ex2 = new List<Telop>();
            foreach ( Telop item in m_telop_ex2 ) {
                res.m_telop_ex2.Add( (Telop)item.Clone() );
            }
            res.m_timesig_ex = new List<TimeSigTableEntry>();
            for ( int i = 0; i < m_timesig_ex.Count; i++ ) {
                res.m_timesig_ex.Add( (TimeSigTableEntry)m_timesig_ex[i].Clone() );
            }
            res.m_tempo = new List<TempoTableEntry>();
            for ( int i = 0; i < m_tempo.Count; i++ ) {
                res.m_tempo.Add( (TempoTableEntry)m_tempo[i].Clone() );
            }
            res.m_base_tempo = m_base_tempo;
            res.m_zorder = new List<ZorderItem>();
            for ( int i = 0; i < m_zorder.Count; i++ ) {
                res.m_zorder.Add( (ZorderItem)m_zorder[i].Clone() );
            }
            /*res.m_commands = new List<Command>();
            for ( int i = 0; i < m_commands.Count; i++ ) {
                res.m_commands.Add( m_commands[i].Clone() );
            }
            res.m_command_position = m_command_position;*/
            res.DwRate = m_dwRate;
            res.DwScale = m_dwScale;
            return res;
        }


        public TimeTableGroup this[TimeTableType type, int group] {
            get {
                if ( type == TimeTableType.character ) {
                    return m_groups_character[group];
                } else {
                    switch ( type ) {
                        case TimeTableType.another:
                            return m_group_another;
                        case TimeTableType.plugin:
                            return m_group_plugin;
                        case TimeTableType.vsq:
                            return m_group_vsq;
                        default:
                            return null;
                    }
                }
            }
            set {
                if ( type == TimeTableType.character ) {
                    m_groups_character[group] = value;
                } else {
                    switch ( type ) {
                        case TimeTableType.another:
                            m_group_another = value;
                            break;
                        case TimeTableType.plugin:
                            m_group_plugin = value;
                            break;
                        case TimeTableType.vsq:
                            m_group_vsq = value;
                            break;
                    }
                }
            }
        }

        public SettingsEx() {
            m_zorder = new List<ZorderItem>();
            m_group_vsq = new TimeTableGroup( _( "VSQ Tracks" ), -1, null );
            m_groups_character = new List<TimeTableGroup>();
            m_group_another = new TimeTableGroup( _( "Another images" ), -1, null );
            m_group_plugin = new TimeTableGroup( _( "Plugin" ), -1, null );
            m_telop_ex2 = new List<Telop>();
            m_screenWidth = 512;
            m_screenHeight = 384;
            m_totalSec = 0.0f;
            m_movieSize = new Size( 512, 384 );
            m_plugins_config = new List<PluginConfig>();
            m_audioFile = "";
            CANVAS_BACKGROUND = Color.White;
            m_telop_ex2 = new List<Telop>();
            m_timesig_ex = new List<TimeSigTableEntry>();
            m_tempo = new List<TempoTableEntry>();
            m_base_tempo = 480000;
            m_dwRate = 30;
            m_dwScale = 1;
            m_fps_buffer = (float)m_dwRate / (float)m_dwScale;
        }

        [OnDeserializing]
        private void onDeserializing( StreamingContext sc ) {
            CANVAS_BACKGROUND = Color.White;
            m_telop_ex2 = new List<Telop>();
            REPEAT_START = 0f;
            REPEAT_END = -1f;
            m_dwRate = 0;
            m_dwScale = 0;
        }

        [OnDeserialized]
        private void onDeserialized( StreamingContext sc ) {
#if DEBUG
            Common.DebugWriteLine( "SettingsEx.onDeserialized(StreamingContext)" );
            Common.DebugWriteLine( "    m_timesig_ex" );
#endif
            m_zorder = new List<ZorderItem>();
            if ( m_timesig_ex == null ) {
                m_timesig_ex = new List<TimeSigTableEntry>();
            } else {
                for ( int i = 0; i < m_timesig_ex.Count; i++ ) {
#if DEBUG
                    Common.DebugWriteLine( "        " + m_timesig_ex[i].ToString() );
#endif
                    if ( m_timesig_ex[i].Numerator == 0 || m_timesig_ex[i].Denominator == 0 ) {
                        m_timesig_ex.Clear();
                        break;
                    }
                }
            }
            if( m_tempo == null ) {
                m_tempo = new List<TempoTableEntry>();
            }
            if ( m_dwRate == 0 ) {
                m_dwScale = 1;
                m_dwRate = 30;
            }
            m_fps_buffer = (float)m_dwRate / (float)m_dwScale;
#if DEBUG
            Common.DebugWriteLine( "    m_telop_ex2" );
            for ( int i = 0; i < m_telop_ex2.Count; i++ ) {
                Common.DebugWriteLine( "        i=" + i + "; ID=" + m_telop_ex2[i].ID + "; Text=" + m_telop_ex2[i].Text );
            }
#endif
        }

        public void Dispose() {
            if ( m_group_vsq != null ) {
                m_group_vsq.Dispose();
                m_group_vsq = null;
            }
            if ( m_groups_character != null ) {
                m_groups_character.Clear();
                m_groups_character = null;
            }
            if ( m_group_another != null ) {
                m_group_another.Dispose();
                m_group_another = null;
            }
            if ( m_group_plugin != null ) {
                m_group_plugin.Dispose();
                m_group_plugin = null;
            }
            if ( m_plugins_config != null ) {
                m_plugins_config.Clear();
                m_plugins_config = null;
            }
        }

        /// <summary>
        /// m_telop_ex用。次に利用可能なIDを調べます
        /// </summary>
        /// <returns></returns>
        public int GetNextID() {
            return GetNextID( 0 );
        }

        public int GetNextID( int skip ) {
            int draft = -1;
            while ( true ) {
                draft++;
                bool found = false;
                foreach ( Telop item in m_telop_ex2 ) {
                    if ( draft == item.ID ) {
                        found = true;
                    }
                }
                if ( !found ) {
                    break;
                }
            }
            return draft + skip;
        }        

        public void SetZorder( ZorderItem item, int zorder ) {
            switch ( item.Type ) {
                case ZorderItemType.plugin:
                    m_group_plugin[item.Index].ZOrder = zorder;
                    break;
                case ZorderItemType.another:
                    m_group_another[item.Index].ZOrder = zorder;
                    break;
                case ZorderItemType.character:
                    m_groups_character[item.Index].ZOrder = zorder;
                    break;
            }
        }

        public int GetZorder( ZorderItem item ) {
            switch ( item.Type ) {
                case ZorderItemType.plugin:
                    return m_group_plugin[item.Index].ZOrder;
                case ZorderItemType.another:
                    return m_group_another[item.Index].ZOrder;
                case ZorderItemType.character:
                    return m_groups_character[item.Index].ZOrder;
            }
            return 0;
        }

        public void UpdateZorder() {
            m_zorder.Clear();
            for ( int i = 0; i < m_plugins_config.Count; i++ ) {
                m_zorder.Add( new ZorderItem( m_plugins_config[i].ID, ZorderItemType.plugin, i ) );
            }
            for ( int i = 0; i < m_groups_character.Count; i++ ) {
                m_zorder.Add( new ZorderItem( m_groups_character[i].Text, ZorderItemType.character, i ) );
            }
            for ( int i = 0; i < m_group_another.Count; i++ ) {
                m_zorder.Add( new ZorderItem( m_group_another[i].Text, ZorderItemType.another, i ) );
            }

            bool changed = true;
            ZorderItem tmp;
            while ( changed ) {
                changed = false;
                for ( int i = 0; i < m_zorder.Count - 1; i++ ) {
                    int order_i = GetZorder( m_zorder[i] );
                    int order_ipp = GetZorder( m_zorder[i + 1] );
                    if ( order_i < order_ipp || (order_i == order_ipp && m_zorder[i + 1].Type.CompareTo( m_zorder[i].Type ) > 0) ) {
                        tmp = (ZorderItem)m_zorder[i].Clone();
                        m_zorder[i] = (ZorderItem)m_zorder[i + 1].Clone();
                        m_zorder[i + 1] = (ZorderItem)tmp.Clone();
                        changed = true;
                    }
                }
            }

            for ( int i = 0; i < m_zorder.Count; i++ ) {
                SetZorder( m_zorder[i], m_zorder.Count - 1 - i );
            }
        
        }

        /// <summary>
        /// 指定されたコマンドを実行します
        /// </summary>
        /// <param name="command"></param>
        public Command Execute( Command command ) {
            int group = command.group;
            int track = command.track;
            int entry = command.entry;
            TimeTableType target = command.target;

            Command ret = null;
            switch ( command.target ) {
                case TimeTableType.telop:
                    #region telop
                    switch ( command.type ) {
                        case CommandType.addTelop:
                            Telop adding = (Telop)command.telop.Clone( GetNextID() );
                            ret = Command.GCommandDeleteTelop( adding );
                            m_telop_ex2.Add( adding );
                            break;
                        case CommandType.editTelop:
                            ret = Command.GCommandEditTelop( command.telop.ID, this[command.telop.ID] );
                            this[command.telop.ID] = (Telop)command.telop.Clone();
                            break;
                        case CommandType.deleteTelop:
                            ret = Command.GCommandAddTelop( this[command.telop.ID] );
                            for ( int i = 0; i < m_telop_ex2.Count; i++ ) {
                                if ( m_telop_ex2[i].ID == command.telop.ID ) {
                                    m_telop_ex2.RemoveAt( i );
                                    break;
                                }
                            }
                            Property.Instance.Editing = null; //dirty...
                            break;
                        case CommandType.shiftTimeTable:
                            ret = Command.GCommandShiftTimeTable( target, -1, -command.floatValue );
                            for ( int i = 0; i < m_telop_ex2.Count; i++ ) {
                                m_telop_ex2[i].Start += command.floatValue;
                                m_telop_ex2[i].End += command.floatValue;
                            }
                            break;
                        case CommandType.addTelopRange:
                            Telop[] adding2 = (Telop[])command.args[0];
                            for ( int i = 0; i < adding2.Length; i++ ) {
                                adding2[i] = (Telop)adding2[i].Clone( GetNextID( i ) );
                            }
                            ret = Command.GCommandDeleteTelopRange( adding2 );
                            m_telop_ex2.AddRange( adding2 );
                            break;
                        case CommandType.deleteTelopRange:
                            Telop[] adding3 = (Telop[])command.args[0];
                            ret = Command.GCommandAddTelopRange( adding3 );
                            for ( int i = 0; i < adding3.Length; i++ ) {
                                for ( int j = 0; j < m_telop_ex2.Count; j++ ) {
                                    if ( m_telop_ex2[j].ID == adding3[i].ID ) {
                                        m_telop_ex2.RemoveAt( j );
                                        break;
                                    }
                                }
                            }
                            break;
                    }
                    Telop.DecideLane( m_telop_ex2 );
                    #endregion
                    break;
                case TimeTableType.another:
                    #region another
                    switch ( command.type ) {
                        case CommandType.addEntry:
                            ret = Command.GCommandDeleteTimeTableEntry( target, -1, track, command.item );
                            m_group_another[track].Add( (TimeTableEntry)command.item.Clone() );
                            m_group_another[track].Sort();
                            break;
                        case CommandType.deleteEntry:
                            ret = Command.GCommandAddTimeTableEntry( target, -1, track, command.item );
                            m_group_another[track].Remove( command.item );
                            break;
                        case CommandType.editEntry:
                            ret = Command.GCommandEditTimeTableEntry( target, -1, track, entry, m_group_another[track][entry] );
                            m_group_another[track][entry] = (TimeTableEntry)command.item.Clone();
                            break;
                        case CommandType.addTimeTable:
                            ret = Command.GCommandDeleteTimeTable( target, -1, m_group_another.Count );
                            m_group_another.Insert( command.track, (TimeTable)command.table.Clone() );
                            UpdateZorder();
                            break;
                        case CommandType.deleteTimeTable:
                            ret = Command.GCommandAddTimeTable( target, -1, track, m_group_another[track] );
                            m_group_another.RemoveAt( command.track );
                            UpdateZorder();
                            break;
                        case CommandType.editTimeTable:
                            ret = Command.GCommandEditTimeTable( target, -1, track, m_group_another[track] );
                            m_group_another[command.track].Clear();
                            m_group_another[command.track] = (TimeTable)command.table.Clone();
                            break;
                        case CommandType.setImage:
                            if ( m_group_another[track].IsAviMode ) {
                                ret = Command.GCommandSetAvi( track, m_group_another[track].AviConfig );
                            } else {
                                ret = Command.GCommandSetImage( track, m_group_another[track].Image );
                            }
                            m_group_another[command.track].SetImage( command.image );
                            break;
                        case CommandType.setPosition:
                            ret = Command.GCommandSetPosition( target, -1, track, m_group_another[track].Position );
                            m_group_another[command.track].Position = command.position;
                            break;
                        case CommandType.changeScale:
                            ret = Command.GCommandChangeScale( target, -1, track, m_group_another[track].Scale );
                            m_group_another[command.track].Scale = command.floatValue;
                            break;
                        case CommandType.editGroup:
                            ret = Command.GCommandEditGroup( target, -1, m_group_another );
                            m_group_another = null;
                            m_group_another = (TimeTableGroup)command.tablegroup.Clone();
                            break;
                        case CommandType.setAvi:
                            if ( m_group_another[track].IsAviMode ) {
                                ret = Command.GCommandSetAvi( track, m_group_another[track].AviConfig );
                            } else {
                                ret = Command.GCommandSetImage( track, m_group_another[track].Image );
                            }
                            m_group_another[command.track].SetAvi( command.str );
                            break;
                        case CommandType.shiftTimeTable:
                            ret = Command.GCommandShiftTimeTable( target, track, -command.floatValue );
                            for ( int i = 0; i < m_group_another[track].Count; i++ ) {
                                m_group_another[track][i].begin += command.floatValue;
                                m_group_another[track][i].end += command.floatValue;
                            }
                            break;
                    }
                    #endregion
                    break;
                case TimeTableType.character:
                    #region character
                    switch ( command.type ) {
                        case CommandType.addEntry:
                            ret = Command.GCommandDeleteTimeTableEntry( target, group, track, command.item );
                            m_groups_character[group][track].Add( (TimeTableEntry)command.item.Clone() );
                            m_groups_character[group][track].Sort();
                            break;
                        case CommandType.deleteEntry:
                            ret = Command.GCommandAddTimeTableEntry( target, group, track, command.item );
                            m_groups_character[group][track].Remove( command.item );
                            break;
                        case CommandType.editEntry:
                            ret = Command.GCommandEditTimeTableEntry( target, group, track, entry, m_groups_character[group][track][entry] );
                            m_groups_character[group][track][entry] = (TimeTableEntry)command.item.Clone();
                            break;
                        case CommandType.addTimeTable:
                            ret = Command.GCommandDeleteTimeTable( target, group, m_groups_character[group].Count );
                            m_groups_character[group].Insert( command.track, (TimeTable)command.table.Clone() );
                            UpdateZorder();
                            break;
                        case CommandType.deleteTimeTable:
                            ret = Command.GCommandAddTimeTable( target, group, track, m_groups_character[group][track] );
                            m_groups_character[group].RemoveAt( command.track );
                            UpdateZorder();
                            break;
                        case CommandType.editTimeTable:
                            ret = Command.GCommandEditTimeTable( target, group, track, m_groups_character[group][track] );
                            m_groups_character[command.group][command.track].Clear();
                            m_groups_character[command.group][command.track] = (TimeTable)command.table.Clone();
                            break;
                        case CommandType.addGroup:
                            ret = Command.GCommandDeleteTimeTableGroup( target, m_groups_character.Count );
                            m_groups_character.Insert( command.group, (TimeTableGroup)command.tablegroup.Clone() );
                            UpdateZorder();
                            break;
                        case CommandType.deleteGroup:
                            ret = Command.GCommandAddGroup( target, group, m_groups_character[group] );
                            m_groups_character.RemoveAt( command.group );
                            Property.Instance.Editing = null;
                            UpdateZorder();
                            break;
                        case CommandType.editGroup:
                            ret = Command.GCommandEditGroup( target, group, m_groups_character[group] );
                            m_groups_character[command.group].Dispose();
                            m_groups_character[command.group] = (TimeTableGroup)command.tablegroup.Clone();
                            break;
                        case CommandType.setPosition:
                            ret = Command.GCommandSetPosition( target, group, track, m_groups_character[group].Position );
                            m_groups_character[command.group].Position = command.position;
                            break;
                        case CommandType.changeScale:
                            ret = Command.GCommandChangeScale( target, group, -1, m_groups_character[group].Scale );
                            m_groups_character[command.group].Scale = command.floatValue;
                            break;
                        case CommandType.shiftTimeTable:
                            ret = Command.GCommandShiftTimeTable( target, group, -command.floatValue );
                            for ( int i = 0; i < m_groups_character[group].Count; i++ ) {
                                for ( int j = 0; j < m_groups_character[group][i].Count; j++ ) {
                                    m_groups_character[group][i][j].begin += command.floatValue;
                                    m_groups_character[group][i][j].end += command.floatValue;
                                }
                            }
                            break;
                    }
                    #endregion
                    break;
                case TimeTableType.plugin:
                    #region plugin
                    switch ( command.type ) {
                        case CommandType.addEntry:
                            ret = Command.GCommandDeleteTimeTableEntry( target, -1, track, command.item );
                            m_group_plugin[track].Add( (TimeTableEntry)command.item.Clone() );
                            m_group_plugin[track].Sort();
                            break;
                        case CommandType.deleteEntry:
                            ret = Command.GCommandAddTimeTableEntry( target, -1, track, command.item );
                            m_group_plugin[track].Remove( command.item );
                            break;
                        case CommandType.editEntry:
                            ret = Command.GCommandEditTimeTableEntry( target, -1, track, entry, m_group_plugin[track][entry] );
                            m_group_plugin[track][entry] = (TimeTableEntry)command.item.Clone();
                            break;
                        case CommandType.addTimeTable:
                            ret = Command.GCommandDeleteTimeTable( target, -1, m_group_plugin.Count );
                            m_group_plugin.Insert( command.track, (TimeTable)command.table.Clone() );
                            UpdateZorder();
                            break;
                        case CommandType.deleteTimeTable:
                            ret = Command.GCommandAddTimeTable( target, -1, track, m_group_plugin[track] );
                            m_group_plugin.RemoveAt( command.track );
                            UpdateZorder();
                            break;
                        case CommandType.editTimeTable:
                            ret = Command.GCommandEditTimeTable( target, -1, track, m_group_plugin[track] );
                            m_group_plugin[command.track].Clear();
                            m_group_plugin[command.track] = (TimeTable)command.table.Clone();
                            break;
                        case CommandType.editGroup:
                            ret = Command.GCommandEditGroup( target, -1, m_group_plugin );
                            m_group_plugin = null;
                            m_group_plugin = (TimeTableGroup)command.tablegroup.Clone();
                            break;
                        case CommandType.shiftTimeTable:
                            ret = Command.GCommandShiftTimeTable( target, track, -command.floatValue );
                            for ( int i = 0; i < m_group_plugin[track].Count; i++ ) {
                                m_group_plugin[track][i].begin += command.floatValue;
                                m_group_plugin[track][i].end += command.floatValue;
                            }
                            break;
                    }
                    #endregion
                    break;
                case TimeTableType.vsq:
                    #region vsq
                    switch ( command.type ) {
                        case CommandType.addEntry:
                            ret = Command.GCommandDeleteTimeTableEntry( target, -1, track, command.item );
                            m_group_vsq[track].Add( (TimeTableEntry)command.item.Clone() );
                            m_group_vsq[track].Sort();
                            break;
                        case CommandType.deleteEntry:
                            ret = Command.GCommandAddTimeTableEntry( target, -1, track, command.item );
                            m_group_vsq[track].Remove( command.item );
                            break;
                        case CommandType.editEntry:
                            ret = Command.GCommandEditTimeTableEntry( target, -1, track, entry, m_group_vsq[track][entry] );
                            m_group_vsq[track][entry] = (TimeTableEntry)command.item.Clone();
                            break;
                        case CommandType.addTimeTable:
                            ret = Command.GCommandDeleteTimeTable( target, -1, m_group_vsq.Count );
                            m_group_vsq.Insert( command.track, (TimeTable)command.table.Clone() );
                            break;
                        case CommandType.deleteTimeTable:
                            ret = Command.GCommandAddTimeTable( target, -1, track, m_group_vsq[track] );
                            m_group_vsq.RemoveAt( command.track );
                            break;
                        case CommandType.editTimeTable:
                            ret = Command.GCommandEditTimeTable( target, -1, track, m_group_vsq[track] );
                            m_group_vsq[command.track].Clear();
                            m_group_vsq[command.track] = (TimeTable)command.table.Clone();
                            break;
                        case CommandType.editGroup:
                            ret = Command.GCommandEditGroup( target, -1, m_group_vsq );
                            m_group_vsq = null;
                            m_group_vsq = (TimeTableGroup)command.tablegroup.Clone();
                            break;
                        case CommandType.shiftTimeTable:
                            ret = Command.GCommandShiftTimeTable( target, track, -command.floatValue );
                            for ( int i = 0; i < m_group_vsq[track].Count; i++ ) {
                                m_group_vsq[track][i].begin += command.floatValue;
                                m_group_vsq[track][i].end += command.floatValue;
                            }
                            break;
                    }
                    #endregion
                    break;
                case TimeTableType.whole:
                    #region whole
                    switch ( command.type ) {
                        case CommandType.changePluginConfig:
                            ret = Command.GCommandChangePluginConfig( track, m_plugins_config[track].Config );
                            m_plugins_config[command.track].Config = command.str;
                            // キャラクタ描画用プラグインの場合は、該当キャラクタトラックのグループ番号に応じて、command.group(>=0)に値がセットされる
                            if ( command.group >= 0 ) {
                                m_groups_character[command.group].Character.PluginConfig.Config = command.str;
                            }
                            m_plugins[command.track].Instance.Config = command.str;
                            break;
                        case CommandType.changeFps:
                            ret = Command.GCommandChangeFps( m_dwRate, m_dwScale );
                            DwRate = command.dwRate;
                            DwScale = command.dwScale;
                            break;
                        case CommandType.changeVideoSize:
                            ret = Command.GCommandChangeVideoSize( m_movieSize );
                            m_movieSize = command.size;
                            break;
                        case CommandType.shiftTimeTable:
                            ret = Command.GCommandShiftTimeTable( TimeTableType.whole, -1, -command.floatValue );
                            float shift = command.floatValue;
                            // vsq
                            shiftTimeTable( ref m_group_vsq, shift );
                            // character
                            for ( int i = 0; i < m_groups_character.Count; i++ ) {
                                TimeTableGroup tmp = m_groups_character[i];
                                shiftTimeTable( ref tmp, shift );
                                m_groups_character[i] = tmp;
                            }
                            // another
                            shiftTimeTable( ref m_group_another, shift );
                            // plugin
                            shiftTimeTable( ref m_group_plugin, shift );
                            // telop
                            for ( int i = 0; i < m_telop_ex2.Count; i++ ) {
                                m_telop_ex2[i].Start += shift;
                                m_telop_ex2[i].End += shift;
                            }
                            break;
                        case CommandType.setMP3:
                            ret = Command.GCommandSetMp3( m_audioFile );
                            m_audioFile = command.str;
                            break;
                        case CommandType.changeBackgroundColor:
                            ret = Command.GCommandChangeBackgroundColor( CANVAS_BACKGROUND );
                            CANVAS_BACKGROUND = command.color;
                            break;
                    }
                    #endregion
                    break;
            }
            if ( command.child != null ) {
                ret.child = Execute( command.child );
            }
            if ( CommandExecuted != null ) {
                CommandExecuted( command.target, command.type );
            }
            return ret;
        }

        private void shiftTimeTable( ref TimeTableGroup table, float shift ) {
            for ( int track = 0; track < table.Count; track++ ) {
                for ( int entry = 0; entry < table[track].Count; entry++ ) {
                    table[track][entry].begin += shift;
                    table[track][entry].end += shift;
                }
            }
        }

        public IEnumerable<ZorderItem> GetZorderItemEnumerator() {
            for( int i = 0; i < m_groups_character.Count; i++ ) {
                yield return new ZorderItem( m_groups_character[i].Text, ZorderItemType.character, i );
            }
            for( int i = 0; i < m_group_another.Count; i++ ) {
                yield return new ZorderItem( m_group_another[i].Text, ZorderItemType.another, i );
            }
            for( int i = 0; i < m_telop_ex2.Count; i++ ) {
                yield return new ZorderItem( m_telop_ex2[i].Text, ZorderItemType.telop, m_telop_ex2[i].ID );
            }
        }

        public IEnumerable<BarLineType> GetBarLineTypeEnumerator( QuantizeMode mode, bool triplet ) {
            int local_denominator;
            int local_numerator;
            int local_clock;
            int local_bar_count;
            int clock_step;
            int end_clock;
            int clock_per_bar;
            for( int i = 0; i < m_timesig_ex.Count; i++ ) {
                local_denominator = m_timesig_ex[i].Denominator;
                local_numerator = m_timesig_ex[i].Numerator;
                local_clock = m_timesig_ex[i].Clock;
                local_bar_count = m_timesig_ex[i].BarCount;
                clock_per_bar = 480 * 4 * local_numerator / local_denominator;
                clock_step = 480 * 4 / local_denominator;
                switch( mode ) {
                    case QuantizeMode.off:
                        clock_step = 480 * 4 / local_denominator;
                        break;
                    case QuantizeMode.q04:
                        clock_step = 480 * 4 / 4;
                        break;
                    case QuantizeMode.q08:
                        clock_step = 480 * 4 / 8;
                        break;
                    case QuantizeMode.q16:
                        clock_step = 480 * 4 / 16;
                        break;
                    case QuantizeMode.q32:
                        clock_step = 480 * 4 / 32;
                        break;
                    case QuantizeMode.q64:
                        clock_step = 480 * 4 / 64;
                        break;
                }
                if ( mode != QuantizeMode.off && triplet ) {
                    clock_step = clock_step * 4 / 3;
                }
                if( i == m_timesig_ex.Count - 1 ) {
                    double tempo;
                    if( m_tempo.Count > 0 ) {
                        tempo = m_tempo[m_tempo.Count - 1].Tempo;
                    } else {
                        tempo = m_base_tempo;
                    }
                    end_clock = (int)((m_totalSec - SecFromClock( m_timesig_ex[i].Clock ) + 1.0) * tpq_sec / tempo);
                } else {
                    end_clock = m_timesig_ex[i + 1].Clock;
                }
                // todo: SettingsEx+GetBarLineTypeEnumerator; clock_per_barがclock_stepの整数倍とならない場合の処理
                for( int clock = local_clock; clock < end_clock; clock += clock_step ) {
                    if( (clock - local_clock) % clock_per_bar == 0 ) {
                        yield return new BarLineType( SecFromClock( clock ), true, false );
                    } else {
                        yield return new BarLineType( SecFromClock( clock ), false, false );
                    }
                }
            }
        }

        /// <summary>
        /// 指定したクロックにおける、clock=0からの演奏経過時間(sec)
        /// </summary>
        /// <param name="clock"></param>
        /// <returns></returns>
        private double SecFromClock( int clock ) {
            if( m_tempo.Count == 0 ) {
                return (double)m_base_tempo * (double)clock / tpq_sec;
            } else {
                int index = 0;
                for( int i = 0; i < m_tempo.Count; i++ ) {
                    if( clock <= m_tempo[i].Clock ) {
                        index = i;
                        break;
                    }
                }
                return m_tempo[index].Time + (double)(m_tempo[index].Tempo) * (clock - m_tempo[index].Clock) / tpq_sec;
            }
        }
    }

}
