using System;
using System.Collections.Generic;
using System.Text;
using Plugin;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace NicoComment {

    public class NicoComment : IPlugin{
        private List<string> config = new List<string>();
        private List<NComment> comments = new List<NComment>();

        public void ApplyLanguage( string language_code ) {

        }


        public string Name {
            get {
                return "Nico Comment";
            }
        }

        public ulong Type {
            get {
                return 0;
            }
        }

        public string Abstract {
            get {
                return "ニコニコ動画ぽい字幕を、ニコニコ動画と同様なコマンドで追加できるプラグイン。";
            }
        }

        public void Apply( ref Bitmap frame, float time, float e_begin, float e_end, ref string e_body ) {
            using ( Graphics g = Graphics.FromImage( frame ) ) {
                Rectangle rc = new Rectangle( 0, 0, frame.Width, frame.Height );
                int skip_count_def = 0;
                bool first_def = true;
                bool first_shita = true;
                bool first_ue = true;
                int y_def = 0;
                int y_shita = rc.Height;
                int y_ue = 0;
                for ( int i = 0; i < comments.Count; i++ ) {
                    if ( comments[i].time <= time && time < comments[i].time + 3.0 ) {
                        double t = time - comments[i].time;
                        int height = (int)comments[i].type.Size.Size;
                        if ( comments[i].type.Position == NComTypePosition.def ) {
                            if ( !first_def ) {
                                y_def += height;
                                if ( y_def > rc.Height ) {
                                    y_def -= rc.Height;
                                    skip_count_def++;
                                }
                                t += 0.1 * skip_count_def;
                            } else {
                                first_def = false;
                            }
                            if ( comments[i].y == NComment.NULL ) {
                                comments[i].y = y_def;
                            }

                        } else if ( comments[i].type.Position == NComTypePosition.shita ) {
                            if ( !first_shita ) {
                                y_shita -= (int)(1.1f * height);
                                if ( y_shita + height < 0 ) {
                                    y_shita += rc.Height;
                                }
                            } else {
                                y_shita -= (int)(1.1f * height);
                                first_shita = false;
                            }
                            if ( comments[i].y == NComment.NULL ) {
                                comments[i].y = y_shita;
                            }

                        } else if ( comments[i].type.Position == NComTypePosition.ue ) {
                            if ( !first_ue ) {
                                y_ue += (int)(1.1f * height);
                                if ( y_ue > rc.Height ) {
                                    y_ue += rc.Height;
                                }
                            } else {
                                first_ue = false;
                            }
                            if ( comments[i].y == NComment.NULL ) {
                                comments[i].y = y_ue;
                            }

                        }
                        NComment entry = comments[i];
                        drawString( g, entry, rc, t, comments.Count );
                        comments[i] = entry;
                    }
                }
            }
        }

        public string Config {
            get {
                string result = "";
                if( config.Count > 0 ){
                    result = config[0];
                }
                for ( int i = 1; i < config.Count; i++ ) {
                    result += "\n" + config[i];
                }
                return result;
            }
            set {
                List<string> work_config = new List<string>();
                foreach ( string item in config ) {
                    work_config.Add( item );
                }
                config.Clear();
                List<NComment> work_comments = new List<NComment>();
                foreach ( NComment item in comments ) {
                    work_comments.Add( item );
                }
                comments.Clear();
                //timing.Clear();
                string[] result = value.Split( new char[] { '\n' } );
                config.Add( result[0] );
                for( int i = 1; i < result.Length; i++ ){
                    config.Add( result[i] );
                    string[] spl = result[i].Split( new char[] { '\t' } );
                    if ( spl.Length < 3 ) {
                        config.Clear();
                        foreach ( string item in work_config ) {
                            config.Add( item );
                        }
                        work_config.Clear();
                        comments.Clear();
                        foreach ( NComment item in work_comments ) {
                            comments.Add( item );
                        }
                        work_comments.Clear();
                        MessageBox.Show( "コメントにエラーがありました。エラーのあった行は次の通りです\n" + i + "行目\n" + result[i] );
                        return;
                    }
                    //timing.Add( double.Parse( spl[0] ) );
                    float time;
                    try {
                        time = float.Parse( spl[0] );
                    } catch {
                        config.Clear();
                        foreach ( string item in work_config ) {
                            config.Add( item );
                        }
                        work_config.Clear();
                        comments.Clear();
                        foreach ( NComment item in work_comments ) {
                            comments.Add( item );
                        }
                        work_comments.Clear();
                        MessageBox.Show( "コメントにエラーがありました。エラーのあった行は次の通りです\n" + i + "行目\n" + result[i] );
                        return;
                    }
                    comments.Add( new NComment( float.Parse( spl[0] ), spl[1], spl[2], i ) );
                    //MessageBox.Show( "Config::set; comments[i-1].type.Position.ToString()=" + comments[i - 1].type.Position.ToString() );
                }
                comments.Sort();
            }
        }

        public DialogResult EntrySetting( ref string entry_config ) {
            return DialogResult.Cancel;
        }

        public DialogResult BaseSetting() {
            FConfig fconf = new FConfig( Config );
            DialogResult result = fconf.ShowDialog();
            if ( result == DialogResult.OK ) {
                Config = fconf.ConfigString;
            }
            fconf.Dispose();
            return result;
        }

        /// <summary>
        /// comで指定されたコメント・タイプのニコニココメントを、
        /// Graphics gの位置xに描画します。
        /// </summary>
        /// <param name="time">このコメントが表示され始めてからの経過時間</param>
        /// <param name="g"></param>
        /// <param name="com"></param>
        /// <param name="x"></param>
        public static void drawString( Graphics g, NComment com, Rectangle rc, double time, int comment_num ) {
            const string font_name = "ＭＳ ゴシック";
            Font font = new Font( font_name, com.type.Size.Size, FontStyle.Bold, GraphicsUnit.Pixel );
            Color color = com.type.Color.Color;
            int axy = com.type.Position.getPosition( rc.Height, com.type.Size.Size );
            int x;
            if ( com.type.Position == NComTypePosition.ue || com.type.Position == NComTypePosition.shita ) {
                x = (int)(rc.Width / 2.0f - com.width / 2.0f);
            } else {
                x = (int)(rc.Width - time * ((com.width + rc.Width) / 3.0f));
            }
            GraphicsPath path = new GraphicsPath();
            //FontFamily ff = new FontFamily( font_name );
            FontFamily ff = font.FontFamily;
            path.AddString( com.message, ff, (int)FontStyle.Bold, com.type.Size.Size, new Point( 0, 0 ), StringFormat.GenericDefault );
            using ( Bitmap tmp = new Bitmap( com.width, com.height, PixelFormat.Format32bppArgb ) ) {
                using ( Graphics gx = Graphics.FromImage( tmp ) ) {
                    gx.Clear( Color.FromArgb( 0, Color.White ) );
                    Color shadow;
                    if ( color != Color.Black ) {
                        shadow = Color.Black;
                    } else {
                        shadow = Color.White;
                    }
                    gx.DrawPath( new Pen( Color.FromArgb( 50, shadow ), 3.5f ), path );
                    gx.DrawPath( new Pen( Color.FromArgb( 180, shadow ), 2.5f ), path );
                    gx.FillPath( new SolidBrush( color ), path );
                    //gx.DrawString( com.message, font, new SolidBrush( color ), new PointF( 0, 0 ) );
                }
                float alpha = 0.6f + 0.4f * ((float)com.count / (float)comment_num);
                ColorMatrix cm = new ColorMatrix();
                cm.Matrix00 = 1;
                cm.Matrix11 = 1;
                cm.Matrix22 = 1;
                cm.Matrix33 = alpha;
                cm.Matrix44 = 1;
                ImageAttributes ia = new ImageAttributes();
                ia.SetColorMatrix( cm );

                g.DrawImage( tmp, new Rectangle( x, com.y, tmp.Width, tmp.Height ),
                    0, 0, tmp.Width, tmp.Height, GraphicsUnit.Pixel, ia );
            }
        }
        public void Render( Graphics g, Size size, float time, string mouth, string reserved ) {
        }

    }

    public class NComment : IComparable {
        public const int NULL = -999;
        public float time;
        public NComType type;
        public string message;
        public int width;
        public int height;
        public int count;
        public int y;

        public NComment( float time, string command, string message, int count ) {
            this.time = time;
            this.type = new NComType( command );
            this.message = message;
            this.count = count;
            this.y = NULL;
            SizeF sizef;
            Bitmap dumy = new Bitmap( 1, 1 );
            Font font = new Font( "ＭＳ ゴシック", this.type.Size.Size, FontStyle.Bold, GraphicsUnit.Pixel );
            using ( Graphics g = Graphics.FromImage( dumy ) ) {
                sizef = g.MeasureString( message, font );
            }
            this.width = (int)sizef.Width;
            this.height = (int)sizef.Height;
            dumy.Dispose();
            font.Dispose();
        }
        
        public int CompareTo( object obj ) {
            NComment com = (NComment)obj;
            if ( this.time > com.time ) {
                return 1;
            } else if ( this.time < com.time ) {
                return -1;
            } else {
                return 0;
            }
        }
    }

    public class NComType {
        public NComTypePosition Position;
        public NComTypeColor Color;
        public NComTypeSize Size;
        public NComType( string command ) {
            Position = NComTypePosition.def;
            Color = NComTypeColor.def;
            Size = NComTypeSize.def;
            string[] spl = command.Split( new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries );
            for ( int i = spl.Length - 1; i >= 0; i-- ) {
                NComTypePosition pos = NComTypePosition.fromCommand( spl[i] );
                if ( pos != NComTypePosition.INVALID ) {
                    Position = pos;
                    continue;
                }
                NComTypeColor col = NComTypeColor.fromCommand( spl[i] );
                if ( col != NComTypeColor.INVALID ) {
                    Color = col;
                    continue;
                }
                NComTypeSize size = NComTypeSize.fromCommand( spl[i] );
                if ( size != NComTypeSize.INVALID ) {
                    Size = size;
                    continue;
                }
            }
        }
    }

    public class NComTypePosition {
        private int m_value;
        public static NComTypePosition ue = new NComTypePosition( 0 );
        public static NComTypePosition shita = new NComTypePosition( 1 );
        public static NComTypePosition def = new NComTypePosition( 2 );
        public static NComTypePosition INVALID = new NComTypePosition( -1 );
        private NComTypePosition( int value ) {
            m_value = value;
        }
        public static NComTypePosition fromCommand( string command ) {
            switch ( command ) {
                case "ue":
                    return ue;
                case "shita":
                    return shita;
                case "naka":
                    return def;
                default:
                    return INVALID;
            }
        }
        public int getPosition( float height, float fontsize ) {
                switch ( m_value ) {
                    case 0:
                        return 0;
                    case 1:
                        return (int)(height - fontsize * 1.2f);
                    default:
                        return -1;

                }
        }
        new public string ToString() {
            switch ( m_value ) {
                case 0:
                    return "ue";
                case 1:
                    return "shita";
                case 2:
                    return "def";
                default:
                    return "INVALID";
            }
        }
    }

    public class NComTypeColor {
        private int m_value;
        public static NComTypeColor white = new NComTypeColor( 0 );
        public static NComTypeColor def = new NComTypeColor( 0 );
        public static NComTypeColor red = new NComTypeColor( 1 );
        public static NComTypeColor pink = new NComTypeColor( 2 );
        public static NComTypeColor orange = new NComTypeColor( 3 );
        public static NComTypeColor yellow = new NComTypeColor( 4 );
        public static NComTypeColor green = new NComTypeColor( 5 );
        public static NComTypeColor cyan = new NComTypeColor( 6 );
        public static NComTypeColor blue = new NComTypeColor( 7 );
        public static NComTypeColor purple = new NComTypeColor( 8 );
        public static NComTypeColor niconicowhite = new NComTypeColor( 9 );
        public static NComTypeColor truered = new NComTypeColor( 10 );
        public static NComTypeColor passionorange = new NComTypeColor( 11 );
        public static NComTypeColor madyellow = new NComTypeColor( 12 );
        public static NComTypeColor elementalgreen = new NComTypeColor( 13 );
        public static NComTypeColor marineblue = new NComTypeColor( 14 );
        public static NComTypeColor nobleviolet = new NComTypeColor( 15 );
        public static NComTypeColor black = new NComTypeColor( 16 );
        public static NComTypeColor INVALID = new NComTypeColor( -1 );
        private NComTypeColor( int value ) {
            m_value = value;
        }
        public static NComTypeColor fromCommand( string command ) {
            switch ( command ) {
                case "white":
                    return white;
                case "red":
                    return red;
                case "pink":
                    return pink;
                case "orange":
                    return orange;
                case "yellow":
                    return yellow;
                case "green":
                    return green;
                case "cyan":
                    return cyan;
                case "blue":
                    return blue;
                case "purple":
                    return purple;
                case "niconicowhite":
                case "white2":
                    return niconicowhite;
                case "truered":
                case "red2":
                    return truered;
                case "passionorange":
                case "orange2":
                    return passionorange;
                case "madyellow":
                case "yellow2":
                    return madyellow;
                case "elementalgree":
                case "green2":
                    return elementalgreen;
                case "marineblue":
                case "blue2":
                    return marineblue;
                case "nobleviolet":
                case "purple2":
                    return nobleviolet;
                case "black":
                    return black;
                default:
                    return INVALID;
            }
        }
        public Color Color {
            get {
                switch ( m_value ) {
                    case 0:
                        return Color.White;
                    case 1:
                        return Color.Red;
                    case 2:
                        return Color.Pink;
                    case 3:
                        return Color.Orange;
                    case 4:
                        return Color.Yellow;
                    case 5:
                        return Color.FromArgb( 0, 255, 0 );
                    case 6:
                        return Color.Cyan;
                    case 7:
                        return Color.Blue;
                    case 8:
                        return Color.Purple;
                    case 9:
                        return Color.FromArgb( 204, 204, 153 );
                    case 10:
                        return Color.FromArgb( 204, 0, 51 );
                    case 11:
                        return Color.FromArgb( 255, 102, 0 );
                    case 12:
                        return Color.FromArgb( 153, 153, 0 );
                    case 13:
                        return Color.FromArgb( 0, 204, 102 );
                    case 14:
                        return Color.FromArgb( 51, 255, 252 );
                    case 15:
                        return Color.FromArgb( 102, 51, 204 );
                    case 16:
                        return Color.Black;
                }
                return Color.White;
            }
        }
    }

    public class NComTypeSize {
        private int m_value;
        public static NComTypeSize big = new NComTypeSize( 0 );
        public static NComTypeSize medium = new NComTypeSize( 1 );
        public static NComTypeSize small = new NComTypeSize( 2 );
        public static NComTypeSize def = new NComTypeSize( 1 );
        public static NComTypeSize INVALID = new NComTypeSize( -1 );
        private NComTypeSize( int value ) {
            m_value = value;
        }
        public static NComTypeSize fromCommand( string command ) {
            switch ( command ) {
                case "big":
                    return big;
                case "medium":
                    return medium;
                case "small":
                    return small;
                default:
                    return INVALID;
            }
        }
        public float Size {
            get {
                switch ( m_value ) {
                    case 0:
                        return 39f;
                    case 1:
                        return 24f;
                    case 2:
                        return 15f;
                    default:
                        return 24f;
                }
            }
        }
    }

}
