/*
 * UstFile.cs
 * Copyright (c) 2009 kbinani, PEX
 *
 * 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.IO;
using System.Collections.Generic;
using System.Text;

using bocoree;

namespace Boare.Lib.Vsq{

    public class UstFile : ICloneable {
        public object Tag;
        private float m_tempo = 120.00f;
        private string m_project_name = "";
        private string m_voice_dir = "";
        private string m_out_file = "";
        private string m_cache_dir = "";
        private string m_tool1 = "";
        private string m_tool2 = "";
        private List<UstTrack> m_tracks = new List<UstTrack>();
        private List<TempoTableEntry> m_tempo_table;
        
        public UstFile( string path ){
            cp932reader sr = new cp932reader( path );
            string line = sr.ReadLine();
            if ( line != "[#SETTING]" ) {
                throw new Exception( "invalid ust file" );
            }
            UstTrack track = new UstTrack();
            int type = 0; //0 => reading "SETTING" section
            while( true ) {
                UstEvent ue = null;
                if ( type == 1 ) {
                    ue = new UstEvent();
                }
                if ( line == "[#TRACKEND]" ) {
                    break;
                }
                line = sr.ReadLine(); // "[#" 直下の行
                while( !line.StartsWith( "[#" ) ){
                    string[] spl = line.Split( "=".ToCharArray(), 2 );
                    if ( type == 0 ) {
                        // reading "SETTING" section
                        if ( spl[0] == "Tempo" ) {
                            m_tempo = 125f;
                            float v = 125f;
                            if ( float.TryParse( spl[1], out v ) ) {
                                m_tempo = v;
                            }
                        } else if ( spl[0] == "ProjectName" ) {
                            m_project_name = spl[1];
                        } else if ( spl[0] == "VoiceDir" ) {
                            m_voice_dir = spl[1];
                        } else if ( spl[0] == "OutFile" ) {
                            m_out_file = spl[1];
                        } else if ( spl[0] == "CacheDir" ) {
                            m_cache_dir = spl[1];
                        } else if ( spl[0] == "Tool1" ) {
                            m_tool1 = spl[1];
                        } else if ( spl[0] == "Tool2" ) {
                            m_tool2 = spl[1];
                        }
                    } else if ( type == 1 ) {
                        // readin event section
                        if ( spl[0] == "Length" ) {
                            ue.Length = 0;
                            int v = 0;
                            if ( int.TryParse( spl[1], out v ) ){
                                ue.Length =v;
                            }
                        } else if ( spl[0] == "Lyric" ) {
                            ue.Lyric = spl[1];
                        } else if ( spl[0] == "NoteNum" ) {
                            ue.Note = 0;
                            int v = 0;
                            if ( int.TryParse( spl[1], out v ) ) {
                                ue.Note = v;
                            }
                        } else if ( spl[0] == "Intensity" ) {
                            ue.Intensity =64;
                            int v = 64;
                            if ( int.TryParse( spl[1], out v ) ) {
                                ue.Intensity = v;
                            }
                        } else if ( spl[0] == "PBType" ) {
                            ue.PBType = 5;
                            int v = 5;
                            if ( int.TryParse( spl[1], out v ) ) {
                                ue.PBType = v;
                            }
                        } else if ( spl[0] == "Piches" ) {
                            string[] spl2 = spl[1].Split( ",".ToCharArray() );
                            float[] t = new float[spl2.Length];
                            for ( int i = 0; i < spl2.Length; i++ ) {
                                float v = 0;
                                float.TryParse( spl2[i], out v );
                                t[i] = v;
                            }
                            ue.Pitches = t;
                        } else if ( spl[0] == "Tempo" ) {
                            ue.Tempo = 125f;
                            float v;
                            if ( float.TryParse( spl[1], out v ) ){
                                ue.Tempo = v;
                            }
                        } else if ( spl[0] == "VBR" ) {
                            ue.Vibrato = new UstVibrato( line );
                            /*
                            PBW=50,50,46,48,56,50,50,50,50
                            PBS=-87
                            PBY=-15.9,-20,-31.5,-26.6
                            PBM=,s,r,j,s,s,s,s,s
                            */
                        } else if ( spl[0] == "PBW" || spl[0] == "PBS" || spl[0] == "PBY" || spl[0] == "PBM" ) {
                            if ( ue.Portamento == null ) {
                                ue.Portamento = new UstPortamento();
                            }
                            ue.Portamento.ParseLine( line );
                        } else if ( spl[0] == "Envelope" ) {
                            ue.Envelope = new UstEnvelope( line );
                            //PreUtterance=1
                            //VoiceOverlap=6
                        } else if ( spl[0] == "VoiceOverlap" ) {
                            if ( spl[1] != "" ) {
                                ue.VoiceOverlap = int.Parse( spl[1] );
                            }
                        } else if ( spl[0] == "PreUtterance" ) {
                            if ( spl[1] != "" ) {
                                ue.PreUtterance = int.Parse( spl[1] );
                            }
                        } else if ( spl[0] == "Flags" ) {
                            ue.Flags = line.Substring( 6 );
                        }
                    }
                    line = sr.ReadLine();
                }
                if ( type == 0 ) {
                    type = 1;
                } else if ( type == 1 ) {
                    track.addEvent( ue );
                }
            }
            m_tracks.Add( track );
            sr.Close();
            updateTempoInfo();
        }

        private UstFile(){
        }

        public string getProjectName() {
            return m_project_name;
        }

        public int getBaseTempo() {
            return (int)(6e7 / m_tempo);
        }

        public double getTotalSec() {
            int max = 0;
            for ( int track = 0; track < m_tracks.Count; track++ ) {
                int count = 0;
                for ( int i = 0; i < m_tracks[track].getEventCount(); i++ ) {
                    count += (int)m_tracks[track].getEvent( i ).Length;
                }
                max = Math.Max( max, count );
            }
            return getSecFromClock( max );
        }

        public List<TempoTableEntry> getTempoList() {
            return m_tempo_table;
        }

        public UstTrack getTrack( int track ) {
            return m_tracks[track];
        }

        public int getTrackCount() {
            return m_tracks.Count;
        }

        /// <summary>
        /// TempoTableの[*].Timeの部分を更新します
        /// </summary>
        /// <returns></returns>
        public void updateTempoInfo() {
            m_tempo_table = new List<TempoTableEntry>();
            if ( m_tracks.Count <= 0 ) {
                return;
            }
            int clock = 0;
            double time = 0.0;
            int last_tempo_clock = 0;  //最後にTempo値が代入されていたイベントのクロック
            float last_tempo = m_tempo;   //最後に代入されていたテンポの値
            for ( int i = 0; i < m_tracks[0].getEventCount(); i++ ) {
                if ( m_tracks[0].getEvent( i ).Tempo > 0f ) {
                    time += (clock - last_tempo_clock) / (8.0 * last_tempo);
                    if ( m_tempo_table.Count == 0 && clock != 0 ) {
                        m_tempo_table.Add( new TempoTableEntry( 0, (int)(6e7 / m_tempo), 0.0 ) );
                    }
                    m_tempo_table.Add( new TempoTableEntry( clock, (int)(6e7 / m_tracks[0].getEvent( i ).Tempo), time ) );
                    last_tempo = m_tracks[0].getEvent( i ).Tempo;
                    last_tempo_clock = clock;
                }
                clock += (int)m_tracks[0].getEvent( i ).Length;
            }
#if DEBUG
            using ( StreamWriter sw = new StreamWriter( Path.Combine( System.Windows.Forms.Application.StartupPath, "ust_tempo_info.txt" ) ) ) {
                sw.WriteLine( "Clock\tTime\tTempo" );
                for ( int i = 0; i < m_tempo_table.Count; i++ ) {
                    sw.WriteLine( m_tempo_table[i].Clock + "\t" + m_tempo_table[i].Time + "\t" + m_tempo_table[i].Tempo );
                }
            }
#endif
        }

        /// <summary>
        /// 指定したクロックにおける、clock=0からの演奏経過時間(sec)
        /// </summary>
        /// <param name="clock"></param>
        /// <returns></returns>
        public double getSecFromClock( int clock ) {
            for ( int i = m_tempo_table.Count - 1; i >= 0; i-- ) {
                if ( m_tempo_table[i].Clock < clock ) {
                    double init = m_tempo_table[i].Time;
                    int dclock = clock - m_tempo_table[i].Clock;
                    double sec_per_clock1 = m_tempo_table[i].Tempo * 1e-6 / 480.0;
                    return init + dclock * sec_per_clock1;
                }
            }
            double sec_per_clock = 0.125 / m_tempo;
            return clock * sec_per_clock;
        }

        public void write( string file ) {
            StreamWriter sw = new StreamWriter( file, false, Encoding.GetEncoding( "Shift_JIS" ) );
            sw.WriteLine( "[#SETTING]" );
            sw.WriteLine( "Tempo=" + m_tempo );
            sw.WriteLine( "Tracks=1" );
            sw.WriteLine( "ProjectName=" + m_project_name );
            sw.WriteLine( "VoiceDir=" + m_voice_dir );
            sw.WriteLine( "OutFile=" + m_out_file );
            sw.WriteLine( "CacheDir=" + m_cache_dir );
            sw.WriteLine( "Tool1=" + m_tool1 );
            sw.WriteLine( "Tool2=" + m_tool2 );
            for ( int i = 0; i < m_tracks[0].getEventCount(); i++ ) {
                m_tracks[0].getEvent( i ).print( sw, (uint)i );
            }
            sw.WriteLine( "[#TRACKEND]" );
            sw.Close();
        }

        public object Clone(){
            UstFile ret = new UstFile();
            ret.m_tempo = m_tempo;
            ret.m_project_name = m_project_name;
            ret.m_voice_dir = m_voice_dir;
            ret.m_out_file = m_out_file;
            ret.m_cache_dir = m_cache_dir;
            ret.m_tool1 = m_tool1;
            ret.m_tool2 = m_tool2;
            for ( int i = 0; i < m_tracks.Count; i++ ) {
                ret.m_tracks[i] = (UstTrack)m_tracks[i].Clone();
            }
            ret.m_tempo_table = new List<TempoTableEntry>();
            for ( int i = 0; i < m_tempo_table.Count; i++ ) {
                ret.m_tempo_table.Add( (TempoTableEntry)m_tempo_table[i].Clone() );
            }
            return ret;
        }
    }

}
