using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows.Forms;
using org.kbinani;
using org.kbinani.apputil;
using org.kbinani.cadencii;

class UtauPluginManager : Form {
    delegate void VoidDelegate();
    
    private Button btnOk;
    private Button btnAdd;
    private Button btnRemove;
    private Button btnCancel;
    /// <summary>
    /// この部分は、Utau Plugin Invoker.csについて、以下のテキスト処理を行ったもの。
    ///     1. 「"」を「\"」に置換（テキストモード）
    ///     2. 「\n」を「\\n" +\n"」に置換（正規表現モード）
    ///     3. 「Utau_Plugin_Invoker」を「{0}」に置換。
    ///     4. 「s_plugin_txt_path = @"E:\Program Files\UTAU\..(一部略)..";」を「s_plugin_txt_path = @"{1}";」に書き換え。
    /// または、付属のツールで次のように処理する
    ///     ParseUtauPluginInvoker.exe ".\ScriptImplement\Utau Plugin Invoker.cs" out.txt
    /// </summary>
    private static readonly String TEXT = "using System;\n" +
        "using System.Collections.Generic;\n" +
        "using System.IO;\n" +
        "using System.Text;\n" +
        "using System.Windows.Forms;\n" +
        "using System.Threading;\n" +
        "using org.kbinani.cadencii;\n" +
        "using org.kbinani.java.util;\n" +
        "using org.kbinani.vsq;\n" +
        "using org.kbinani;\n" +
        "\n" +
        "public class {0} : Form {\n" +
        "    class StartPluginArgs {\n" +
        "        public string exePath = \"\";\n" +
        "        public string tmpFile = \"\";\n" +
        "    }\n" +
        "\n" +
        "    private static string s_plugin_txt_path = @\"{1}\";\n" +
        "    private Label lblMessage;\n" +
        "    private static readonly string s_class_name = \"{0}\";\n" +
        "    private static readonly string s_display_name = \"{0}\";\n" +
        "\n" +
        "    private string m_exe_path = \"\";\n" +
        "    private System.ComponentModel.BackgroundWorker bgWork;\n" +
        "    private string m_temp = \"\";\n" +
        "\n" +
        "    private {0}( string exe_path, string temp_file ) {\n" +
        "        InitializeComponent();\n" +
        "        m_exe_path = exe_path;\n" +
        "        m_temp = temp_file;\n" +
        "    }\n" +
        "\n" +
        "    private void InitializeComponent() {\n" +
        "        this.lblMessage = new System.Windows.Forms.Label();\n" +
        "        this.bgWork = new System.ComponentModel.BackgroundWorker();\n" +
        "        this.SuspendLayout();\n" +
        "        // \n" +
        "        // lblMessage\n" +
        "        // \n" +
        "        this.lblMessage.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)\n" +
        "                    | System.Windows.Forms.AnchorStyles.Right)));\n" +
        "        this.lblMessage.Location = new System.Drawing.Point( 12, 21 );\n" +
        "        this.lblMessage.Name = \"lblMessage\";\n" +
        "        this.lblMessage.Size = new System.Drawing.Size( 289, 23 );\n" +
        "        this.lblMessage.TabIndex = 0;\n" +
        "        this.lblMessage.Text = \"waiting plugin process...\";\n" +
        "        this.lblMessage.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;\n" +
        "        // \n" +
        "        // bgWork\n" +
        "        // \n" +
        "        this.bgWork.DoWork += new System.ComponentModel.DoWorkEventHandler( this.bgWork_DoWork );\n" +
        "        this.bgWork.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler( this.bgWork_RunWorkerCompleted );\n" +
        "        // \n" +
        "        // {0}\n" +
        "        // \n" +
        "        this.ClientSize = new System.Drawing.Size( 313, 119 );\n" +
        "        this.Controls.Add( this.lblMessage );\n" +
        "        this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;\n" +
        "        this.MaximizeBox = false;\n" +
        "        this.MinimizeBox = false;\n" +
        "        this.Name = \"{0}\";\n" +
        "        this.ShowIcon = false;\n" +
        "        this.ShowInTaskbar = false;\n" +
        "        this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;\n" +
        "        this.Text = \"Utau Plugin Invoker\";\n" +
        "        this.Load += new System.EventHandler( this.{0}_Load );\n" +
        "        this.ResumeLayout( false );\n" +
        "\n" +
        "    }\n" +
        "\n" +
        "    public static ScriptReturnStatus Edit( VsqFileEx vsq )\n" +
        "    {\n" +
        "        // 選択状態のアイテムがなければ戻る\n" +
        "        if ( AppManager.getSelectedEventCount() <= 0 ) {\n" +
        "            return ScriptReturnStatus.NOT_EDITED;\n" +
        "        }\n" +
        "\n" +
        "        // 現在のトラック\n" +
        "        int selected = AppManager.getSelected();\n" +
        "        VsqTrack vsq_track = vsq.Track.get( selected );\n" +
        "        vsq_track.sortEvent();\n" +
        "\n" +
        "        // プラグイン情報の定義ファイル(plugin.txt)があるかどうかチェック\n" +
        "        string pluginTxtPath = s_plugin_txt_path;\n" +
        "        if ( pluginTxtPath == \"\" ) {\n" +
        "            AppManager.showMessageBox( \"pluginTxtPath=\" + pluginTxtPath );\n" +
        "            return ScriptReturnStatus.ERROR;\n" +
        "        }\n" +
        "        if ( !System.IO.File.Exists( pluginTxtPath ) ) {\n" +
        "            AppManager.showMessageBox( \"'\" + pluginTxtPath + \"' does not exists\" );\n" +
        "            return ScriptReturnStatus.ERROR;\n" +
        "        }\n" +
        "\n" +
        "        // plugin.txtがあれば，プラグインの実行ファイルのパスを取得する\n" +
        "        System.Text.Encoding shift_jis = System.Text.Encoding.GetEncoding( \"Shift_JIS\" );\n" +
        "        string name = \"\";\n" +
        "        string exe_path = \"\";\n" +
        "        using ( StreamReader sr = new StreamReader( pluginTxtPath, shift_jis ) ) {\n" +
        "            string line = \"\";\n" +
        "            while ( (line = sr.ReadLine()) != null ) {\n" +
        "                string[] spl = line.Split( new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries );\n" +
        "                if ( line.StartsWith( \"name=\" ) ) {\n" +
        "                    name = spl[1];\n" +
        "                } else if ( line.StartsWith( \"execute=\" ) ) {\n" +
        "                    exe_path = Path.Combine( Path.GetDirectoryName( pluginTxtPath ), spl[1] );\n" +
        "                }\n" +
        "            }\n" +
        "        }\n" +
        "        if ( exe_path == \"\" ) {\n" +
        "            return ScriptReturnStatus.ERROR;\n" +
        "        }\n" +
        "        if ( !System.IO.File.Exists( exe_path ) ) {\n" +
        "            AppManager.showMessageBox( \"'\" + exe_path + \"' does not exists\" );\n" +
        "            return ScriptReturnStatus.ERROR;\n" +
        "        }\n" +
        "\n" +
        "        // 選択状態のアイテムの最初と最後がどこか調べる\n" +
        "        // clock_start, clock_endは，最終的にはPREV, NEXTを含んだ範囲を表すことになる\n" +
        "        // sel_start, sel_endはPREV, NEXTを含まない選択範囲を表す\n" +
        "        int id_start = -1;\n" +
        "        int clock_start = int.MaxValue;\n" +
        "        int id_end = -1;\n" +
        "        int clock_end = int.MinValue;\n" +
        "        int sel_start = 0;\n" +
        "        int sel_end = 0;\n" +
        "        for ( Iterator<SelectedEventEntry> itr = AppManager.getSelectedEventIterator(); itr.hasNext(); ) {\n" +
        "            SelectedEventEntry item = itr.next();\n" +
        "            if ( item.original.ID.type != VsqIDType.Anote ) {\n" +
        "                continue;\n" +
        "            }\n" +
        "            int clock = item.original.Clock;\n" +
        "            if ( clock < clock_start ) {\n" +
        "                id_start = item.original.InternalID;\n" +
        "                clock_start = clock;\n" +
        "                sel_start = clock;\n" +
        "            }\n" +
        "            clock += item.original.ID.getLength();\n" +
        "            if ( clock_end < clock ) {\n" +
        "                id_end = item.original.InternalID;\n" +
        "                clock_end = clock;\n" +
        "                sel_end = clock;\n" +
        "            }\n" +
        "        }\n" +
        "\n" +
        "        // 選択範囲の前後の音符を探す\n" +
        "        VsqEvent ve_prev = null;\n" +
        "        VsqEvent ve_next = null;\n" +
        "        VsqEvent l = null;\n" +
        "        for ( Iterator<VsqEvent> itr = vsq_track.getNoteEventIterator(); itr.hasNext(); ) {\n" +
        "            VsqEvent item = itr.next();\n" +
        "            if ( item.InternalID == id_start ) {\n" +
        "                if ( l != null ) {\n" +
        "                    ve_prev = l;\n" +
        "                }\n" +
        "            }\n" +
        "            if ( l != null ) {\n" +
        "                if ( l.InternalID == id_end ) {\n" +
        "                    ve_next = item;\n" +
        "                }\n" +
        "            }\n" +
        "            l = item;\n" +
        "            if ( ve_prev != null && ve_next != null ) {\n" +
        "                break;\n" +
        "            }\n" +
        "        }\n" +
        "        int next_rest_clock = -1;\n" +
        "        bool prev_is_rest = false;\n" +
        "        if ( ve_prev != null ) {\n" +
        "            // 直前の音符がある場合\n" +
        "            if ( ve_prev.Clock + ve_prev.ID.getLength() == clock_start ) {\n" +
        "                // 接続している\n" +
        "                clock_start = ve_prev.Clock;\n" +
        "            } else {\n" +
        "                // 接続していない\n" +
        "                int new_clock_start = ve_prev.Clock + ve_prev.ID.getLength();\n" +
        "                clock_start = new_clock_start;\n" +
        "            }\n" +
        "        } else {\n" +
        "            // 無い場合\n" +
        "            if ( vsq.getPreMeasureClocks() < clock_start ) {\n" +
        "                prev_is_rest = true;\n" +
        "            }\n" +
        "            int new_clock_start = vsq.getPreMeasureClocks();\n" +
        "            clock_start = new_clock_start;\n" +
        "        }\n" +
        "        if ( ve_next != null ) {\n" +
        "            // 直後の音符がある場合\n" +
        "            if ( ve_next.Clock == clock_end ) {\n" +
        "                // 接続している\n" +
        "                clock_end = ve_next.Clock + ve_next.ID.getLength();\n" +
        "            } else {\n" +
        "                // 接続していない\n" +
        "                next_rest_clock = clock_end;\n" +
        "                clock_end = ve_next.Clock;\n" +
        "            }\n" +
        "        }\n" +
        "\n" +
        "        // 作業用のVSQに，選択範囲のアイテムを格納\n" +
        "        VsqFileEx v = (VsqFileEx)vsq.clone();// new VsqFile( \"Miku\", 1, 4, 4, 500000 );\n" +
        "        // 選択トラックだけ残して他を削る\n" +
        "        for ( int i = 1; i < selected; i++ ) {\n" +
        "            v.Track.removeElementAt( 1 );\n" +
        "        }\n" +
        "        for ( int i = selected + 1; i < v.Track.size(); i++ ) {\n" +
        "            v.Track.removeElementAt( selected + 1 );\n" +
        "        }\n" +
        "        // 選択トラックの音符を全消去する\n" +
        "        VsqTrack v_track = v.Track.get( 1 );\n" +
        "        v_track.MetaText.getEventList().clear();\n" +
        "        for ( Iterator<VsqEvent> itr = vsq_track.getNoteEventIterator(); itr.hasNext(); ) {\n" +
        "            VsqEvent item = itr.next();\n" +
        "            if ( clock_start <= item.Clock && item.Clock + item.ID.getLength() <= clock_end ) {\n" +
        "                v_track.addEvent( (VsqEvent)item.clone(), item.InternalID );\n" +
        "            }\n" +
        "        }\n" +
        "        // 最後のRを手動で追加．これは自動化できない\n" +
        "        if ( next_rest_clock != -1 ) {\n" +
        "            VsqEvent item = (VsqEvent)ve_next.clone();\n" +
        "            item.ID.LyricHandle.L0.Phrase = \"R\";\n" +
        "            item.Clock = next_rest_clock;\n" +
        "            item.ID.setLength( clock_end - next_rest_clock );\n" +
        "            v_track.addEvent( item );\n" +
        "        }\n" +
        "        // 0～選択範囲の開始位置までを削除する\n" +
        "        v.removePart( 0, clock_start );\n" +
        "\n" +
        "        // vsq -> ustに変換\n" +
        "        // キーがustのIndex, 値がInternalID\n" +
        "        TreeMap<int, int> map = new TreeMap<int, int>();\n" +
        "        UstFile u = new UstFile( v, 1, map );\n" +
        "\n" +
        "        u.write( fsys.combine( PortUtil.getApplicationStartupPath(), \"u.ust\" ) );\n" +
        "\n" +
        "        // PREV, NEXTのIndex値を設定する\n" +
        "        if ( ve_prev != null || prev_is_rest ) {\n" +
        "            u.getTrack( 0 ).getEvent( 0 ).Index = UstFile.PREV_INDEX;\n" +
        "        }\n" +
        "        if ( ve_next != null ) {\n" +
        "            u.getTrack( 0 ).getEvent( u.getTrack( 0 ).getEventCount() - 1 ).Index = UstFile.NEXT_INDEX;\n" +
        "        }\n" +
        "\n" +
        "        // ustファイルに出力\n" +
        "        UstFileWriteOptions option = new UstFileWriteOptions();\n" +
        "        option.settingCacheDir = false;\n" +
        "        option.settingOutFile = false;\n" +
        "        option.settingProjectName = false;\n" +
        "        option.settingTempo = true;\n" +
        "        option.settingTool1 = true;\n" +
        "        option.settingTool2 = true;\n" +
        "        option.settingTracks = false;\n" +
        "        option.settingVoiceDir = true;\n" +
        "        option.trackEnd = false;\n" +
        "        string temp = Path.GetTempFileName();\n" +
        "        u.write( temp, option );\n" +
        "\n" +
        "        StringBuilder before = new StringBuilder();\n" +
        "        using ( StreamReader sr = new StreamReader( temp, System.Text.Encoding.GetEncoding( \"Shift_JIS\" ) ) ) {\n" +
        "            string line = \"\";\n" +
        "            while ( (line = sr.ReadLine()) != null ) {\n" +
        "                before.AppendLine( line );\n" +
        "            }\n" +
        "        }\n" +
        "        String md5_before = PortUtil.getMD5FromString( before.ToString() );\n" +
        "        // プラグインの実行ファイルを起動\n" +
        "        {0} dialog = new {0}( exe_path, temp );\n" +
        "        dialog.ShowDialog();\n" +
        "        StringBuilder after = new StringBuilder();\n" +
        "        using ( StreamReader sr = new StreamReader( temp, System.Text.Encoding.GetEncoding( \"Shift_JIS\" ) ) ) {\n" +
        "            string line = \"\";\n" +
        "            while ( (line = sr.ReadLine()) != null ) {\n" +
        "                after.AppendLine( line );\n" +
        "            }\n" +
        "        }\n" +
        "        String md5_after = PortUtil.getMD5FromString( after.ToString() );\n" +
        "        if ( str.compare( md5_before, md5_after ) ) {\n" +
        "            // 編集されなかったようだ\n" +
        "            return ScriptReturnStatus.NOT_EDITED;\n" +
        "        }\n" +
        "\n" +
        "        // プラグインの実行結果をustオブジェクトにロード\n" +
        "        UstFile r = new UstFile( temp );\n" +
        "        if ( r.getTrackCount() < 1 ) {\n" +
        "            return ScriptReturnStatus.ERROR;\n" +
        "        }\n" +
        "\n" +
        "        // 変更のなかったものについてはプラグインは記録してこないので，\n" +
        "        // 最初の値を代入するようにする\n" +
        "        UstTrack utrack_src = u.getTrack( 0 );\n" +
        "        UstTrack utrack_dst = r.getTrack( 0 );\n" +
        "        for ( int i = 0; i < utrack_dst.getEventCount(); i++ ) {\n" +
        "            UstEvent ue_dst = utrack_dst.getEvent( i );\n" +
        "            int index = ue_dst.Index;\n" +
        "            UstEvent ue_src = utrack_src.findEventFromIndex( index );\n" +
        "            if ( ue_src == null ) {\n" +
        "                continue;\n" +
        "            }\n" +
        "            if ( !ue_dst.isEnvelopeSpecified() && ue_src.isEnvelopeSpecified() ) {\n" +
        "                ue_dst.setEnvelope( ue_src.getEnvelope() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isIntensitySpecified() && ue_src.isIntensitySpecified() ) {\n" +
        "                ue_dst.setIntensity( ue_src.getIntensity() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isLengthSpecified() && ue_src.isLengthSpecified() ) {\n" +
        "                ue_dst.setLength( ue_src.getLength() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isLyricSpecified() && ue_src.isLyricSpecified() ) {\n" +
        "                ue_dst.setLyric( ue_src.getLyric() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isModurationSpecified() && ue_src.isModurationSpecified() ) {\n" +
        "                ue_dst.setModuration( ue_src.getModuration() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isNoteSpecified() && ue_src.isNoteSpecified() ) {\n" +
        "                ue_dst.setNote( ue_src.getNote() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isPBTypeSpecified() && ue_src.isPBTypeSpecified() ) {\n" +
        "                ue_dst.setPBType( ue_src.getPBType() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isPitchesSpecified() && ue_src.isPitchesSpecified() ) {\n" +
        "                ue_dst.setPitches( ue_src.getPitches() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isPortamentoSpecified() && ue_src.isPortamentoSpecified() ) {\n" +
        "                ue_dst.setPortamento( ue_src.getPortamento() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isPreUtteranceSpecified() && ue_src.isPreUtteranceSpecified() ) {\n" +
        "                ue_dst.setPreUtterance( ue_src.getPreUtterance() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isStartPointSpecified() && ue_src.isStartPointSpecified() ) {\n" +
        "                ue_dst.setStartPoint( ue_src.getStartPoint() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isTempoSpecified() && ue_src.isTempoSpecified() ) {\n" +
        "                ue_dst.setTempo( ue_src.getTempo() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isVibratoSpecified() && ue_src.isVibratoSpecified() ) {\n" +
        "                ue_dst.setVibrato( ue_src.getVibrato() );\n" +
        "            }\n" +
        "            if ( !ue_dst.isVoiceOverlapSpecified() && ue_src.isVoiceOverlapSpecified() ) {\n" +
        "                ue_dst.setVoiceOverlap( ue_src.getVoiceOverlap() );\n" +
        "            }\n" +
        "        }\n" +
        "\n" +
        "        // PREVとNEXT含めて，clock_startからclock_endまでプラグインに渡したけれど，\n" +
        "        // それが伸びて帰ってきたか縮んで帰ってきたか．\n" +
        "        int ret_length = 0;\n" +
        "        UstTrack r_track = r.getTrack( 0 );\n" +
        "        int size = r_track.getEventCount();\n" +
        "        for ( int i = 0; i < size; i++ ) {\n" +
        "            UstEvent ue = r_track.getEvent( i );\n" +
        "            // 戻りのustには，変更があったものしか記録されていない\n" +
        "            int ue_length = ue.getLength();\n" +
        "            if ( !ue.isLengthSpecified() && map.ContainsKey( ue.Index ) ) {\n" +
        "                int internal_id = map[ue.Index];\n" +
        "                VsqEvent found_item = vsq_track.findEventFromID( internal_id );\n" +
        "                if ( found_item != null ) {\n" +
        "                    ue_length = found_item.ID.getLength();\n" +
        "                }\n" +
        "            }\n" +
        "            // PREV, ENDの場合は長さに加えない\n" +
        "            if ( ue.Index != UstFile.NEXT_INDEX &&\n" +
        "                 ue.Index != UstFile.PREV_INDEX ) {\n" +
        "                ret_length += ue_length;\n" +
        "            }\n" +
        "        }\n" +
        "\n" +
        "        // 伸び縮みがあった場合\n" +
        "        // 伸ばしたり縮めたりするよ\n" +
        "        int delta = ret_length - (sel_end - sel_start);\n" +
        "        if ( delta > 0 ) {\n" +
        "            // のびた\n" +
        "            vsq.insertBlank( selected, sel_end, delta );\n" +
        "        } else if ( delta < 0 ) {\n" +
        "            // 縮んだ\n" +
        "            vsq.removePart( selected, sel_end + delta, sel_end );\n" +
        "        }\n" +
        "\n" +
        "        // r_trackの内容をvsq_trackに転写\n" +
        "        size = r_track.getEventCount();\n" +
        "        int c = clock_start;\n" +
        "        for ( int i = 0; i < size; i++ ) {\n" +
        "            UstEvent ue = r_track.getEvent( i );\n" +
        "            if ( ue.Index == UstFile.NEXT_INDEX || ue.Index == UstFile.PREV_INDEX ) {\n" +
        "                // PREVとNEXTは単に無視する\n" +
        "                continue;\n" +
        "            }\n" +
        "            int ue_length = ue.getLength();\n" +
        "            if ( map.containsKey( ue.Index ) ) {\n" +
        "                // 既存の音符の編集\n" +
        "                VsqEvent target = vsq_track.findEventFromID( map[ue.Index] );\n" +
        "                if ( target == null ) {\n" +
        "                    // そんなばかな・・・\n" +
        "                    continue;\n" +
        "                }\n" +
        "                if ( !ue.isLengthSpecified() ) {\n" +
        "                    ue_length = target.ID.getLength();\n" +
        "                }\n" +
        "                if ( target.UstEvent == null ) {\n" +
        "                    target.UstEvent = (UstEvent)ue.clone();\n" +
        "                }\n" +
        "                // utau固有のパラメータを転写\n" +
        "                // pitchは後でやるので無視していい\n" +
        "                // テンポもあとでやるので無視していい\n" +
        "                if ( ue.isEnvelopeSpecified() ) {\n" +
        "                    target.UstEvent.setEnvelope( ue.getEnvelope() );\n" +
        "                }\n" +
        "                if ( ue.isModurationSpecified() ) {\n" +
        "                    target.UstEvent.setModuration( ue.getModuration() );\n" +
        "                }\n" +
        "                if ( ue.isPBTypeSpecified() ) {\n" +
        "                    target.UstEvent.setPBType( ue.getPBType() );\n" +
        "                }\n" +
        "                if ( ue.isPortamentoSpecified() ) {\n" +
        "                    target.UstEvent.setPortamento( ue.getPortamento() );\n" +
        "                }\n" +
        "                if ( ue.isPreUtteranceSpecified() ) {\n" +
        "                    target.UstEvent.setPreUtterance( ue.getPreUtterance() );\n" +
        "                }\n" +
        "                if ( ue.isStartPointSpecified() ) {\n" +
        "                    target.UstEvent.setStartPoint( ue.getStartPoint() );\n" +
        "                }\n" +
        "                if ( ue.isVibratoSpecified() ) {\n" +
        "                    target.UstEvent.setVibrato( ue.getVibrato() );\n" +
        "                }\n" +
        "                if ( ue.isVoiceOverlapSpecified() ) {\n" +
        "                    target.UstEvent.setVoiceOverlap( ue.getVoiceOverlap() );\n" +
        "                }\n" +
        "                // vocaloid, utauで同じ意味のパラメータを転写\n" +
        "                if ( ue.isIntensitySpecified() ) {\n" +
        "                    target.UstEvent.setIntensity( ue.getIntensity() );\n" +
        "                    target.ID.Dynamics = ue.getIntensity();\n" +
        "                }\n" +
        "                if ( ue.isLengthSpecified() ) {\n" +
        "                    target.UstEvent.setLength( ue.getLength() );\n" +
        "                    target.ID.setLength( ue.getLength() );\n" +
        "                }\n" +
        "                if ( ue.isLyricSpecified() ) {\n" +
        "                    target.UstEvent.setLyric( ue.getLyric() );\n" +
        "                    target.ID.LyricHandle.L0.Phrase = ue.getLyric();\n" +
        "                }\n" +
        "                if ( ue.isNoteSpecified() ) {\n" +
        "                    target.UstEvent.setNote( ue.getNote() );\n" +
        "                    target.ID.Note = ue.getNote();\n" +
        "                }\n" +
        "            } else {\n" +
        "                // マップに入っていないので，新しい音符の追加だと思う\n" +
        "                if ( str.compare( ue.getLyric(), \"R\" ) ) {\n" +
        "                    // 休符．なにもしない\n" +
        "                } else {\n" +
        "                    VsqEvent newe = new VsqEvent();\n" +
        "                    newe.Clock = c;\n" +
        "                    newe.UstEvent = (UstEvent)ue.clone();\n" +
        "                    newe.ID = new VsqID();\n" +
        "                    AppManager.editorConfig.applyDefaultSingerStyle( newe.ID );\n" +
        "                    if ( ue.isIntensitySpecified() ) {\n" +
        "                        newe.ID.Dynamics = ue.getIntensity();\n" +
        "                    }\n" +
        "                    newe.ID.LyricHandle = new LyricHandle( \"あ\", \"a\" );\n" +
        "                    if ( ue.isLyricSpecified() ) {\n" +
        "                        newe.ID.LyricHandle.L0.Phrase = ue.getLyric();\n" +
        "                    }\n" +
        "                    newe.ID.Note = ue.getNote();\n" +
        "                    newe.ID.setLength( ue.getLength() );\n" +
        "                    newe.ID.type = VsqIDType.Anote;\n" +
        "                    // internal id はaddEventメソッドで自動で割り振られる\n" +
        "                    vsq_track.addEvent( newe );\n" +
        "                }\n" +
        "            }\n" +
        "\n" +
        "            // テンポの追加がないかチェック\n" +
        "            if ( ue.isTempoSpecified() ) {\n" +
        "                insertTempoInto( vsq, c, ue.getTempo() );\n" +
        "            }\n" +
        "\n" +
        "            c += ue_length;\n" +
        "        }\n" +
        "\n" +
        "        // ピッチを転写\n" +
        "        // pitのデータがほしいので，PREV, NEXTを削除して，VsqFileにコンバートする\n" +
        "        UstFile uf = (UstFile)r.clone();\n" +
        "        // prev, nextを削除\n" +
        "        UstTrack uf_track = uf.getTrack( 0 );\n" +
        "        for ( int i = 0; i < uf_track.getEventCount(); ) {\n" +
        "            UstEvent ue = uf_track.getEvent( i );\n" +
        "            if ( ue.Index == UstFile.NEXT_INDEX ||\n" +
        "                 ue.Index == UstFile.PREV_INDEX ) {\n" +
        "                uf_track.removeEventAt( i );\n" +
        "            } else {\n" +
        "                i++;\n" +
        "            }\n" +
        "        }\n" +
        "        uf.updateTempoInfo();\n" +
        "        // VsqFileにコンバート\n" +
        "        VsqFile uf_vsq = new VsqFile( uf );\n" +
        "        // uf_vsqの最初のトラックの0からret_lengthクロックまでが，\n" +
        "        // vsq_trackのsel_startからsel_start+ret_lengthクロックまでに対応する．\n" +
        "        // まずPBSをコピーする\n" +
        "        CurveType[] type = new CurveType[] { CurveType.PBS, CurveType.PIT };\n" +
        "        foreach ( CurveType ct in type ) {\n" +
        "            // コピー元を取得\n" +
        "            VsqBPList src = vec.get( uf_vsq.Track, 1 ).getCurve( ct.getName() );\n" +
        "            if ( src != null ) {\n" +
        "                // コピー先を取得\n" +
        "                VsqBPList dst = vsq_track.getCurve( ct.getName() );\n" +
        "                if ( dst == null ) {\n" +
        "                    // コピー先がnullだった場合は作成\n" +
        "                    dst = new VsqBPList( ct.getName(), ct.getDefault(), ct.getMinimum(), ct.getMaximum() );\n" +
        "                    vsq_track.setCurve( ct.getName(), dst );\n" +
        "                }\n" +
        "                // あとで復元するので，最終位置での値を保存しておく\n" +
        "                int value_at_end = dst.getValue( sel_start + ret_length );\n" +
        "                // 復元するかどうか．最終位置にそもそもデータ点があれば復帰の必要がないので．\n" +
        "                bool do_revert = (dst.findIndexFromClock( sel_start + ret_length ) < 0);\n" +
        "                // [sel_start, sel_start + ret_length)の範囲の値を削除しておく\n" +
        "                size = dst.size();\n" +
        "                for ( int i = size - 1; i >= 0; i-- ) {\n" +
        "                    int cl = dst.getKeyClock( i );\n" +
        "                    if ( sel_start <= cl && cl < sel_start + ret_length ) {\n" +
        "                        dst.removeElementAt( i );\n" +
        "                    }\n" +
        "                }\n" +
        "                // コピーを実行\n" +
        "                size = src.size();\n" +
        "                for ( int i = 0; i < size; i++ ) {\n" +
        "                    int cl = src.getKeyClock( i );\n" +
        "                    if ( ret_length <= cl ) {\n" +
        "                        break;\n" +
        "                    }\n" +
        "                    int value = src.getElementA( i );\n" +
        "                    dst.add( cl + sel_start, value );\n" +
        "                }\n" +
        "                // コピー後，最終位置での値が元と異なる場合，元に戻すようにする\n" +
        "                if ( do_revert && dst.getValue( sel_start + ret_length ) != value_at_end ) {\n" +
        "                    dst.add( sel_start + ret_length, value_at_end );\n" +
        "                }\n" +
        "            }\n" +
        "        }\n" +
        "\n" +
        "        return ScriptReturnStatus.EDITED;\n" +
        "    }\n" +
        "\n" +
        "    /// <summary>\n" +
        "    /// 指定したVSQの指定した位置に，テンポの挿入を試みます．\n" +
        "    /// 既存のテンポがある場合，値が上書きされます\n" +
        "    /// </summary>\n" +
        "    /// <param name=\"vsq\">挿入対象のVSQ</param>\n" +
        "    /// <param name=\"clock\">挿入位置</param>\n" +
        "    /// <param name=\"tempo\">楽譜表記上のテンポ．BPS</param>\n" +
        "    private static void insertTempoInto( VsqFileEx vsq, int clock, float t )\n" +
        "    {\n" +
        "        // clockの位置にテンポ変更があるかどうか？\n" +
        "        int num_tempo = vsq.TempoTable.size();\n" +
        "        int index = -1;\n" +
        "        for ( int j = 0; j < num_tempo; j++ ) {\n" +
        "            TempoTableEntry itemj = vec.get( vsq.TempoTable, j );\n" +
        "            if ( itemj.Clock == clock ) {\n" +
        "                index = j;\n" +
        "                break;\n" +
        "            }\n" +
        "        }\n" +
        "        int tempo = (int)(60e6 / t);\n" +
        "        if ( index >= 0 ) {\n" +
        "            // clock位置に既存のテンポ変更がある場合，テンポ値を変更\n" +
        "            TempoTableEntry itemj = vec.get( vsq.TempoTable, index );\n" +
        "            itemj.Tempo = tempo;\n" +
        "        } else {\n" +
        "            // 既存のものはないので新規に追加\n" +
        "            vec.add( vsq.TempoTable, new TempoTableEntry( clock, tempo, 0.0 ) );\n" +
        "        }\n" +
        "        // テンポテーブルを更新\n" +
        "        vsq.TempoTable.updateTempoInfo();\n" +
        "    }\n" +
        "\n" +
        "    private void {0}_Load( object sender, EventArgs e ) {\n" +
        "        bgWork.RunWorkerAsync();\n" +
        "    }\n" +
        "\n" +
        "    private void bgWork_DoWork( object sender, System.ComponentModel.DoWorkEventArgs e ) {\n" +
        "        string dquote = new string( (char)0x22, 1 );\n" +
        "        using ( System.Diagnostics.Process p = new System.Diagnostics.Process() ) {\n" +
        "            p.StartInfo.FileName = m_exe_path;\n" +
        "            p.StartInfo.Arguments = dquote + m_temp + dquote;\n" +
        "            p.StartInfo.WorkingDirectory = Path.GetDirectoryName( m_exe_path );\n" +
        "            p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;\n" +
        "            p.Start();\n" +
        "            p.WaitForExit();\n" +
        "        }\n" +
        "    }\n" +
        "\n" +
        "    private void bgWork_RunWorkerCompleted( object sender, System.ComponentModel.RunWorkerCompletedEventArgs e ) {\n" +
        "        this.Close();\n" +
        "    }\n" +
        "\n" +
        "    public static String GetDisplayName() {\n" +
        "        return s_display_name;\n" +
        "    }\n" +
        "}\n" + "";
    private ListView listPlugins;
    private ColumnHeader headerName;
    private ColumnHeader headerPath;
    private OpenFileDialog openFileDialog;

