/*
 * TempoTableEntry.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;

namespace Boare.Lib.Vsq {

    public class TempoTable : ICloneable {
        private struct TempoTableEntry : IComparable<TempoTableEntry> {
            public int Clock;
            public int Tempo;
            public double Time;

            public TempoTableEntry( int clock, int tempo, double time ) {
                Clock = clock;
                Tempo = tempo;
                Time = time;
            }

            public int CompareTo( TempoTableEntry item ) {
                return Clock - item.Clock;
            }
        }

        private List<TempoTableEntry> m_tempo_table;
        private int m_base_tempo;
        private int m_tpq;

        private TempoTable() {
        }

        public TempoTable( int base_tempo, int clock_per_quoter ) {
            m_base_tempo = base_tempo;
            m_tpq = clock_per_quoter;
            m_tempo_table = new List<TempoTableEntry>();
            m_tempo_table.Add( new TempoTableEntry( 0, base_tempo, 0.0 ) );
        }

        public object Clone() {
            TempoTable ret = new TempoTable();
            ret.m_base_tempo = m_base_tempo;
            ret.m_tpq = m_tpq;
            ret.m_tempo_table = new List<TempoTableEntry>();
            for ( int i = 0; i < m_tempo_table.Count; i++ ) {
                ret.m_tempo_table.Add( m_tempo_table[i] );
            }
            ret.update();
            return ret;
        }

        public void add( int clock, int tempo ) {
            bool found = false;
            for ( int i = 0; i < m_tempo_table.Count; i++ ) {
                if ( m_tempo_table[i].Clock == clock ) {
                    found = true;
                    m_tempo_table[i] = new TempoTableEntry( clock, tempo, 0.0 );
                    break;
                }
            }
            if ( !found ) {
                m_tempo_table.Add( new TempoTableEntry( clock, tempo, 0.0 ) );
            }
            m_tempo_table.Sort();
            update();
        }

        public void clear( int base_tempo ) {
            m_tempo_table.Clear();
            m_tempo_table.Add( new TempoTableEntry( 0, base_tempo, 0.0 ) );
        }

        private void update() {
            for ( int i = 0; i < m_tempo_table.Count; i++ ) {
                long sum = 0;
                for ( int k = 0; k < i; k++ ) {
                    sum += (m_tempo_table[k].Tempo * (m_tempo_table[k + 1].Clock - m_tempo_table[k].Clock));
                }
                double time = sum / (m_tpq * 1e6);
                m_tempo_table[i] = new TempoTableEntry( m_tempo_table[i].Clock, m_tempo_table[i].Tempo, time );
            }
        }

        /// <summary>
        /// 指定した時刻における、クロックを取得します
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public double getClockFromSec( double time ) {
            // timeにおけるテンポを取得
            int tempo = m_base_tempo;
            double base_clock = 0;
            double base_time = 0f;
            if ( m_tempo_table.Count == 0 ) {
                tempo = m_base_tempo;
                base_clock = 0;
                base_time = 0f;
            } else if ( m_tempo_table.Count == 1 ) {
                tempo = m_tempo_table[0].Tempo;
                base_clock = m_tempo_table[0].Clock;
                base_time = m_tempo_table[0].Time;
            } else {
                for ( int i = m_tempo_table.Count - 1; i >= 0; i-- ) {
                    if ( m_tempo_table[i].Time < time ) {
                        return m_tempo_table[i].Clock + (time - m_tempo_table[i].Time) * m_tpq * 1000000.0 / m_tempo_table[i].Tempo;
                    }
                }
            }
            double dt = time - base_time;
            return base_clock + dt * m_tpq * 1000000.0 / (double)tempo;
        }

        /// <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 = m_base_tempo * 1e-6 / 480.0;
            return clock * sec_per_clock;
        }

        public int getBaseTempo() {
            return m_base_tempo;
        }
    }

    [Serializable]
    public class TempoTableEntry : IComparable<TempoTableEntry>, ICloneable {
        public int Clock;
        public int Tempo;
        public double Time;

        public object Clone() {
            return new TempoTableEntry( Clock, Tempo, Time );
        }

        public TempoTableEntry( int clock, int _tempo, double _time ) {
            this.Clock = clock;
            this.Tempo = _tempo;
            this.Time = _time;
        }

        public TempoTableEntry() {
        }

        public int CompareTo( TempoTableEntry entry ) {
            return this.Clock - entry.Clock;
        }

        public bool Equals( TempoTableEntry entry ) {
            if ( this.Clock == entry.Clock ) {
                return true;
            } else {
                return false;
            }
        }
    }

}
