/*
 * VsqTrack.cs
 * Copyright (c) 2008-2009 kbinani
 *
 * This file is part of Boare.Lib.Vsq.
 *
 * Boare.Lib.Vsq is free software; you can redistribute it and/or
 * modify it under the terms of the BSD License.
 *
 * Boare.Lib.Vsq 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.IO;

namespace Boare.Lib.Vsq {

    /// <summary>
    /// Stores the data of a vsq track.
    /// </summary>
    [Serializable]
    public partial class VsqTrack : ICloneable {
        public object Tag;
        /// <summary>
        /// トラックの名前。
        /// </summary>
        public string Name;
        private VsqMetaText m_meta_text;
        private List<Boare.Lib.Vsq.MidiEvent> m_midi_event;
        private int m_edited_start = int.MaxValue;
        private int m_edited_end = int.MinValue;
        public string Renderer = "VOCALOID2";

        private class SingerEventIterator : Iterator {
            VsqEventList m_list;
            int m_pos;

            public SingerEventIterator( VsqEventList list ) {
                m_list = list;
                m_pos = -1;
            }

            public bool hasNext() {
                for ( int i = m_pos + 1; i < m_list.getCount(); i++ ) {
                    if ( m_list.getElement( i ).ID.type == VsqIDType.Singer ) {
                        return true;
                    }
                }
                return false;
            }

            public object next() {
                for ( int i = m_pos + 1; i < m_list.getCount(); i++ ) {
                    VsqEvent item = m_list.getElement( i );
                    if ( item.ID.type == VsqIDType.Singer ) {
                        m_pos = i;
                        return item;
                    }
                }
                return null;
            }

            public void remove() {
                if ( 0 <= m_pos && m_pos < m_list.getCount() ) {
                    m_list.removeAt( m_pos );
                }
            }
        }

        private class NoteEventIterator : Iterator {
            VsqEventList m_list;
            int m_pos;

            public NoteEventIterator( VsqEventList list ) {
                m_list = list;
                m_pos = -1;
            }

            public bool hasNext() {
                for ( int i = m_pos + 1; i < m_list.getCount(); i++ ) {
                    if ( m_list.getElement( i ).ID.type == VsqIDType.Anote ) {
                        return true;
                    }
                }
                return false;
            }

            public object next() {
                for ( int i = m_pos + 1; i < m_list.getCount(); i++ ) {
                    VsqEvent item = m_list.getElement( i );
                    if ( item.ID.type == VsqIDType.Anote ) {
                        m_pos = i;
                        return item;
                    }
                }
                return null;
            }

            public void remove() {
                if ( 0 <= m_pos && m_pos < m_list.getCount() ) {
                    m_list.removeAt( m_pos );
                }
            }
        }

        private class EventIterator : Iterator{
            private VsqEventList m_list;
            private int m_pos;

            public EventIterator( VsqEventList list ) {
                m_list = list;
                m_pos = -1;
            }

            public Boolean hasNext() {
                if ( 0 <= m_pos + 1 && m_pos + 1 < m_list.getCount() ) {
                    return true;
                }
                return false;
            }

            public Object next() {
                m_pos++;
                return m_list.getElement( m_pos );
            }

            public void remove() {
                if ( 0 <= m_pos && m_pos < m_list.getCount() ) {
                    m_list.removeAt( m_pos );
                }
            }
        }


        /// <summary>
        /// 歌手変更イベントを，曲の先頭から順に返すIteratorを取得します
        /// </summary>
        /// <returns></returns>
        public Iterator getSingerEventIterator() {
            return new SingerEventIterator( m_meta_text.getEventList() );
        }

        /// <summary>
        /// 音符イベントを，曲の先頭から順に返すIteratorを取得します
        /// </summary>
        /// <returns></returns>
        public Iterator getNoteEventIterator() {
            return new NoteEventIterator( m_meta_text.getEventList() );
        }

        /// <summary>
        /// メタテキストを，メモリー上のストリームに出力します
        /// </summary>
        /// <param name="sw"></param>
        /// <param name="encode"></param>
        /// <param name="eos"></param>
        /// <param name="start"></param>
        public void printMetaText( TextMemoryStream sw, int eos, int start ) {
            m_meta_text.print( sw, false, eos, start );
        }

        /// <summary>
        /// メタテキストを，指定されたファイルに出力します
        /// </summary>
        /// <param name="file"></param>
        public void printMetaText( string file ) {
            TextMemoryStream tms = new TextMemoryStream( FileAccess.ReadWrite );
            int count = m_meta_text.getEventList().getCount();
            int clLast = m_meta_text.getEventList().getElement( count - 1 ).Clock + 480;
            m_meta_text.print( tms, true, clLast, 0 );
            using ( StreamWriter sw = new StreamWriter( file ) ) {
                tms.rewind();
                while ( tms.peek() >= 0 ) {
                    string line = tms.readLine();
                    sw.WriteLine( line );
                }
            }
        }

        /// <summary>
        /// Masterを取得します
        /// </summary>
        public VsqMaster getMaster() {
            return m_meta_text.master;
        }

        internal void setMaster( VsqMaster value ) {
            m_meta_text.master = value;
        }

        /// <summary>
        /// Mixerを取得します
        /// </summary>
        public VsqMixer getMixer() {
            return m_meta_text.mixer;
        }

        internal void setMixer( VsqMixer value ) {
            m_meta_text.mixer = value;
        }

        /// <summary>
        /// このトラックが保持している，指定されたカーブのBPListを取得します
        /// </summary>
        /// <param name="curve"></param>
        /// <returns></returns>
        public VsqBPList getCurve( string curve ) {
            return m_meta_text.getElement( curve );
        }

        public void setCurve( string curve, VsqBPList value ) {
            m_meta_text.setElement( curve, value );
        }

        public int getEventCount() {
            return m_meta_text.getEventList().getCount();
        }

        public VsqEvent getEvent( int index ) {
            return m_meta_text.getEventList().getElement( index );
        }

        public void setEvent( int index, VsqEvent item ) {
            m_meta_text.getEventList().setElement( index, item );
        }

        public void addEvent( VsqEvent item ) {
            m_meta_text.getEventList().add( item );
        }

        public Iterator getEventIterator() {
            return new EventIterator( m_meta_text.getEventList() );
        }

        public void removeEvent( int index ) {
            m_meta_text.getEventList().removeAt( index );
        }

        /// <summary>
        /// このトラックの，最後に編集が加えられた範囲の，開始位置（クロック）を取得します．
        /// このインスタンスを保持しているVsqFileインスタンスのExecuteメソッドによって自動的に更新されます
        /// </summary>
        public int getEditedStart() {
            return m_edited_start;
        }

        internal void setEditedStart( int value ) {
            if ( value < m_edited_start ) {
                m_edited_start = value;
            }
        }

        /// <summary>
        /// このトラックの，最後に編集が加えられた範囲の，終了位置（クロック）を取得します．
        /// このインスタンスを保持しているVsqFileインスタンスのExecuteメソッドによって自動的に更新されます
        /// </summary>
        public int getEditedEnd() {
            return m_edited_end;
        }

        internal void setEditedEnd( int value ) {
            if ( m_edited_end < value ) {
                m_edited_end = value;
            }
        }

        /// <summary>
        /// このトラックの，編集範囲（EditedStart, EditedEnd）をリセットします．
        /// </summary>
        public void resetEditedArea() {
            m_edited_start = int.MaxValue;
            m_edited_end = int.MinValue;
        }

        /// <summary>
        /// このインスタンスのコピーを作成します
        /// </summary>
        /// <returns></returns>
        public object Clone() {
            VsqTrack res = new VsqTrack();
            res.Name = Name;
            if ( m_meta_text != null ) {
                res.m_meta_text = (VsqMetaText)m_meta_text.Clone();
            }
            if ( m_midi_event != null ) {
                res.m_midi_event = new List<MidiEvent>( m_midi_event );
            }
            res.m_edited_start = m_edited_start;
            res.m_edited_end = m_edited_end;
            res.Renderer = Renderer;
            return res;
        }

        private VsqTrack() {
        }

        /// <summary>
        /// Master Trackを構築
        /// </summary>
        /// <param name="tempo"></param>
        /// <param name="numerator"></param>
        /// <param name="denominator"></param>
        public VsqTrack( int tempo, int numerator, int denominator ) {
            this.Name = "Master Track";
            this.m_meta_text = null;
            m_midi_event = new List<MidiEvent>();

            // テンポを設定
            MidiEvent mi_tempo = new MidiEvent();
            mi_tempo.Clock = 0;
            mi_tempo.FirstByte = 0xff;
            byte b1 = (byte)(tempo & 0xff);
            tempo = tempo >> 8;
            byte b2 = (byte)(tempo & 0xff);
            tempo = tempo >> 8;
            byte b3 = (byte)(tempo & 0xff);
            mi_tempo.Data = new byte[5] { 0x51, 0x03, b3, b2, b1 };
            m_midi_event.Add( mi_tempo );

            // 拍子を設定
            MidiEvent mi_timesig = MidiEvent.generateTimeSigEvent( 0, numerator, denominator );
            m_midi_event.Add( mi_timesig );
        }

        /// <summary>
        /// Master Trackでないトラックを構築。
        /// </summary>
        /// <param name="name"></param>
        /// <param name="singer"></param>
        public VsqTrack( string name, string singer ) {
            Name = name;
            m_meta_text = new VsqMetaText( name, singer );
            m_midi_event = new List<MidiEvent>();
        }

        /// <summary>
        /// 歌詞の文字数を調べます
        /// </summary>
        /// <returns></returns>
        public int getLyricLength() {
            int counter = 0;
            for ( int i = 0; i < m_meta_text.getEventList().getCount(); i++ ) {
                if ( m_meta_text.getEventList().getElement( i ).ID.type == VsqIDType.Anote ) {
                    counter++;
                }
            }
            return counter;
        }

        public VsqTrack( List<Boare.Lib.Vsq.MidiEvent> midi_events ) {
            m_midi_event = new List<MidiEvent>( midi_events );
            Name = "";
#if DEBUG
            bocoree.debug.push_log( "VsqTrack..ctor" );
#endif
            using ( TextMemoryStream sw = new TextMemoryStream() ) {
                for ( int i = 0; i < m_midi_event.Count; i++ ) {
                    if ( m_midi_event[i].FirstByte == 0xff && m_midi_event[i].Data.Length > 0 ) {
                        // meta textを抽出
                        byte type = m_midi_event[i].Data[0];
                        if ( type == 0x01 || type == 0x03 ) {
                            char[] ch = new char[m_midi_event[i].Data.Length - 1];
                            for ( int j = 1; j < midi_events[i].Data.Length; j++ ) {
                                ch[j - 1] = (char)midi_events[i].Data[j];
                            }
                            string line = new string( ch );
                            if ( type == 0x01 ) {
                                int second_colon = line.IndexOf( ':', 3 );
                                line = line.Substring( second_colon + 1 );
                                line = line.Replace( "\\n", Environment.NewLine );
                                line = line.Replace( "\n", Environment.NewLine );
                                sw.write( line );
                            } else {
                                Name = line;
                            }
                        }
                    } else {
                        continue;
                    }
                }
                sw.rewind();
                m_meta_text = new VsqMetaText( sw );
            }
        }

        public List<MidiEvent> getTempoList() {
            List<MidiEvent> list = new List<MidiEvent>();
            for ( int i = 0; i < m_midi_event.Count; i++ ) {
                if ( m_midi_event[i].FirstByte == 0xff && m_midi_event[i].Data.Length >= 4 && m_midi_event[i].Data[0] == 0x51 ) {
                    list.Add( m_midi_event[i] );
                }
            }
            return list;
        }

        /// <summary>
        /// MidiEventの中から拍子情報を抽出します
        /// </summary>
        /// <returns></returns>
        public List<MidiEvent> getTimeSigList() {
            List<MidiEvent> list = new List<MidiEvent>();
            for ( int i = 0; i < m_midi_event.Count; i++ ) {
                if ( m_midi_event[i].FirstByte == 0xff && m_midi_event[i].Data.Length >= 5 && m_midi_event[i].Data[0] == 0x58 ) {
                    list.Add( m_midi_event[i] );
                }
            }
            return list;
        }
    }

}