    /// <summary>
    /// plugin.txtのパスのリスト
    /// </summary>
    public static List<string> Plugins = new List<string>();
    public static int ColumnWidthName = 100;
    public static int ColumnWidthPath = 200;
    public static int DialogWidth = 295;
    public static int DialogHeight = 352;
    private static List<string> oldPlugins = null;

    public UtauPluginManager() {
        InitializeComponent();
        if ( ColumnWidthName > 0 ) {
            headerName.Width = ColumnWidthName;
        }
        if ( ColumnWidthPath > 0 ) {
            headerPath.Width = ColumnWidthPath;
        }
        if ( DialogWidth > 0 ) {
            Width = DialogWidth;
        }
        if ( DialogHeight > 0 ) {
            Height = DialogHeight;
        }

        btnAdd.Text = _( "Add" );
        btnRemove.Text = _( "Remove" );
        btnOk.Text = _( "OK" );
        btnCancel.Text = _( "Cancel" );
        headerName.Text = _( "Name" );
        headerPath.Text = _( "Path" );

        oldPlugins = new List<string>();
        Encoding sjis = Encoding.GetEncoding( "Shift_JIS" );
        foreach ( string s in getPlugins() ) {
            if ( !System.IO.File.Exists( s ) ) {
                continue;
            }
            string name = getPluginName( s );
            if ( name != "" ) {
                listPlugins.Items.Add( new ListViewItem( new string[] { name, s } ) );
            }
            oldPlugins.Add( s );
        }
    }

