/*
 * Character3.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.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;

using LipSync.Properties;

namespace LipSync {

    /// <summary>
    /// キャラクタを取り扱うクラス。
    /// 第3世代
    /// </summary>
    [Serializable]
    public class Character3 : ICloneable, IDisposable {
        string m_name;
        CharacterType m_type;
        ImageEntry[] m_basic = new ImageEntry[9];
        List<ImageEntry> m_another;
        Size m_size;
        [NonSerialized]
        Bitmap m_cache = null;
        [NonSerialized]
        int[] m_cache_draw;
        string m_author;
        string m_version;
        PluginConfig m_plugin_config;
        bool m_updated = false;         // 第2世代のCharacterからアップデートされたものであることを表すフラグ
        bool m_is_build_in = false;
        /// <summary>
        /// 使用上の注意など
        /// </summary>
        [OptionalField]
        string m_lisence;

        #region public static field
        /// <summary>
        /// ビルトイン・キャラクタ。
        /// </summary>
        public static readonly Character3 Miku = new Character3(
            "Miku",
            "さなり",
            "",
            new ImageEntry[] { 
                new ImageEntry( "base", Resources.b_miku175_base, "base", true ),
                new ImageEntry( "a", Resources.b_miku175_a, "mouth", false ),
                new ImageEntry( "aa", Resources.b_miku175_aa, "mouth", false ),
                new ImageEntry( "i", Resources.b_miku175_i, "mouth", false ),
                new ImageEntry( "u", Resources.b_miku175_u, "mouth", false ),
                new ImageEntry( "e", Resources.b_miku175_e, "mouth", false ),
                new ImageEntry( "o", Resources.b_miku175_o, "mouth", false ),
                new ImageEntry( "xo", Resources.b_miku175_xo, "mouth", false ),
                new ImageEntry( "nn", Resources.b_miku175_nn, "mouth", false )
            },
            new ImageEntry[] {
                new ImageEntry( "目閉じ", Resources.b_miku175_eyeclose, "eye", false ),
                new ImageEntry( "低目にっこり", Resources.b_miku175_smile, "eye", false ),
                new ImageEntry( "目半目", Resources.b_miku175_eyethin, "eye", false ),
                new ImageEntry( "こなた", Resources.b_miku175_konata, "eye", false ),
                new ImageEntry( "＞＜", Resources.b_miku175_kudo, "eye", false ),
                new ImageEntry( "哀左ウィンク", Resources.b_miku175_winkleft, "eye", false ),
                new ImageEntry( "右ウィンク", Resources.b_miku175_winkright, "eye", false ),
                new ImageEntry( "bee", Resources.b_miku175_bee, "mouth", false),
                new ImageEntry( "neko", Resources.b_miku175_neko, "mouth", false )
            },
            true
        );

        public static readonly Character3 Rin = new Character3(
            "Rin",
            "さなり",
            "",
            new ImageEntry[] {
                new ImageEntry( "base", Resources.b_rin100_base, "base",true ),
                new ImageEntry( "a", Resources.b_rin100_a, "mouth", false ),
                new ImageEntry( "aa", Resources.b_rin100_aa, "mouth", false ),
                new ImageEntry( "i", Resources.b_rin100_i, "mouth", false ),
                new ImageEntry( "u", Resources.b_rin100_u, "mouth", false ),
                new ImageEntry( "e", Resources.b_rin100_e, "mouth", false ),
                new ImageEntry( "o", Resources.b_rin100_o, "mouth", false ),
                new ImageEntry( "xo", Resources.b_rin100_xo, "mouth", false ),
                new ImageEntry( "nn", Resources.b_rin100_nn, "mouth", false )
            },
            new ImageEntry[] {
                new ImageEntry( "目閉じ", Resources.b_rin100_eyeclose, "eye", false ),
                new ImageEntry( "低目にっこり", Resources.b_rin100_smile, "eye", false ),
                new ImageEntry( "目半目", Resources.b_rin100_eyethin, "eye", false ),
                new ImageEntry( "＞＜", Resources.b_rin100_kudo, "eye", false ),
                new ImageEntry( "哀左ウィンク", Resources.b_rin100_winkleft, "eye", false ),
                new ImageEntry( "低右ウィンク", Resources.b_rin100_winkright, "eye" , false),
                new ImageEntry( "bee", Resources.b_rin100_bee, "mouth", false ),
                new ImageEntry( "neko", Resources.b_rin100_neko, "mouth", false ),
                new ImageEntry( "きしし", Resources.b_rin100_kisisi, "mouth", false )
            },
            true
        );

        public static readonly Character3 Len = new Character3(
            "Len",
            "さなり",
            "",
            new ImageEntry[] {
                new ImageEntry( "base", Resources.b_len100_base, "本体", true ),
                new ImageEntry( "a", Resources.b_len100_a, "mouth", false ),
                new ImageEntry( "aa", Resources.b_len100_aa, "mouth", false ),
                new ImageEntry( "i", Resources.b_len100_i, "mouth", false ),
                new ImageEntry( "u", Resources.b_len100_u, "mouth", false ),
                new ImageEntry( "e", Resources.b_len100_e, "mouth", false ),
                new ImageEntry( "o", Resources.b_len100_o, "mouth", false ),
                new ImageEntry( "xo", Resources.b_len100_xo, "mouth", false ),
                new ImageEntry( "nn", Resources.b_len100_nn, "mouth", false )
            },
            new ImageEntry[] {
                new ImageEntry( "目閉じ", Resources.b_len100_eyeclose, "eye", false ),
                new ImageEntry( "低目にっこり", Resources.b_len100_smile, "eye", false ),
                new ImageEntry( "目半目", Resources.b_len100_eyethin, "eye", false ),
                new ImageEntry( "（｀・ω・´）", Resources.b_len100_shakin, "eye", false ),
                new ImageEntry( "哀左ウィンク", Resources.b_len100_winkleft, "eye", false ),
                new ImageEntry( "中右ウィンク", Resources.b_len100_winkright, "eye", false ),
                new ImageEntry( "きしし", Resources.b_len100_kisisi, "mouth", false )
            },
            true
        );
        #endregion

        [OnDeserialized]
        private void onDeserialized( StreamingContext sc ) {
            SortedList<int, string> slist = new SortedList<int, string>();
            foreach ( ImageEntry ie in m_basic ) {
                slist.Add( ie.Z, ie.title );
            }
            foreach ( ImageEntry ie in m_another ) {
                slist.Add( ie.Z, ie.title );
            }
            for ( int i = 0; i < slist.Keys.Count; i++ ) {
                string title = slist[slist.Keys[i]];
                bool found = false;
                for ( int j = 0; j < m_basic.Length; j++ ) {
                    if ( m_basic[j].title == title ) {
                        m_basic[j].Z = i;
                        found = true;
                        break;
                    }
                }
                if ( !found ) {
                    for ( int j = 0; j < m_another.Count; j++ ) {
                        if ( m_another[j].title == title ) {
                            m_another[j].Z = i;
                            break;
                        }
                    }
                }
            }
        }

        public bool IsBuildIn {
            get {
                return m_is_build_in;
            }
        }

        public void Remove( string title ) {
            for ( int i = 0; i < m_another.Count; i++ ) {
                if ( m_another[i].title == title ) {
                    m_another.RemoveAt( i );
                    break;
                }
            }
        }

        public void SetImage( Image img, int index ) {
            this[index].SetImage( img );
        }

        public void SetImage( Image img, string title ) {
            this[title].SetImage( img );
        }

        public PluginConfig PluginConfig {
            get {
                return m_plugin_config;
            }
            set {
                m_plugin_config = value;
            }
        }

        public int Count {
            get {
                return 9 + m_another.Count;
            }
        }

        public void Dispose() {
            m_basic = null;
            m_another.Clear();
            if ( m_cache != null ) {
                m_cache.Dispose();
            }
        }

        public void Add( ImageEntry item ) {
            ImageEntry adding = (ImageEntry)item.Clone();
            adding.Z = this.Count;
            m_another.Add( adding );
        }

        public string Version {
            get {
                return m_version;
            }
            set {
                m_version = value;
            }
        }

        public string Author {
            get {
                return m_author;
            }
            set {
                m_author = value;
            }
        }

        public Character3() {
            m_name = "";
            m_type = CharacterType.def;
            m_basic = new ImageEntry[9];
            m_basic[0] = new ImageEntry( "base", null, "base", true );
            m_basic[1] = new ImageEntry( "a", null, "mouth", false );
            m_basic[2] = new ImageEntry( "aa", null, "mouth", false );
            m_basic[3] = new ImageEntry( "i", null, "mouth", false );
            m_basic[4] = new ImageEntry( "u", null, "mouth", false );
            m_basic[5] = new ImageEntry( "e", null, "mouth", false );
            m_basic[6] = new ImageEntry( "o", null, "mouth", false );
            m_basic[7] = new ImageEntry( "xo", null, "mouth", false );
            m_basic[8] = new ImageEntry( "nn", null, "mouth", false );
            for ( int i = 0; i < 9; i++ ) {
                m_basic[i].Z = i;
            }
            m_another = new List<ImageEntry>();
            m_size = new Size();
            m_author = "";
            m_version = "";
        }

        public void Write( Stream s ) {
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize( s, this );
        }

        public string GetMD5() {
            System.Security.Cryptography.MD5CryptoServiceProvider mcsp = new System.Security.Cryptography.MD5CryptoServiceProvider();
            using ( MemoryStream ms = new MemoryStream() ) {
                this.Write( ms );
                byte[] dat = mcsp.ComputeHash( ms );
                string res = "";
                foreach ( byte b in dat ) {
                    res += b.ToString( "x2" );
                }
                return res;
            }
            return "";
        }

        /// <summary>
        /// ファイルに保存する
        /// </summary>
        /// <param name="path"></param>
        /// <param name="pack"></param>
        public void WriteXml( string path ) {
            string f = Path.GetFileName( path );
            if ( f != "content.xml" ) {
                return;
            }
            int width = this.Width;
            int height = this.Height;
            string base_path = Path.GetDirectoryName( path );
            string image_path = Path.Combine( base_path, "images" );
            if ( !Directory.Exists( image_path ) ) {
                Directory.CreateDirectory( Path.Combine( base_path, "images" ) );
            }
            using ( FileStream fs = new FileStream( Path.Combine( base_path, "content.xml" ), FileMode.Create ) ) {
                XmlSerializer xs = new XmlSerializer( typeof( Character3 ) );
                xs.Serialize( fs, this );
            }
            using ( FileStream fs = new FileStream( Path.Combine( base_path, "basic.xml" ), FileMode.Create ) ) {
                XmlSerializer xs = new XmlSerializer( typeof( ImageEntry[] ) );
                xs.Serialize( fs, m_basic );
            }
            using ( FileStream fs = new FileStream( Path.Combine( base_path, "another.xml" ), FileMode.Create ) ) {
                XmlSerializer xs = new XmlSerializer( typeof( List<ImageEntry> ) );
                xs.Serialize( fs, m_another );
            }
            int count = -1;
            foreach ( ImageEntry img in this ) {
                count++;
                if ( img.Image != null ) {
                    string file = Path.Combine( Path.Combine( base_path, "images" ), img.Z + ".png" );
                    Bitmap temp = img.GetImage( width, height );
                    temp.Save( file );
                }
            }
        }

        public Character3( PluginConfig plugin_config ) {
            m_name = plugin_config.ID;
            m_type = CharacterType.plugin;
            m_plugin_config = plugin_config.Clone();
            m_updated = false;
        }

        public static Character3 Read( Stream s ) {
            BinaryFormatter bf = new BinaryFormatter();
            Character3 res = null;
            try {
                res = (Character3)bf.Deserialize( s );
                return res;
            } catch {
                return null;
            }
        }

        /// <summary>
        /// xmlファイルからのコンストラクタ
        /// </summary>
        public static Character3 FromXml( string path ) {
#if DEBUG
                Common.DebugWriteLine( "Character3.ctor(string);" );
#endif
            Character3 res;
            string dir = Path.GetDirectoryName( path );
            using ( FileStream fs = new FileStream( path, FileMode.Open ) ) {
                XmlSerializer xs = new XmlSerializer( typeof( Character3 ) );
                res = (Character3)xs.Deserialize( fs );
            }

            using ( FileStream fs = new FileStream( Path.Combine( dir, "basic.xml" ), FileMode.Open ) ) {
                XmlSerializer xs = new XmlSerializer( typeof( ImageEntry[] ) );
                res.m_basic = (ImageEntry[])xs.Deserialize( fs );
            }
            using ( FileStream fs = new FileStream( Path.Combine( dir, "another.xml" ), FileMode.Open ) ) {
                XmlSerializer xs = new XmlSerializer( typeof( List<ImageEntry> ) );
                res.m_another = (List<ImageEntry>)xs.Deserialize( fs );
            }
            //res.ZReorder();
            for ( int i = 0; i < res.Count; i++ ) {
                int z = res[i].Z;
                string file = Path.Combine( Path.Combine( dir, "images" ), z + ".png" );
#if DEBUG
                    Common.DebugWriteLine( "Character3.ctor(String); file=" + file );
#endif
                if ( File.Exists( file ) ) {
                    res[i].SetImage( Common.ImageFromFile( file ) );
                }
            }
#if DEBUG
            Common.DebugWriteLine( "Character3.FromXml()" );
            for ( int i = 0; i < res.Count; i++ ) {
                Common.DebugWriteLine( "i=" + i + "; title=" + res[i].title + "; (image==null)=" + (res[i].Image == null) );
            }
            Common.DebugWriteLine( "m_size=" + res.m_size );
#endif
            return res;
        }

        public static Character3 FromFile( string path ) {
            Character3 res;
            using ( FileStream fs = new FileStream( path, FileMode.Open ) ) {
                BinaryFormatter bf = new BinaryFormatter();
                res = (Character3)bf.Deserialize( fs );
            }
            return res;
        }

        /// <summary>
        /// 第2世代目のCharacterからのコンバート
        /// </summary>
        /// <param name="character"></param>
        /// <returns></returns>
        public Character3( Character character ) {
            List<ImageEntry> basic = new List<ImageEntry>();
            List<ImageEntry> another = new List<ImageEntry>();
            string[] titles = new string[] { "base", "a", "aa", "i", "u", "e", "o", "xo", "nn" };

            // zオーダーを更新しておく
            for ( int i = 0; i < character.Images.Count; i++ ) {
                character.Images[i].Z = i;
            }
#if DEBUG
            string t1 = "";
            for ( int i = 0; i < character.Images.Count; i++ ) {
                t1 += character.Images[i].ToString() + "\n";
            }
            System.Windows.Forms.MessageBox.Show( t1 );
#endif
            foreach ( string title in titles ) {
                bool found = false;
                foreach ( ImageEntry img in character.Images ) {
                    if ( img.title == title ) {
                        ImageEntry cp = new ImageEntry( img.title, null, img.tag, img.IsDefault, img.Z );
                        cp.SetImage( img.GetImage() );
                        basic.Add( cp );
                        found = true;
                        break;
                    }
                }
                if ( !found ) {
                    if ( title == "base" ) {
                        basic.Add( new ImageEntry( title, null, "base", true ) );
                    } else {
                        basic.Add( new ImageEntry( title, null, "mouth", false ) );
                    }
                }
            }

            // another
            foreach ( ImageEntry img in character.Images ) {
                bool is_basic = false;
                foreach ( string title in titles ) {
                    if ( img.title == title ) {
                        is_basic = true;
                        break;
                    }
                }
                if ( !is_basic ) {
                    ImageEntry cp = new ImageEntry( img.title, null, img.tag, img.IsDefault, img.Z );
                    cp.SetImage( img.GetImage() );
                    another.Add( cp );
                }
            }

            m_name = character.Name;
            m_basic = basic.ToArray();
            m_another = new List<ImageEntry>( another.ToArray() );
            m_size = character.m_size;
            m_type = CharacterType.def;
            m_author = "";
            m_version = "";
            m_updated = true;
            //ZReorder();
#if DEBUG
            string t = "";
            for ( int i = 0; i < m_basic.Length; i++ ) {
                t += m_basic[i].ToString() + "\n";
            }
            for ( int i = 0; i < m_another.Count; i++ ) {
                t += m_another[i].ToString() + "\n";
            }
            System.Windows.Forms.MessageBox.Show( t );
#endif
        }

        [XmlIgnore]
        public Bitmap DefaultFace {
            get {
                List<int> type = new List<int>();
                int count = -1;
                foreach ( ImageEntry img in this ) {
                    count++;
                    if ( img.IsDefault ) {
                        type.Add( count );
                    }
                }
                return Face( type.ToArray() );
            }
        }

        public IEnumerator<ImageEntry> GetEnumerator() {
            for ( int i = 0; i < m_basic.Length; i++ ) {
                yield return m_basic[i];
            }
            for ( int i = 0; i < m_another.Count; i++ ) {
                yield return m_another[i];
            }
        }

        public Bitmap Face( int[] targets ) {
            if ( Width <= 0 || Height <= 0 ) {
                return null;
            }

            // zオーダー順に描画する画像を並べ替える
            int[] zorder = new int[targets.Length];
            for ( int i = 0; i < targets.Length; i++ ) {
                zorder[i] = this[targets[i]].Z;
            }
            bool c = true;
            while ( c ) {
                c = false;
                for ( int i = 0; i < targets.Length - 1; i++ ) {
                    if ( zorder[i] > zorder[i + 1] ) {
                        int b = targets[i];
                        targets[i] = targets[i + 1];
                        targets[i + 1] = b;
                        b = zorder[i];
                        zorder[i] = zorder[i + 1];
                        zorder[i + 1] = b;
                        c = true;
                    }
                }
                if ( !c ) {
                    break;
                }
            }

            // 前回描画したのと同じ描画要求であれば、キャッシュをそのまま返す
            if ( m_cache != null && m_cache_draw != null ) {
                if ( m_cache_draw.Length == targets.Length ) {
                    bool match = true;
                    for ( int i = 0; i < targets.Length; i++ ) {
                        if ( m_cache_draw[i] != targets[i] ) {
                            match = false;
                            break;
                        }
                    }
                    if ( match ) {
                        return (Bitmap)m_cache.Clone();
                    }
                }
            }
            m_cache_draw = targets;

            Bitmap bmp = new Bitmap( Width, Height );
            using ( Graphics g = Graphics.FromImage( bmp ) ) {
                for ( int i = 0; i < targets.Length; i++ ) {
                    ImageEntry img = this[targets[i]];
                    if ( img != null ) {
                        img.DrawTo( g );
                    }
                }
            }

            if ( m_cache != null ) {
                m_cache = null;
            }
            m_cache = (Bitmap)bmp.Clone();
            return bmp;
        }

        public ImageEntry this[string title] {
            get {
                for ( int i = 0; i < m_basic.Length; i++ ) {
                    if ( m_basic[i].title == title ) {
                        return m_basic[i];
                    }
                }
                for ( int i = 0; i < m_another.Count; i++ ) {
                    if ( m_another[i].title == title ) {
                        return m_another[i];
                    }
                }
                return null;
            }
            /*set {
                for ( int i = 0; i < m_basic.Length; i++ ) {
                    if ( m_basic[i].title == title ) {
                        m_basic[i] = value;
                    }
                }
                for ( int i = 0; i < m_another.Count; i++ ) {
                    if ( m_another[i].title == title ) {
                        m_another[i] = value;
                    }
                }
            }*/
        }

        public ImageEntry this[int zorder] {
            get {
                for ( int i = 0; i < m_basic.Length; i++ ) {
                    if ( m_basic[i].Z == zorder ) {
                        return m_basic[i];
                    }
                }
                for ( int i = 0; i < m_another.Count; i++ ) {
                    if ( m_another[i].Z == zorder ) {
                        return m_another[i];
                    }
                }
                return null;
            }
            set {
                for ( int i = 0; i < m_basic.Length; i++ ) {
                    if ( m_basic[i].Z == zorder ) {
                        m_basic[i] = value;
                        m_basic[i].Z = zorder;
                        return;
                    }
                }
                for ( int i = 0; i < m_another.Count; i++ ) {
                    if ( m_another[i].Z == zorder ) {
                        m_another[i] = value;
                        m_another[i].Z = zorder;
                        return;
                    }
                }
            }
        }

        public Size Size {
            get {
                if ( m_type == CharacterType.def ) {
                    return m_size;
                } else {
                    throw new NotImplementedException();
                }
            }
            set {
                m_size = value;
            }
        }

        [XmlIgnore]
        public int Width {
            get {
                return m_size.Width;
            }
        }

        [XmlIgnore]
        public int Height {
            get {
                return m_size.Height;
            }
        }

        public object Clone() {
            Character3 res = new Character3();
            res.m_name = m_name;
            res.m_author = m_author;
            res.m_version = m_version;
            res.m_type = m_type;
            if ( m_plugin_config != null ) {
                res.m_plugin_config = m_plugin_config.Clone();
            }
            for ( int i = 0; i < 9; i++ ) {
                res.m_basic[i] = (ImageEntry)m_basic[i].Clone();
            }
            res.m_another.Clear();
            for ( int i = 0; i < m_another.Count; i++ ) {
                res.m_another.Add( (ImageEntry)m_another[i].Clone() );
            }
            res.m_updated = m_updated;
            res.m_size = m_size;
            return res;
        }

        private Character3( string name, string author, string version, ImageEntry[] basic, ImageEntry[] another, bool is_build_in )
            : this( name, author, version, basic, another ) {
            m_is_build_in = is_build_in;
        }

        public Character3( string name, string author, string version, ImageEntry[] basic, ImageEntry[] another ) {
            m_type = CharacterType.def;
            m_name = name;
            m_author = author;
            m_version = version;
            if ( basic.Length < 9 ) {
                throw new ArgumentException( "basic.Length < 9" );
            }
            int z = -1;
            for ( int i = 0; i < 9; i++ ) {
                z++;
                m_basic[i] = (ImageEntry)basic[i].Clone();
                m_basic[i].Z = z;
            }
            if ( another != null ) {
                m_another = new List<ImageEntry>( another );
            } else {
                m_another = new List<ImageEntry>();
            }
            for ( int i = 0; i < m_another.Count; i++ ) {
                z++;
                m_another[i].Z = z;
            }
            int width = 0;
            int height = 0;
            if ( basic != null ) {
                foreach ( ImageEntry img in basic ) {
                    if ( img.Image != null ) {
                        width = Math.Max( width, img.Image.Width );
                        height = Math.Max( height, img.Image.Height );
                    }
                }
            }
            if ( another != null ) {
                foreach ( ImageEntry img in another ) {
                    if ( img.Image != null ) {
                        width = Math.Max( width, img.Image.Width );
                        height = Math.Max( height, img.Image.Height );
                    }
                }
            }
            //ZReorder();
            m_size = new Size( width, height );
            m_updated = false;
        }

        /// <summary>
        /// キャラクタのタイプを取得します
        /// </summary>
        public CharacterType Type {
            get {
                return m_type;
            }
        }

        /// <summary>
        /// キャラクタの名称を取得します
        /// </summary>
        public string Name {
            get {
                return m_name;
            }
            set {
                m_name = value;
            }
        }
    }

}
