/*
 * RspImporter.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.IO;
using System.Web;
using System.Xml;

using Boare.Lib.AppUtil;

namespace LipSync {

    class RspTriggerEvent : IComparable{
        public float time;
        public string triggerName;
        public RspTriggerStatus status;
        public RspTriggerEvent( float time, string triggerName, string status ){
            this.time = time;
            this.triggerName = triggerName.Trim();
            if( status.Trim() == "ON" ){
                this.status = RspTriggerStatus.ON;
            }else{
                this.status = RspTriggerStatus.OFF;
            }
        }
        public int CompareTo( object obj ) {
            RspTriggerEvent item = (RspTriggerEvent)obj;
            if ( this.time > item.time ) {
                return 1;
            } else if ( this.time < item.time ) {
                return -1;
            } else {
                if ( this.status == RspTriggerStatus.OFF && item.status == RspTriggerStatus.ON ) {
                    return -1;
                } else if ( this.status == RspTriggerStatus.ON && item.status == RspTriggerStatus.OFF ) {
                    return 1;
                }
                return 0;
            }
        }
        new public string ToString() {
            return time.ToString() + "," + triggerName + "," + status.ToString();
        }
    }

    enum RspTriggerStatus{
        ON,
        OFF,
    }

    public class RspImporter {

        private static XmlDocument doc;

        private static TimeTableGroup group_vsq;
        private static List<TimeTableGroup> groups_character;
        private static TimeTableGroup group_another;
        private static List<Telop> telop = new List<Telop>();
        private static float optional_length;
        private static int width;
        private static int height;
        private static int back_color;
        private static string sound_file;

        public static string gettext( string s ) {
            return Messaging.GetMessage( s );
        }


        public static string _( string s ) {
            return gettext( s );
        }


        public static bool Import( string filepath, ref SettingsEx save ) {
            string rsp_path = Path.GetDirectoryName( filepath );

            doc = new XmlDocument();
            doc.Load( filepath );

            group_vsq = new TimeTableGroup( _( "VSQ Tracks" ), 0, null );
            groups_character = new List<TimeTableGroup>();
            group_another = new TimeTableGroup( _( "Another images" ), 0, null );

            // 最初にtotal_lengthを取得
            foreach( XmlNode level1 in doc.DocumentElement.ChildNodes ){
                if( level1.LocalName == "View" ){
                    optional_length = -1f;
                    foreach ( XmlNode level2 in level1.ChildNodes ) {
                        if ( level2.LocalName == "OptionalLength" ) {
                            optional_length = float.Parse( level2.InnerText );
                            break;
                        }
                    }
                    break;
                }            
            }

            foreach ( XmlNode level1 in doc.DocumentElement.ChildNodes ) {
                switch ( level1.LocalName ) {
                    case "Canvas":
                        #region Canvas
                        width = 100;
                        height = 100;
                        back_color = -1;
                        foreach ( XmlNode level2 in level1.ChildNodes ) {
                            switch ( level2.LocalName ) {
                                case "Width":
                                    width = int.Parse( level2.InnerText );
                                    break;

                                case "Height":
                                    height = int.Parse( level2.InnerText );
                                    break;

                                case "BackColor":
                                    back_color = int.Parse( level2.InnerText );
                                    break;
                            }
                        }
                        #endregion
                        break;
                    case "Sound":
                        #region Sound
                        sound_file = "";
                        foreach ( XmlNode level2 in level1.ChildNodes ) {
                            switch ( level2.LocalName ) {
                                case "FileName":
                                    sound_file = level2.InnerText;
                                    break;
                            }
                        }
                        #endregion
                        break;
                    case "TimeTable":
                        #region TimeTable
                        foreach ( XmlNode level2 in level1.ChildNodes ) {
                            if ( level2.LocalName == "Table" ) {
                                group_vsq.Add( new TimeTable( "", 0, TimeTableType.vsq, null ) );
                                int index = group_vsq.Count - 1;
                                foreach ( XmlNode level3 in level2.ChildNodes ) {
                                    switch ( level3.LocalName ) {
                                        case "Name":
                                            group_vsq[index].Text = level3.InnerText;
                                            break;
                                        case "Unit":
                                            float start = 0f;
                                            float end = 0f;
                                            string lyric = "";
                                            string symbol = "";
                                            foreach ( XmlNode level4 in level3.ChildNodes ) {
                                                switch ( level4.LocalName ) {
                                                    case "Start":
                                                        start = float.Parse( level4.InnerText );
                                                        break;
                                                    case "End":
                                                        end = float.Parse( level4.InnerText );
                                                        break;
                                                    case "Text":
                                                        lyric = level4.InnerText;
                                                        break;
                                                    case "Symbol":
                                                        symbol = level4.InnerText;
                                                        break;
                                                }
                                            }
                                            group_vsq[index].Add( new TimeTableEntry( start, end, lyric + "(" + symbol + ")" ) );
                                            break;
                                    }
                                }
                            }
                        }
                        #endregion
                        break;
                    case "DrawObject":
                        #region DrawObject
                        foreach ( XmlNode level2 in level1.ChildNodes ) {
                            if ( level2.LocalName == "Object" ) {
                                // まずtypeを調べる
                                string type_name = "";
                                foreach ( XmlNode level3 in level2.ChildNodes ) {
                                    if ( level3.LocalName == "TypeName" ) {
                                        type_name = level3.InnerText;
                                        break;
                                    }
                                }

                                // typeで動作を切り替え
                                switch ( type_name ) {
                                    case "RipSync.Plugins.Extensions.RipSyncImage.RipSyncImage":
                                        #region RipSync.Plugins.Extensions.RipSyncImage.RipSyncImage
                                        string source_table = "";
                                        CharacterEx cex = new CharacterEx();
                                        List<RspTriggerEvent> events = new List<RspTriggerEvent>();
                                        groups_character.Add( new TimeTableGroup( "", 0, null ) );
                                        int index = groups_character.Count - 1;
                                        foreach ( XmlNode level3 in level2.ChildNodes ) {
                                            switch( level3.LocalName ){
                                                case "Name":
                                                    groups_character[index].Text = level3.InnerText;
                                                    break;
                                                case "Parameter":
                                                    //MessageBox.Show( "Parameter node" );
                                                    foreach( XmlNode level4 in level3.ChildNodes ){
                                                        switch( level4.LocalName ){
                                                            case "FileName":
                                                                string rsi_path = Path.Combine( rsp_path, level4.InnerText );
                                                                cex = RsiReader.Read( rsi_path );
                                                                groups_character[index].Character = cex.character;
                                                                break;
                                                            case "Table":
                                                                source_table = level4.InnerText;
                                                                break;
                                                            case "Triggers":
                                                                foreach( XmlNode level5 in level4.ChildNodes ){
                                                                    if ( level5.LocalName == "TriggerEvent" ) {
                                                                        float time = 0f;
                                                                        string trigger_name = "";
                                                                        string status = "";
                                                                        foreach ( XmlNode level6 in level5.ChildNodes ) {
                                                                            switch ( level6.LocalName ) {
                                                                                case "Time":
                                                                                    time = float.Parse( level6.InnerText );
                                                                                    break;
                                                                                case "TriggerName":
                                                                                    trigger_name = level6.InnerText;
                                                                                    break;
                                                                                case "Status":
                                                                                    status = level6.InnerText;
                                                                                    break;
                                                                            }
                                                                        }
                                                                        events.Add( new RspTriggerEvent( time, trigger_name, status ) );
                                                                    }
                                                                }
                                                                break;
                                                        }
                                                    }
                                                    break;
                                            }
                                        }
                                        // source_tableから、口パクを作成
                                        // group_vsqから、タイトルがsource_tableで或るやつを検索
                                        int source = -1;
                                        for ( int i = 0; i < group_vsq.Count; i++ ) {
                                            if ( group_vsq[i].Text == source_table ) {
                                                source = i;
                                                break;
                                            }
                                        }
                                        if ( source >= 0 ) {
                                            // 口パク用のタイムテーブルが指定されていた場合
                                            TimeTableGroup temp = (TimeTableGroup)groups_character[index].Clone();
                                            TimeTableGroup.GenerateLipSyncFromVsq(
                                                group_vsq[source], 
                                                ref temp, 
                                                groups_character[index].Character, 
                                                optional_length, 
                                                true, 
                                                save.FrameRate, 
                                                0f );  //todo: ここのoptional_lengthが怪しい
                                            groups_character[index] = temp;
                                        } else {
                                            //口パク用タイムテーブルが指定されていなかった場合
                                            // 画像分だけトラックを用意
                                            /*groups_character[index].Add( new TimeTable( "base", 0, TimeTableType.eye, cex.character.Base ) );
                                            groups_character[index].Add( new TimeTable( "a", 0, TimeTableType.mouth, cex.character.a ) );
                                            groups_character[index].Add( new TimeTable( "aa", 0, TimeTableType.mouth, cex.character.aa ) );
                                            groups_character[index].Add( new TimeTable( "i", 0, TimeTableType.mouth, cex.character.i ) );
                                            groups_character[index].Add( new TimeTable( "u", 0, TimeTableType.mouth, cex.character.u ) );
                                            groups_character[index].Add( new TimeTable( "e", 0, TimeTableType.mouth, cex.character.e ) );
                                            groups_character[index].Add( new TimeTable( "o", 0, TimeTableType.mouth, cex.character.o ) );
                                            groups_character[index].Add( new TimeTable( "xo", 0, TimeTableType.mouth, cex.character.xo ) );
                                            groups_character[index].Add( new TimeTable( "nn", 0, TimeTableType.mouth, cex.character.nn ) );*/
                                            for ( int k = 0; k < groups_character[index].Character.Count; k++ ) {
                                                groups_character[index].Add( new TimeTable( groups_character[index].Character[k].title, 0, TimeTableType.character, null ) );
                                            }
                                        }

                                        // triggerから、表情を追加。
                                        // まず、排他も考慮して、OFFのイベントを追加
                                        events.Sort();
                                        //MessageBox.Show( "events.Cocunt=" + events.Count );
                                        #region 排他を考慮して、OFFイベントを追加
                                        int ii = -1;
                                        while( (ii + 1) < events.Count ){
                                            ii++;
                                        //for ( int i = 0; i < events.Count; i++ ) {
                                            string trigger_name = events[ii].triggerName;
                                            //MessageBox.Show( "trigger_name=" + trigger_name );
                                            float begin = events[ii].time;
                                            float end = begin - 10f; // begin > endであれば-10じゃなくてもいい
                                            // 排他に該当して無いかどうか検査
                                            bool exclusion = isExclusionTrigger( cex, trigger_name );
                                            bool defined = false; // 排他制御で、OFF位置が定義できたか否か
                                            if ( exclusion ) {
                                                // 排他制御をする必要がある場合
                                                // 他の排他要素の中で、beginより後にONしていて、かつそれがもっとも早いものを検索
                                                float first_begin = end;
                                                //bool int_exclusion = false;
                                                for ( int j = ii + 1; j < events.Count; j++ ) {
                                                    if ( isExclusionTrigger( cex, events[j].triggerName ) ) {
                                                        defined = true;
                                                        end = events[j].time;
                                                        break;
                                                    }
                                                }
                                            }
                                            //MessageBox.Show( "defined=" + defined );
                                            if ( !defined ) {
                                                // この後で同じトリガが再びON指定になってたり、作為的にOFFにされて無いかどうかを検査
                                                for ( int j = ii + 1; j < events.Count; j++ ) {
                                                    if ( events[j].triggerName == trigger_name ) {
                                                        end = events[j].time;
                                                        defined = true;
                                                        break;
                                                    }
                                                }
                                            }
                                            if ( defined ) {
                                                events.Add( new RspTriggerEvent( end, trigger_name, "OFF" ) );
                                                events.Sort();
                                                ii++; //++しないと∞ループだな
                                            }
                                        }
                                        #endregion

                                        for ( int i = 0; i < events.Count; i++ ) {
                                            float begin, end;
                                            if ( events[i].status == RspTriggerStatus.ON ){
                                                // まず、開始と終了の時刻を設定
                                                begin = events[i].time;
                                                end = -1f;
                                                if ( i + 1 < events.Count ) {
                                                    if ( events[i + 1].triggerName == events[i].triggerName && events[i + 1].status == RspTriggerStatus.OFF ) {
                                                        end = events[i + 1].time;
                                                    }
                                                }

                                                // どのタイムラインに追加するのかを決定
                                                string trigger = events[i].triggerName;
                                                int target = -1;
                                                for ( int j = 0; j < groups_character[index].Count; j++ ) {
                                                    if ( groups_character[index][j].Text == trigger ) {
                                                        target = j;
                                                        break;
                                                    }
                                                }
 
                                                // 追加を実行
                                                if ( target >= 0 ) {
                                                    groups_character[index][target].Add( new TimeTableEntry( begin, end, trigger ) );
                                                }
                                            }
                                        }

                                        #endregion
                                        break;
                                    case "RipSync.Plugins.Extensions.SimpleImage.SimpleImage":
                                        #region RipSync.Plugins.Extensions.SimpleImage.SimpleImage
                                        string image_file = "";
                                        string name = "";
                                        foreach ( XmlNode level3 in level2.ChildNodes ) {
                                            switch ( level3.LocalName ) {
                                                case "Parameter":
                                                    foreach ( XmlNode level4 in level3.ChildNodes ) {
                                                        if ( level4.LocalName == "FileName" ) {
                                                            image_file = level3.InnerText;
                                                            break;
                                                        }
                                                    }
                                                    break;
                                                case "Name":
                                                    name = level3.InnerText;
                                                    break;
                                            }
                                        }

                                        image_file = HttpUtility.UrlDecode( image_file );
                                        string file = Path.Combine( rsp_path, image_file );
                                        if ( File.Exists( file ) ) {
                                            Bitmap img = new Bitmap( Common.ImageFromFile( file ) );
                                            group_another.Add( new TimeTable( name, 0, TimeTableType.another, img ) );
                                        } else {
                                            group_another.Add( new TimeTable( name, 0, TimeTableType.another, null ) );
                                        }

                                        #endregion
                                        break;
                                    case "RipSync.Plugins.Extensions.TextObject.TextObject":
                                        #region RipSync.Plugins.Extensions.TextObject.TextObject
                                        string tname = "";
                                        string text = "";
                                        Color color = Color.Black;
                                        Font font = new Font( "MS UI Gothc", 10 );
                                        foreach ( XmlNode level3 in level2.ChildNodes ) {
                                            switch ( level3.LocalName ) {
                                                case "Name":
                                                    tname = level3.InnerText;
                                                    break;
                                                case "Parameter":
                                                    foreach ( XmlNode level4 in level3.ChildNodes ) {
                                                        switch ( level4.LocalName ) {
                                                            case "Text":
                                                                text = level4.InnerText;
                                                                break;
                                                            case "Color":
                                                                int col = int.Parse( level4.InnerText );
                                                                color = Color.FromArgb( col );
                                                                break;
                                                            case "Font":
                                                                string font_name = level4.InnerText;
                                                                //Microsoft Sans Serif, 12pt, style=Bold
                                                                string[] spl = font_name.Split( new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries );
                                                                string font_family = spl[0];
                                                                string font_size = spl[1];
                                                                font_size = font_size.Replace( "pt", "" );
                                                                int em_size = int.Parse( font_size );
                                                                string font_style = "";
                                                                //style=Bold, Italic, Underline, Strikeout
                                                                FontStyle t_font_style = FontStyle.Regular;
                                                                if ( spl.Length >= 3 ) {
                                                                    //spl[2]のみ、Style=がついてるのでそれをはずす
                                                                    string[] spl2 = spl[2].Split( new string[] { "=" }, StringSplitOptions.RemoveEmptyEntries );
                                                                    font_style = spl2[1];
                                                                }
                                                                for ( int ic = 3; ic < spl.Length; ic++ ) {
                                                                    font_style += "," + spl[ic];
                                                                }
                                                                spl = font_style.Split( new string[] { "," }, StringSplitOptions.RemoveEmptyEntries );
                                                                foreach ( string style_name in spl ) {
                                                                    switch ( style_name ) {
                                                                        case "Bold":
                                                                            t_font_style = t_font_style | FontStyle.Bold;
                                                                            break;
                                                                        case "Italic":
                                                                            t_font_style = t_font_style | FontStyle.Italic;
                                                                            break;
                                                                        case "Underline":
                                                                            t_font_style = t_font_style | FontStyle.Underline;
                                                                            break;
                                                                        case "Strikeout":
                                                                            t_font_style = t_font_style | FontStyle.Strikeout;
                                                                            break;
                                                                    }
                                                                }
                                                                font = new Font( font_family, em_size, t_font_style );
                                                                break;
                                                        }
                                                    }
                                                    break;
                                            }
                                        }
                                        Telop temp_telop = new Telop( save.GetNextID() );
                                        temp_telop.Text = text;
                                        temp_telop.Color = color;
                                        temp_telop.Font = font;
                                        temp_telop.Tag = tname;
                                        telop.Add( temp_telop );
                                        #endregion
                                        break;
                                }
                            }
                        }
                        #endregion
                        break;
                    case "TimeLine":
                        #region TimeLine
                        foreach ( XmlNode level2 in level1.ChildNodes ) {
                            if ( level2.LocalName == "Unit" ) {
                                int zindex = 0;
                                float start = 0f;
                                float end = -10f;
                                int position_x = 0;
                                int position_y = 0;
                                float scale = 1f;
                                string draw_object = "";
                                bool fadein = false;
                                bool fadeout = false;
                                foreach ( XmlNode level3 in level2.ChildNodes ) {
                                    switch ( level3.LocalName ) {
                                        case "ZIndex":
                                            zindex = int.Parse( level3.InnerText );
                                            break;
                                        case "Start":
                                            start = float.Parse( level3.InnerText );
                                            break;
                                        case "End":
                                            end = float.Parse( level3.InnerText );
                                            break;
                                        case "PositionX":
                                            position_x = int.Parse( level3.InnerText );
                                            break;
                                        case "PositionY":
                                            position_y = int.Parse( level3.InnerText );
                                            break;
                                        case "Scale":
                                            scale = float.Parse( level3.InnerText );
                                            break;
                                        case "DrawObject":
                                            draw_object = level3.InnerText;
                                            break;
                                        case "FadeIn":
                                            if( level3.InnerText == "True" ){
                                                fadein = true;
                                            }
                                            break;
                                        case "FadeOut":
                                            if( level3.InnerText == "True" ){
                                                fadeout = true;
                                            }
                                            break;
                                    }
                                }

                                // 名前がdraw_objectであるトラック（orキャラクタ）を検索
                                // group_another
                                for ( int i = 0; i < group_another.Count; i++ ) {
                                    if ( draw_object == group_another[i].Text ) {
                                        group_another[i].ZOrder = zindex;
                                        group_another[i].Clear();
                                        group_another[i].Add( new TimeTableEntry( start, end, draw_object ) );
                                        group_another[i].Position = new Point( position_x, position_y );
                                        group_another[i].Scale = scale;
                                    }
                                }

                                // group_character
                                for ( int i = 0; i < groups_character.Count; i++ ) {
                                    if ( draw_object == groups_character[i].Text ) {
                                        groups_character[i].ZOrder = zindex;
                                        groups_character[i].Position = new Point( position_x, position_y );
                                    }
                                }

                                // telop
                                for ( int i = 0; i < telop.Count; i++ ) {
                                    if ( draw_object == (string)telop[i].Tag ) {
                                        telop[i].Start = start;
                                        telop[i].End = end;
                                        telop[i].FadeIn = fadein;
                                        telop[i].FadeOut = fadeout;
                                        telop[i].Position = new Point( position_x, position_y );
                                        if ( scale != 1 ) {
                                            Font tnew = new Font( telop[i].Font.FontFamily, telop[i].Font.SizeInPoints * scale, telop[i].Font.Style );
                                            telop[i].Font.Dispose();
                                            telop[i].Font = null;
                                            telop[i].Font = tnew;
                                        }
                                    }
                                }
                            }
                        }
                        #endregion
                        break;
                }
            }
            //MessageBox.Show( "tables=" + group_vsq.Count );
            if ( optional_length < 0 ) {
                float thismax = -1;
                for ( int track = 0; track < group_vsq.Count; track++ ) {
                    int last = group_vsq[track].Count - 1;
                    if ( last >= 0 ) {
                        thismax = group_vsq[track][last].end;
                        optional_length = Math.Max( optional_length, thismax );
                    }
                }

                for ( int group = 0; group < groups_character.Count; group++ ) {
                    for ( int track = 0; track < groups_character[group].Count; track++ ) {
                        int last = groups_character[group][track].Count - 1;
                        if ( last >= 0 ) {
                            thismax = groups_character[group][track][last].end;
                            optional_length = Math.Max( optional_length, thismax );
                        }
                    }
                }
            }

            // 時間が負になってる表情用エントリを整理
            for ( int group = 0; group < groups_character.Count; group++ ) {
                for ( int track = 0; track < groups_character[group].Count; track++ ) {
                    for( int entry = 0; entry < groups_character[group][track].Count; entry++ ){
                        if ( groups_character[group][track][entry].end < 0 ) {
                            groups_character[group][track][entry].end = optional_length;
                        }
                    }
                }
            }
            // 時間が負になっているその他のイメージのエントリを整理
            for ( int track = 0; track < group_another.Count; track++ ) {
                for ( int entry = 0; entry < group_another[track].Count; entry++ ) {
                    if ( group_another[track][entry].end < 0f ) {
                        group_another[track][entry].end = optional_length;
                    }
                }
            }
            // 時間が負になっているテロップのエントリを整理
            for ( int i = 0; i < telop.Count; i++ ) {
                if ( telop[i].End < 0f ) {
                    telop[i].End = optional_length;
                }
            }

            // base画像エントリの修正。genMouthFromVsqCore実行時にはoptional_length == 0なので、修正。
            for ( int group = 0; group < groups_character.Count; group++ ) {
                int index_base = -1;
                for ( int track = 0; track < groups_character[group].Count; track++ ) {
                    if ( groups_character[group][track].Text == "base" ) {
                        index_base = track;
                        break;
                    }
                }
                if ( index_base >= 0 ) {
                    groups_character[group][index_base].Clear();
                    groups_character[group][index_base].Add( new TimeTableEntry( 0.0f, optional_length, "base" ) );
                }
            }

            // zorderの数値がrip syncでは逆なので修正
            // まずzorderの最大値を探す
            int zmax = -100;
            for ( int i = 0; i < group_another.Count; i++ ) {
                zmax = Math.Max( zmax, group_another[i].ZOrder );
            }
            for ( int i = 0; i < groups_character.Count; i++ ) {
                zmax = Math.Max( zmax, groups_character[i].ZOrder );
            }
            // 修正
            for ( int i = 0; i < group_another.Count; i++ ) {
                group_another[i].ZOrder = zmax - group_another[i].ZOrder;
            }
            for ( int i = 0; i < groups_character.Count; i++ ) {
                groups_character[i].ZOrder = zmax - groups_character[i].ZOrder;
            }

            if ( optional_length > 0 ) {
                save.m_totalSec = optional_length;
            } else {
                save.m_totalSec = 0;
            }
            save.m_group_vsq = (TimeTableGroup)group_vsq.Clone();
            save.m_groups_character.Clear();
            for ( int i = 0; i < groups_character.Count; i++ ) {
                save.m_groups_character.Add( groups_character[i] );
            }
            save.m_group_another = (TimeTableGroup)group_another.Clone();
            save.m_movieSize = new Size( width, height );
            save.m_telop_ex2.Clear();
            save.m_telop_ex2 = new List<Telop>();
            for ( int i = 0; i < telop.Count; i++ ) {
                save.m_telop_ex2.Add( telop[i] );
            }
            save.UpdateZorder();
            save.m_audioFile = sound_file;
            //s.InvertZOrder();
            return true;
        }


        /// <summary>
        /// 指定された名前のトリガーが、排他指定されているか否かを判定します
        /// </summary>
        /// <param name="cex">判定に使用されるCharacterEx</param>
        /// <param name="name">検査するトリガーの名前</param>
        /// <returns></returns>
        private static bool isExclusionTrigger( CharacterEx cex, string name ) {
            for ( int i = 0; i < cex.exclusion.Count; i++ ) {
                if ( cex.exclusion[i] == name ) {
                    return true;
                }
            }
            return false;
        }

    }



}