    private static List<string> getPlugins() {
        if ( Plugins == null ) {
            Plugins = new List<string>();
        }
        return Plugins;
    }

    public static ScriptReturnStatus Edit( VsqFileEx vsq ) {
        UtauPluginManager dialog = new UtauPluginManager();
        dialog.ShowDialog();
        return ScriptReturnStatus.NOT_EDITED;
    }

    private void InitializeComponent() {
        this.btnOk = new System.Windows.Forms.Button();
        this.btnAdd = new System.Windows.Forms.Button();
        this.btnRemove = new System.Windows.Forms.Button();
        this.btnCancel = new System.Windows.Forms.Button();
        this.listPlugins = new System.Windows.Forms.ListView();
        this.headerName = new System.Windows.Forms.ColumnHeader();
        this.headerPath = new System.Windows.Forms.ColumnHeader();
        this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
        this.SuspendLayout();
        // 
        // btnOk
        // 
        this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
        this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK;
        this.btnOk.Location = new System.Drawing.Point( 119, 283 );
        this.btnOk.Name = "btnOk";
        this.btnOk.Size = new System.Drawing.Size( 75, 23 );
        this.btnOk.TabIndex = 7;
        this.btnOk.Text = "OK";
        this.btnOk.UseVisualStyleBackColor = true;
        this.btnOk.Click += new System.EventHandler( this.btnOk_Click );
        // 
        // btnAdd
        // 
        this.btnAdd.Location = new System.Drawing.Point( 12, 12 );
        this.btnAdd.Name = "btnAdd";
        this.btnAdd.Size = new System.Drawing.Size( 75, 23 );
        this.btnAdd.TabIndex = 9;
        this.btnAdd.Text = "Add";
        this.btnAdd.UseVisualStyleBackColor = true;
        this.btnAdd.Click += new System.EventHandler( this.btnAdd_Click );
        // 
        // btnRemove
        // 
        this.btnRemove.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
        this.btnRemove.Location = new System.Drawing.Point( 200, 12 );
        this.btnRemove.Name = "btnRemove";
        this.btnRemove.Size = new System.Drawing.Size( 75, 23 );
        this.btnRemove.TabIndex = 10;
        this.btnRemove.Text = "Remove";
        this.btnRemove.UseVisualStyleBackColor = true;
        this.btnRemove.Click += new System.EventHandler( this.btnRemove_Click );
        // 
        // btnCancel
        // 
        this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
        this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
        this.btnCancel.Location = new System.Drawing.Point( 200, 283 );
        this.btnCancel.Name = "btnCancel";
        this.btnCancel.Size = new System.Drawing.Size( 75, 23 );
        this.btnCancel.TabIndex = 11;
        this.btnCancel.Text = "Cancel";
        this.btnCancel.UseVisualStyleBackColor = true;
        // 
        // listPlugins
        // 
        this.listPlugins.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
                    | System.Windows.Forms.AnchorStyles.Left)
                    | System.Windows.Forms.AnchorStyles.Right)));
        this.listPlugins.Columns.AddRange( new System.Windows.Forms.ColumnHeader[] {
            this.headerName,
            this.headerPath} );
        this.listPlugins.FullRowSelect = true;
        this.listPlugins.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
        this.listPlugins.Location = new System.Drawing.Point( 12, 41 );
        this.listPlugins.Name = "listPlugins";
        this.listPlugins.Size = new System.Drawing.Size( 263, 236 );
        this.listPlugins.TabIndex = 12;
        this.listPlugins.UseCompatibleStateImageBehavior = false;
        this.listPlugins.View = System.Windows.Forms.View.Details;
        // 
        // headerName
        // 
        this.headerName.Text = "Name";
        // 
        // headerPath
        // 
        this.headerPath.Text = "Path";
        // 
        // openFileDialog
        // 
        this.openFileDialog.FileName = "plugin.txt";
        // 
        // UtauPluginManager
        // 
        this.AcceptButton = this.btnOk;
        this.CancelButton = this.btnCancel;
        this.ClientSize = new System.Drawing.Size( 287, 318 );
        this.Controls.Add( this.listPlugins );
        this.Controls.Add( this.btnCancel );
        this.Controls.Add( this.btnRemove );
        this.Controls.Add( this.btnAdd );
        this.Controls.Add( this.btnOk );
        this.MaximizeBox = false;
        this.MinimizeBox = false;
        this.Name = "UtauPluginManager";
        this.ShowIcon = false;
        this.ShowInTaskbar = false;
        this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
        this.Text = "UTAU Plugin Manager";
        this.FormClosing += new System.Windows.Forms.FormClosingEventHandler( this.UtauPluginManager_FormClosing );
        this.ResumeLayout( false );

    }

    private void btnAdd_Click( object sender, EventArgs e ) {
        if ( openFileDialog.ShowDialog() != DialogResult.OK ) {
            return;
        }

        string file = openFileDialog.FileName;
        string name = getPluginName( file );
        if ( name == "" ) {
            return;
        }

        foreach ( ListViewItem litem in listPlugins.Items ) {
            if ( litem.SubItems.Count < 2 ) {
                continue;
            }
            string tname = litem.SubItems[0].Text;
            string tfile = litem.SubItems[1].Text;

            if ( tname == name ) {
                org.kbinani.windows.forms.BDialogResult dr =
                    AppManager.showMessageBox( string.Format( _( "Script named '{0}' is already registered. Overwrite?" ), name ),
                                               "UTAU Plugin Manager",
                                               org.kbinani.windows.forms.Utility.MSGBOX_OK_CANCEL_OPTION,
                                               org.kbinani.windows.forms.Utility.MSGBOX_QUESTION_MESSAGE );
                if ( dr != org.kbinani.windows.forms.BDialogResult.YES ) {
                    return;
                }

                listPlugins.Items.Remove( litem );
                foreach ( string f in getPlugins() ) {
                    if ( f == tfile ) {
                        getPlugins().Remove( f );
                        break;
                    }
                }
            }
        }

        listPlugins.Items.Add( new ListViewItem( new string[] { name, file } ) );
        getPlugins().Add( file );
    }

    private static string getPluginName( string plugin_txt_file ) {
        if ( !fsys.isFileExists( plugin_txt_file ) ) {
            return "";
        }

        string name = "";
        using ( StreamReader sr = new StreamReader( plugin_txt_file, Encoding.GetEncoding( "Shift_JIS" ) ) ) {
            string line = "";
            while ( (line = sr.ReadLine()) != null ) {
                if ( line.StartsWith( "name=" ) ) {
                    name = line.Substring( 5 ).Trim();
                    break;
                }
            }
        }
        return name;
    }

    private static string _( string id ) {
        string lang = Messaging.getLanguage();
        if ( lang != "en" ) {
            if ( id == "Script named '{0}' is already registered. Overwrite?" ) {
                if ( lang == "ja" ) {
                    return "'{0}' という名前のスクリプトは既に登録されています。上書きしますか？";
                }
            } else if ( id == "Remove '{0}'?" ) {
                if ( lang == "ja" ) {
                    return "'{0}' を削除しますか？";
                }
            } else if ( id == "Add" ) {
                if ( lang == "ja" ) {
                    return "追加";
                }
            } else if ( id == "Remove" ) {
                if ( lang == "ja" ) {
                    return "削除";
                }
            } else if ( id == "OK" ) {
                if ( lang == "ja" ) {
                    return "了解";
                }
            } else if ( id == "Cancel" ) {
                if ( lang == "ja" ) {
                    return "取消";
                }
            } else if ( id == "Name" ) {
                if ( lang == "ja" ) {
                    return "名称";
                }
            } else if ( id == "Path" ) {
                if ( lang == "ja" ) {
                    return "保存場所";
                }
            }
        }
        return id;
    }

    private void UtauPluginManager_FormClosing( object sender, FormClosingEventArgs e ) {
        ColumnWidthName = headerName.Width;
        ColumnWidthPath = headerPath.Width;
        if ( WindowState == FormWindowState.Normal ) {
            DialogWidth = Width;
            DialogHeight = Height;
        }
    }

    private void btnRemove_Click( object sender, EventArgs e ) {
        int count = listPlugins.SelectedIndices.Count;
        if ( count <= 0 ) {
            return;
        }

        int indx = listPlugins.SelectedIndices[0];
        ListViewItem litem = listPlugins.Items[indx];
        if ( litem.SubItems.Count < 2 ){
            return;
        }
        string name = litem.SubItems[0].Text;
        string path = litem.SubItems[1].Text;

        org.kbinani.windows.forms.BDialogResult dr =
            AppManager.showMessageBox( string.Format( _( "Remove '{0}'?" ), name ),
                                       "UTAU Plugin Manager",
                                       org.kbinani.windows.forms.Utility.MSGBOX_YES_NO_OPTION,
                                       org.kbinani.windows.forms.Utility.MSGBOX_QUESTION_MESSAGE );
        if ( dr != org.kbinani.windows.forms.BDialogResult.YES ) {
            return;
        }

        listPlugins.Items.Remove( litem );
        if ( getPlugins().Contains( path ) ) {
            getPlugins().Remove( path );
        }
    }

    private void btnOk_Click( object sender, EventArgs e ) {
        if ( oldPlugins != null ) {
            // 削除されたスクリプトをアンインストールする
            foreach ( string file in oldPlugins ) {
                if ( !getPlugins().Contains( file ) ) {
                    string name = getPluginName( file );
                    if ( name != "" ) {
                        string script_path = fsys.combine( Utility.getScriptPath(), name + ".txt" );
                        if ( fsys.isFileExists( script_path ) ) {
                            PortUtil.deleteFile( script_path );
                        }
                    }
                }
            }
        }

        foreach ( string file in getPlugins() ) {
            if ( !fsys.isFileExists( file ) ) {
                continue;
            }

            string name = getPluginName( file );
            if ( name == "" ) {
                continue;
            }
            char[] invalid_classname = new char[] { ' ', '!', '#', '$', '%', '&', '\'', '(', ')', '=', '-', '~', '^', '`', '@', '{', '}', '[', ']', '+', '*', ';', '.' };
            foreach ( char c in invalid_classname ) {
                name = name.Replace( c, '_' );
            }
            string text = TEXT;
            string code = text.Replace( "{0}", name );
            code = code.Replace( "{1}", file );
            using ( StreamWriter sw = new StreamWriter( fsys.combine( Utility.getScriptPath(), name + ".txt" ) ) ) {
                sw.WriteLine( code );
            }
        }

        if ( AppManager.mMainWindow != null ) {
            VoidDelegate deleg = new VoidDelegate( AppManager.mMainWindow.updateScriptShortcut );
            if ( deleg != null ) {
                AppManager.mMainWindow.Invoke( deleg );
            }
        }
    }

    public static String GetDisplayName() {
        String lang = Messaging.getLanguage();
        if ( lang == "ja" ) {
            return "UTAU用プラグインをインストール";
        } else {
            return "Install UTAU Plugin";
        }
    }
}
