using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;

namespace m3uTool
{
    /// <summary>
    /// Indicates that a problem existed when parsing an mp3 file
    /// </summary>
    public class Mp3FormatException : Exception
    {
        private readonly string mFilename;

        private readonly string mFormatExceptionString;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="formatExceptionString"></param>
        public Mp3FormatException(string filename, string formatExceptionString)
        {
            mFilename = filename;
            mFormatExceptionString = formatExceptionString;
        }

        public string Filename
        {
            get { return mFilename; }
        }

        public string FormatExceptionString
        {
            get { return mFormatExceptionString; }
        }
    }

    /// <summary>
    /// Tool for accessing the SampleRateFreqency Enum
    /// </summary>
    public class ChannelModeUtility
    {
        /// <summary>
        /// Converts a ChannelMode into an Int value
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static int GetChannelModeInt(ChannelMode s)
        {
            switch (s)
            {
                case ChannelMode.Stereo:
                    return 2;
                case ChannelMode.JointStereo:
                    return 2;
                case ChannelMode.DualChannel:
                    return 2;
                case ChannelMode.SingleChannel:
                    return 1;
                default:
                    return 2;
            }
        }

        /// <summary>
        /// (j)oint, (s)imple, (f)orce, (d)dual-mono, (m)ono    (-m)
        /// </summary>
        /// <param name="s"></param>
        public static char GetChannelModeChar(ChannelMode s)
        {
            switch (s)
            {
                case ChannelMode.Stereo:
                    return 's';
                case ChannelMode.JointStereo:
                    return 'j';
                case ChannelMode.DualChannel:
                    return 'd';
                case ChannelMode.SingleChannel:
                    return 'm';
                default:
                    return 'j';
            }
        }
    }

    /// <summary>
    /// The channel mode an mp3 is encoded in 
    /// </summary>
    public enum ChannelMode
    {
        /// <summary>
        /// 
        /// </summary>
        Stereo,

        /// <summary>
        /// Merge a given frequency range of multiple sound channels together so that the 
        /// resulting encoding will perceive the sound information of that range not as a 
        /// bundle of separate channels but as one homogenous lump
        /// </summary>
        JointStereo,

        /// <summary>
        /// made of two independant mono channel. Each one uses exactly half the bitrate of the file
        /// </summary>
        DualChannel,

        /// <summary>
        /// Mono
        /// </summary>
        SingleChannel
    }

    /// <summary>
    /// Tool for accessing the SampleRateFreqency Enum
    /// </summary>
    public class SampleRateFrequencyUtility
    {
        /// <summary>
        /// Converts a SampleRateFrequency into an Int value
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static int GetSampleRateFrequencyInt(SampleRateFrequency s)
        {
            return Convert.ToInt32(s.ToString().Substring(s.ToString().IndexOf("_") + 1));
        }
    }

    /// <summary>
    /// Sample rate frequency of an audio file
    /// </summary>
    public enum SampleRateFrequency
    {
        /// <summary>
        /// reserved
        /// </summary>
        Hz_0,
        /// <summary>
        /// 8000
        /// </summary>
        Hz_8000,
        /// <summary>
        /// 11025
        /// </summary>
        Hz_11025,
        /// <summary>
        /// 12000
        /// </summary>
        Hz_12000,
        /// <summary>
        /// 16000
        /// </summary>
        Hz_16000,
        /// <summary>
        /// 22050
        /// </summary>
        Hz_22050,
        /// <summary>
        /// 24000
        /// </summary>
        Hz_24000,
        /// <summary>
        /// 32000
        /// </summary>
        Hz_32000,
        /// <summary>
        /// 41000
        /// </summary>
        Hz_44100,
        /// <summary>
        /// 48000
        /// </summary>
        Hz_48000
    }

    /// <summary>
    /// Bitrates
    /// 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256 and 320
    /// </summary>
    public enum BitRate
    {
        /// <summary>
        /// reserved
        /// </summary>
        KBPS_0,
        /// <summary>
        /// 32
        /// </summary>
        KBPS_32,
        /// <summary>
        /// 40
        /// </summary>
        KBPS_40,
        /// <summary>
        /// 48
        /// </summary>
        KBPS_48,
        /// <summary>
        /// 48
        /// </summary>
        KBPS_56,
        /// <summary>
        /// 64
        /// </summary>
        KBPS_64,
        /// <summary>
        /// 80
        /// </summary>
        KBPS_80,
        /// <summary>
        /// 96
        /// </summary>
        KBPS_96,
        /// <summary>
        /// 112
        /// </summary>
        KBPS_112,
        /// <summary>
        /// 128
        /// </summary>
        KBPS_128,
        /// <summary>
        /// 160
        /// </summary>
        KBPS_160,
        /// <summary>
        /// 192
        /// </summary>
        KBPS_192,
        /// <summary>
        /// 224
        /// </summary>
        KBPS_224,
        /// <summary>
        /// 256
        /// </summary>
        KBPS_256,
        /// <summary>
        /// 320
        /// </summary>
        KBPS_320,
    }

    /// <summary>
    /// Tool for accessing the BitRate Enum
    /// </summary>
    public class BitRateFrequencyUtility
    {
        /// <summary>
        /// Converts a BitRate into an Int value
        /// </summary>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int GetBitRateInt(BitRate b)
        {
            return Convert.ToInt32(b.ToString().Substring(b.ToString().IndexOf("_") + 1));
        }

        /// <summary>
        /// Converts a BitRate into an Int value
        /// </summary>
        /// <param name="b"></param>
        /// <returns></returns>
        public static int GetBitRateBitsInt(BitRate b)
        {
            return GetBitRateInt(b)*1024;
        }
    }

    /// <summary>
    /// Given an mp3 file, this extracts all the encoding details and id3 fields
    /// </summary>
    public class Mp3FileProperties
    {
        #region private variables

        /// <summary>
        /// mp3's filename, without path
        /// </summary>
        private readonly string mMp3Filename;

        /// <summary>
        /// path to the mp3
        /// </summary>
        private readonly string mMp3Path;

        private ulong bithdr;
        private bool boolVBitRate;
        private int intVFrames;

        /// <summary>
        /// The mp3 bit rate
        /// </summary>
        private int mBitRate;

        /// <summary>
        /// the channel mode
        /// </summary>
        private ChannelMode mChannelMode;

        /// <summary>
        /// the file size, in bytes
        /// </summary>
        private long mFileSize;

        /// <summary>
        /// The album field
        /// </summary>
        private string mId3Album;

        /// <summary>
        /// The artist field
        /// </summary>
        public string mId3Artist;

        /// <summary>
        /// The comment field
        /// </summary>
        private string mId3Comment;

        /// <summary>
        /// The genre field byte
        /// </summary>
        private byte mId3Genre;

        /// <summary>
        /// The genre field name
        /// </summary>
        private string mId3GenreName;

        /// <summary>
        /// True if the id3 tag exists in the mp3
        /// </summary>
        private bool mId3TagExists;

        /// <summary>
        /// The title field
        /// </summary>
        private string mId3Title;

        /// <summary>
        /// The track number field
        /// </summary>
        private byte mId3TrackNumber;

        /// <summary>
        /// The year field
        /// </summary>
        private string mId3Year;

        /// <summary>
        /// the mp3 length in seconds
        /// </summary>
        private int mLengthInSeconds;

        /// <summary>
        /// The sample rate frequency
        /// </summary>
        private SampleRateFrequency mSampleRateFrequency;

        /// <summary>
        /// The sample rate frequency integer
        /// </summary>
        private int mSampleRateIntegerFrequency;

        // Private variables used in the process of reading in the MP3 files

        #endregion

        #region Public Properties

        /// <summary>
        /// path to the mp3
        /// </summary>
        public string Mp3Path
        {
            get { return mMp3Path; }
        }

        /// <summary>
        /// mp3's filename, without path
        /// </summary>
        public string Mp3Filename
        {
            get { return mMp3Filename; }
        }

        /// <summary>
        /// mp3's path and filename
        /// </summary>
        public string Mp3PathAndFilename
        {
            get { return mMp3Path + (mMp3Path.EndsWith("\\") ? "" : "\\") + mMp3Filename; }
        }

        /// <summary>
        /// The mp3 bit rate
        /// </summary>
        public int BitRate
        {
            get { return mBitRate; }
        }

        /// <summary>
        /// the file size, in bytes
        /// </summary>
        public long FileSize
        {
            get { return mFileSize; }
        }

        /// <summary>
        /// The sample rate frequency integer
        /// </summary>
        public int SampleRateIntegerFrequency
        {
            get { return mSampleRateIntegerFrequency; }
        }

        /// <summary>
        /// The sample rate frequency
        /// </summary>
        public SampleRateFrequency SampleRateFrequency
        {
            get { return mSampleRateFrequency; }
        }

        /// <summary>
        /// the channel mode
        /// </summary>
        public ChannelMode ChannelMode
        {
            get { return mChannelMode; }
        }

        /// <summary>
        /// the mp3 length in seconds
        /// </summary>
        public int LengthInSeconds
        {
            get { return mLengthInSeconds; }
        }

        /// <summary>
        /// the length formatted
        /// </summary>
        public string LengthFormatted
        {
            get
            {
                // Complete number of seconds
                int s = GetLengthInSeconds();

                // Seconds to display
                int ss = s%60;

                // Complete number of minutes
                int m = (s - ss)/60;

                // Minutes to display
                int mm = m%60;

                // Complete number of hours
                int h = (m - mm)/60;

                // Make "hh:mm:ss"
                return h.ToString("D2") + ":" + mm.ToString("D2") + ":" + ss.ToString("D2");
            }
        }

        /// <summary>
        /// the mp3 length in timespan 
        /// </summary>
        public TimeSpan FileLength
        {
            get { return new TimeSpan(0, 0, 0, 0, mLengthInSeconds); }
        }

        /// <summary>
        /// True if the id3 tag exists in the mp3
        /// </summary>
        public bool Id3TagExists
        {
            get { return mId3TagExists; }
        }

        /// <summary>
        /// The title field
        /// </summary>
        public string Id3Title
        {
            get { return mId3Title; }
        }

        /// <summary>
        /// The artist field
        /// </summary>
        public string Id3Artist
        {
            get { return mId3Artist; }
        }

        /// <summary>
        /// The album field
        /// </summary>
        public string Id3Album
        {
            get { return mId3Album; }
        }

        /// <summary>
        /// The year field
        /// </summary>
        public string Id3Year
        {
            get { return mId3Year; }
        }

        /// <summary>
        /// The comment field
        /// </summary>
        public string Id3Comment
        {
            get { return mId3Comment; }
        }

        /// <summary>
        /// The track number field
        /// </summary>
        public byte Id3TrackNumber
        {
            get { return mId3TrackNumber; }
        }

        /// <summary>
        /// The genre field byte
        /// </summary>
        public byte Id3Genre
        {
            get { return mId3Genre; }
        }

        /// <summary>
        /// The genre field name
        /// </summary>
        public string Id3GenreName
        {
            get { return mId3GenreName; }
        }

        #endregion

        #region Private Readonly Static Lookup

        /// <summary>
        /// list of genres defined by id3 1.1
        /// </summary>
        private static readonly string[] mGenres =
            {
                "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", "Metal",
                "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", "Industrial",
                "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop",
                "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House",
                "Game", "Sound Clip", "Gospel", "Noise", "Alternative Rock", "Bass", "Soul", "Punk", "Space",
                "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
                "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream",
                "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle",
                "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi",
                "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk",
                "Folk/Rock", "National Folk", "Swing", "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass"
                ,
                "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock",
                "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera",
                "Chamber Music",
                "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club",
                "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhytmic Soul", "Freestyle", "Duet",
                "Punk Rock", "Drum Solo", "Acapella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House",
                "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap",
                "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian",
                "Christian Rock", "Merengue", "Salsa", "Trash Metal", "Anime", "JPop", "SynthPop"
            };

        /// <summary>
        /// bit rates available to mp3
        /// </summary>
        private static readonly int[,,] mBitrateTable = {
                                                            {
                                                                // MPEG 2 & 2.5
                                                                {
                                                                    0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128,
                                                                    144
                                                                    , 160, 0
                                                                }, // Layer III
                                                                {
                                                                    0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128,
                                                                    144
                                                                    , 160, 0
                                                                }, // Layer II
                                                                {
                                                                    0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176,
                                                                    192
                                                                    , 224, 256, 0
                                                                } // Layer I
                                                            },
                                                            {
                                                                // MPEG 1
                                                                {
                                                                    0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192,
                                                                    224,
                                                                    256, 320, 0
                                                                }, // Layer III
                                                                {
                                                                    0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224,
                                                                    256
                                                                    , 320, 384, 0
                                                                }, // Layer II
                                                                {
                                                                    0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320,
                                                                    352,
                                                                    384, 416, 448, 0
                                                                } // Layer I
                                                            }
                                                        };

        /// <summary>
        /// integer sample rates avaiable for mp3
        /// </summary>
        private static readonly int[,] mSampleFrequencyIntegerTable = {
                                                                          {32000, 16000, 8000}, // MPEG 2.5
                                                                          {0, 0, 0}, // reserved
                                                                          {22050, 24000, 16000}, // MPEG 2
                                                                          {44100, 48000, 32000} // MPEG 1
                                                                      };

        /// <summary>
        /// Lookup for 
        /// </summary>
        private static readonly SampleRateFrequency[,] mSampleFrequencyTable = {
                                                                                   {
                                                                                       SampleRateFrequency.Hz_32000,
                                                                                       SampleRateFrequency.Hz_16000,
                                                                                       SampleRateFrequency.Hz_8000
                                                                                       // MPEG 2.5
                                                                                   },
                                                                                   {
                                                                                       SampleRateFrequency.Hz_0,
                                                                                       SampleRateFrequency.Hz_0,
                                                                                       SampleRateFrequency.Hz_0
                                                                                       // reserved
                                                                                   },
                                                                                   {
                                                                                       SampleRateFrequency.Hz_22050,
                                                                                       SampleRateFrequency.Hz_24000,
                                                                                       SampleRateFrequency.Hz_16000
                                                                                       // MPEG 2
                                                                                   },
                                                                                   {
                                                                                       SampleRateFrequency.Hz_44100,
                                                                                       SampleRateFrequency.Hz_48000,
                                                                                       SampleRateFrequency.Hz_32000
                                                                                       // MPEG 1
                                                                                   }
                                                                               };

        #endregion

        #region Constructor

        // Required struct constructor
        public Mp3FileProperties(string name)
        {
            var fFileInfo = new FileInfo(name);
            mMp3Path = fFileInfo.DirectoryName;
            mMp3Filename = fFileInfo.Name;

            ReadMP3Information();
            ReadMP3Tag();
        }

        #endregion

        private Mp3FileProperties()
        {
        }

        #region private methods

        private void ReadMP3Information()
        {
            var fs = new FileStream(Mp3PathAndFilename, FileMode.Open, FileAccess.Read);
            // Set the filename not including the path information
            var chrSeparators = new[] {'\\', '/'};
            string[] strSeparator = Mp3PathAndFilename.Split(chrSeparators);
            int intUpper = strSeparator.GetUpperBound(0);
            //mMp3PathAndFilename = strSeparator[intUpper];

            //// Replace ' with '' for the SQL INSERT statement
            //mMp3PathAndFilename = mMp3PathAndFilename.Replace("'", "''");

            // Set the file size
            mFileSize = fs.Length;

            var bytHeader = new byte[4];
            var bytVBitRate = new byte[12];
            int intPos = 0;

            // Keep reading 4 bytes from the header until we know for sure that in 
            // fact it's an MP3
            do
            {
                fs.Position = intPos;
                fs.Read(bytHeader, 0, 4);
                intPos++;
                LoadMP3Header(bytHeader);
            } while (!IsValidHeader() && (fs.Position != fs.Length));

            // If the current file stream position is equal to the length, 
            // that means that we've read the entire file and it's not a valid MP3 file
            if (fs.Position == fs.Length)
                throw new Mp3FormatException(Mp3PathAndFilename, "Invalid mp3 file");

            intPos += 3;

            if (getVersionIndex() == 3) // MPEG Version 1
            {
                if (getModeIndex() == 3) // Single Channel
                {
                    intPos += 17;
                }
                else
                {
                    intPos += 32;
                }
            }
            else // MPEG Version 2.0 or 2.5
            {
                if (getModeIndex() == 3) // Single Channel
                {
                    intPos += 9;
                }
                else
                {
                    intPos += 17;
                }
            }

            // Check to see if the MP3 has a variable bitrate
            fs.Position = intPos;
            fs.Read(bytVBitRate, 0, 12);
            boolVBitRate = LoadVBRHeader(bytVBitRate);

            // Once the file's read in, then assign the properties of the file to the public variables
            mBitRate = GetBitrate();
            mSampleRateIntegerFrequency = GetSampleRateFrequencyInt();
            mSampleRateFrequency = GetSampleRateFrequency();
            mChannelMode = GetChannelMode();
            mLengthInSeconds = GetLengthInSeconds();
            fs.Close();
        }

        private void LoadMP3Header(byte[] c)
        {
            // this thing is quite interesting, it works like the following
            // c[0] = 00000011
            // c[1] = 00001100
            // c[2] = 00110000
            // c[3] = 11000000
            // the operator << means that we'll move the bits in that direction
            // 00000011 << 24 = 00000011000000000000000000000000
            // 00001100 << 16 =         000011000000000000000000
            // 00110000 << 24 =                 0011000000000000
            // 11000000       =                         11000000
            //                +_________________________________
            //                  00000011000011000011000011000000
            bithdr = (ulong) (((c[0] & 255) << 24) | ((c[1] & 255) << 16) | ((c[2] & 255) << 8) | ((c[3] & 255)));
        }

        private bool LoadVBRHeader(byte[] inputheader)
        {
            // If it's a variable bitrate MP3, the first 4 bytes will read 'Xing'
            // since they're the ones who added variable bitrate-edness to MP3s
            if (inputheader[0] == 88 && inputheader[1] == 105 &&
                inputheader[2] == 110 && inputheader[3] == 103)
            {
                int flags = (((inputheader[4] & 255) << 24) | ((inputheader[5] & 255) << 16) |
                             ((inputheader[6] & 255) << 8) | ((inputheader[7] & 255)));
                if ((flags & 0x0001) == 1)
                {
                    intVFrames = (((inputheader[8] & 255) << 24) | ((inputheader[9] & 255) << 16) |
                                  ((inputheader[10] & 255) << 8) | ((inputheader[11] & 255)));
                    return true;
                }
                else
                {
                    intVFrames = -1;
                    return true;
                }
            }
            return false;
        }

        private bool IsValidHeader()
        {
            return (((getFrameSync() & 2047) == 2047) &&
                    ((getVersionIndex() & 3) != 1) &&
                    ((getLayerIndex() & 3) != 0) &&
                    ((getBitrateIndex() & 15) != 0) &&
                    ((getBitrateIndex() & 15) != 15) &&
                    ((getFrequencyIndex() & 3) != 3) &&
                    ((getEmphasisIndex() & 3) != 2));
        }

        private int getFrameSync()
        {
            return (int) ((bithdr >> 21) & 2047);
        }

        private int getVersionIndex()
        {
            return (int) ((bithdr >> 19) & 3);
        }

        private int getLayerIndex()
        {
            return (int) ((bithdr >> 17) & 3);
        }

        //private int getProtectionBit() 
        //{ 
        //    return (int)((bithdr>>16) & 1);  
        //}

        private int getBitrateIndex()
        {
            return (int) ((bithdr >> 12) & 15);
        }

        private int getFrequencyIndex()
        {
            return (int) ((bithdr >> 10) & 3);
        }

        //private int getPaddingBit()    
        //{ 
        //    return (int)((bithdr>>9) & 1);  
        //}

        //private int getPrivateBit()    
        //{ 
        //    return (int)((bithdr>>8) & 1);  
        //}

        private int getModeIndex()
        {
            return (int) ((bithdr >> 6) & 3);
        }

        //private int getModeExtIndex()  
        //{ 
        //    return (int)((bithdr>>4) & 3);  
        //}

        //private int getCoprightBit()   
        //{ 
        //    return (int)((bithdr>>3) & 1);  
        //}

        //private int getOrginalBit()    
        //{ 
        //    return (int)((bithdr>>2) & 1);  
        //}

        private int getEmphasisIndex()
        {
            return (int) (bithdr & 3);
        }

        //private double getVersion() 
        //{
        //    double[] table = {2.5, 0.0, 2.0, 1.0};
        //    return table[getVersionIndex()];
        //}

        //private int getLayer() 
        //{
        //    return (int)(4 - getLayerIndex());
        //}

        private int GetBitrate()
        {
            // If the file has a variable bitrate, then we return an integer average bitrate,
            // otherwise, we use a lookup table to return the bitrate
            int layerIndex = getLayerIndex();
            if (!boolVBitRate)
            {
                try
                {
                    int versionIndex = getVersionIndex();
                    int bitrateIndex = getBitrateIndex();
                    return mBitrateTable[versionIndex & 1, layerIndex - 1, bitrateIndex];
                }
                catch (Exception) // must have been variable afterall
                {
                    boolVBitRate = true;
                }
            }

            double medFrameSize = mFileSize/(double) getNumberOfFrames();
            return (int) ((medFrameSize*GetSampleRateFrequencyInt())/(1000.0*((layerIndex == 3) ? 12.0 : 144.0)));
        }

        private int GetSampleRateFrequencyInt()
        {
            return mSampleFrequencyIntegerTable[getVersionIndex(), getFrequencyIndex()];
        }

        private SampleRateFrequency GetSampleRateFrequency()
        {
            return mSampleFrequencyTable[getVersionIndex(), getFrequencyIndex()];
        }

        private ChannelMode GetChannelMode()
        {
            switch (getModeIndex())
            {
                default:
                    return ChannelMode.Stereo;
                case 1:
                    return ChannelMode.JointStereo;
                case 2:
                    return ChannelMode.DualChannel;
                case 3:
                    return ChannelMode.SingleChannel;
            }
        }

        private int GetLengthInSeconds()
        {
            // "intKilBitFileSize" made by dividing by 1000 in order to match the "Kilobits/second"
            int intKiloBitFileSize = ((8*(int) mFileSize)/1000);
            return (intKiloBitFileSize/GetBitrate());
        }

        private int getNumberOfFrames()
        {
            // Again, the number of MPEG frames is dependant on whether it's a variable bitrate MP3 or not
            if (!boolVBitRate)
            {
                double medFrameSize = (((getLayerIndex() == 3) ? 12 : 144)*
                                       ((1000.0*GetBitrate())/GetSampleRateFrequencyInt()));
                return (int) (mFileSize/medFrameSize);
            }
            else
                return intVFrames;
        }

        private static string TruncateStringAtNull(string sourceString)
        {
            var targetCharList = new ArrayList(sourceString.Length);
            for (int i = 0; i < sourceString.Length && sourceString[i] != 0; i++)
            {
                // Debug.WriteLine("c : " + (int)sourceString[i] + "  " + sourceString[i]);

                targetCharList.Add(sourceString[i] < 32 ? ' ' : sourceString[i]);
            }
            return new string((char[]) targetCharList.ToArray(typeof (char)));
            // Regex.Replace(new string((char [])targetCharList.ToArray(typeof (char))), @"\W*", "");
        }

        private void ReadMP3Tag()
        {
            //	Read the 128 byte ID3 tag into a byte array
            FileStream oFileStream;
            oFileStream = new FileStream(Mp3PathAndFilename, FileMode.Open);
            var bBuffer = new byte[128];
            oFileStream.Seek(-128, SeekOrigin.End);
            oFileStream.Read(bBuffer, 0, 128);
            oFileStream.Close();

            //	Convert the	Byte Array to a String
            Encoding instEncoding = new ASCIIEncoding(); //	NB: Encoding is an Abstract class
            string id3Tag = instEncoding.GetString(bBuffer);

            //	If	there is an	attched ID3	v1.x TAG then read it 
            if (id3Tag.Substring(0, 3) == "TAG")
            {
                mId3Title = TruncateStringAtNull(id3Tag.Substring(3, 30));
                mId3Artist = TruncateStringAtNull(id3Tag.Substring(33, 30));
                mId3Album = TruncateStringAtNull(id3Tag.Substring(63, 30));
                mId3Year = TruncateStringAtNull(id3Tag.Substring(93, 4));
                mId3Comment = TruncateStringAtNull(id3Tag.Substring(97, 28));

                // Get the	track	number if TAG conforms to ID3	v1.1
                if (id3Tag[125] == 0)
                    mId3TrackNumber = bBuffer[126];
                else
                    mId3TrackNumber = 0;
                mId3Genre = bBuffer[127];
                if (mGenres.Length > mId3Genre)
                    mId3GenreName = mGenres[mId3Genre];
                mId3TagExists = true;
                // ********* IF USED IN ANGER: ENSURE to test for non-numeric year
            }
            else
            {
                // ID3 Tag	not found so create an empty TAG	in	case the	user saces later
                mId3Title = "";
                mId3Artist = "";
                mId3Album = "";
                mId3Year = "";
                mId3Comment = "";
                mId3TrackNumber = 0;
                mId3Genre = 0;
                mId3GenreName = "";
                mId3TagExists = false;
            }
        }

        private static void UpdateMP3ID3Tag(ref Mp3FileProperties paramMP3)
        {
            //	Trim any	whitespace
            paramMP3.mId3Title = paramMP3.mId3Title.Trim();
            paramMP3.mId3Artist = paramMP3.mId3Artist.Trim();
            paramMP3.mId3Album = paramMP3.mId3Album.Trim();
            paramMP3.mId3Year = paramMP3.mId3Year.Trim();
            paramMP3.mId3Comment = paramMP3.mId3Comment.Trim();

            //	Ensure all properties are correct size
            if (paramMP3.mId3Title.Length > 30) paramMP3.mId3Title = paramMP3.mId3Title.Substring(0, 30);
            if (paramMP3.mId3Artist.Length > 30) paramMP3.mId3Artist = paramMP3.mId3Artist.Substring(0, 30);
            if (paramMP3.mId3Album.Length > 30) paramMP3.mId3Album = paramMP3.mId3Album.Substring(0, 30);
            if (paramMP3.mId3Year.Length > 4) paramMP3.mId3Year = paramMP3.mId3Year.Substring(0, 4);
            if (paramMP3.mId3Comment.Length > 28) paramMP3.mId3Comment = paramMP3.mId3Comment.Substring(0, 28);

            //	Build	a new	ID3 Tag (128 Bytes)
            var tagByteArray = new byte[128];
            for (int i = 0; i < tagByteArray.Length; i++)
                tagByteArray[i] = 0; //	Initialise array to nulls

            //	Convert the	Byte Array to a String
            Encoding instEncoding = new ASCIIEncoding();
            //	NB: Encoding is an Abstract class // ************ To DO:	Make a shared instance of ASCIIEncoding so we don't keep	creating/destroying it
            //	Copy "TAG" to Array
            byte[] workingByteArray = instEncoding.GetBytes("TAG");
            Array.Copy(workingByteArray, 0, tagByteArray, 0, workingByteArray.Length);
            //	Copy Title to Array
            workingByteArray = instEncoding.GetBytes(paramMP3.mId3Title);
            Array.Copy(workingByteArray, 0, tagByteArray, 3, workingByteArray.Length);
            //	Copy Artist	to	Array
            workingByteArray = instEncoding.GetBytes(paramMP3.mId3Artist);
            Array.Copy(workingByteArray, 0, tagByteArray, 33, workingByteArray.Length);
            //	Copy Album to Array
            workingByteArray = instEncoding.GetBytes(paramMP3.mId3Album);
            Array.Copy(workingByteArray, 0, tagByteArray, 63, workingByteArray.Length);
            //	Copy Year to Array
            workingByteArray = instEncoding.GetBytes(paramMP3.mId3Year);
            Array.Copy(workingByteArray, 0, tagByteArray, 93, workingByteArray.Length);
            //	Copy Comment to Array
            workingByteArray = instEncoding.GetBytes(paramMP3.mId3Comment);
            Array.Copy(workingByteArray, 0, tagByteArray, 97, workingByteArray.Length);
            //	Copy Track and	Genre	to	Array
            tagByteArray[126] = paramMP3.mId3TrackNumber;
            tagByteArray[127] = paramMP3.mId3Genre;

            //	SAVE TO DISK: Replace the final 128	Bytes	with our	new ID3 tag
            var oFileStream = new FileStream(paramMP3.Mp3PathAndFilename, FileMode.Open);
            if (paramMP3.mId3TagExists)
                oFileStream.Seek(-128, SeekOrigin.End);
            else
                oFileStream.Seek(0, SeekOrigin.End);
            oFileStream.Write(tagByteArray, 0, 128);
            oFileStream.Close();
            paramMP3.mId3TagExists = true;
        }

        #endregion

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            return string.Format("{0}, BR:{1}, SR:{2}, CH:{3}", mMp3Filename, mBitRate, mSampleRateFrequency,
                                 mChannelMode);
        }

        public static Mp3FileProperties GetCommonProperties(List<Mp3FileProperties> properties)
        {
            var targetProperty = new Mp3FileProperties();

            Type mp3FilePropertiesType = typeof (Mp3FileProperties);
            foreach (PropertyInfo propertyInfo in mp3FilePropertiesType.GetProperties())
            {
                FieldInfo field = mp3FilePropertiesType.GetField("m" + propertyInfo.Name,
                                                                 BindingFlags.NonPublic | BindingFlags.Instance);
                if (field == null)
                {
                    // Debug.WriteLine("Couldn't find " + "m" + propertyInfo.Name);
                    continue;
                }
                if (propertyInfo.PropertyType == typeof (string))
                {
                    string commonString = GetCommonString(properties, propertyInfo);
                    if (commonString != null)
                        field.SetValue(targetProperty, commonString);
                    Debug.WriteLine(propertyInfo.Name + " " + commonString);
                }
                else if (propertyInfo.PropertyType == typeof (int))
                {
                    field.SetValue(targetProperty, GetCommonThing<int>(properties, propertyInfo));
                }
                else if (propertyInfo.PropertyType == typeof (long))
                {
                    field.SetValue(targetProperty, GetCommonThing<long>(properties, propertyInfo));
                }
                else if (propertyInfo.PropertyType == typeof (ChannelMode))
                {
                    field.SetValue(targetProperty, GetCommonThing<ChannelMode>(properties, propertyInfo));
                }
                else if (propertyInfo.PropertyType == typeof (SampleRateFrequency))
                {
                    field.SetValue(targetProperty, GetCommonThing<SampleRateFrequency>(properties, propertyInfo));
                }

                else if (propertyInfo.PropertyType == typeof (byte))
                {
                    field.SetValue(targetProperty, GetCommonThing<byte>(properties, propertyInfo));
                }
                else if (propertyInfo.PropertyType == typeof (bool))
                {
                    field.SetValue(targetProperty, GetCommonThing<bool>(properties, propertyInfo));
                }
                else
                    throw new ApplicationException("Unknown type " + propertyInfo.PropertyType);


                // Debug.WriteLine(propertyInfo);
            }
            return targetProperty;
        }

        /// <summary>
        /// Gets the common thing.
        /// </summary>
        /// <param name="properties">The properties.</param>
        /// <param name="propertyInfo">The property info.</param>
        /// <returns></returns>
        public static T GetCommonThing<T>(List<Mp3FileProperties> properties, PropertyInfo propertyInfo) where T : new()
        {
            if (typeof (T) != propertyInfo.PropertyType)
                throw new ArgumentException("Incorrect type.  Expecting " + typeof (T) + ", got " +
                                            propertyInfo.PropertyType);
            var tCounts = new Dictionary<T, int>();

            foreach (Mp3FileProperties property in properties)
            {
                var value = (T) propertyInfo.GetValue(property, null);
                if (tCounts.ContainsKey(value))
                    tCounts[value] = tCounts[value] + 1;
                else
                    tCounts.Add(value, 1);
            }

            var keys = new List<T>(tCounts.Keys);
            keys.Sort(delegate(T x, T y) { return tCounts[x].CompareTo(tCounts[y]); }
                );
            if (keys.Count > 0)
                return keys[0];
            return new T();
        }


        /// <summary>
        /// Gets the common string.
        /// </summary>
        /// <param name="properties">The properties.</param>
        /// <param name="propertyInfo">The property info.</param>
        /// <returns></returns>
        public static string GetCommonString(List<Mp3FileProperties> properties, PropertyInfo propertyInfo)
        {
            var stringCounts = new Dictionary<string, int>();
            var originalStrings = new Dictionary<string, string>();
            foreach (Mp3FileProperties property in properties)
            {
                var originalString = (string) propertyInfo.GetValue(property, null);
                if (originalString == null)
                    continue;

                // remove whitespace
                while (originalString.Contains("  "))
                    originalString.Replace("  ", " ");
                while (originalString.Contains("\t"))
                    originalString.Replace("\t", " ");

                string value = originalString.ToLower();

                if (!originalStrings.ContainsKey(value))
                    originalStrings.Add(value, originalString);

                if (stringCounts.ContainsKey(value))
                    stringCounts[value] = stringCounts[value] + 1;
                else
                    stringCounts[value] = 1;
            }
            if (stringCounts.Count == 0)
                return null;

            var keys = new List<string>(originalStrings.Keys);
            keys.Sort(delegate(string x, string y) { return stringCounts[x].CompareTo(stringCounts[y]); }
                );

            if (stringCounts.Count > 1 && stringCounts.Count > (properties.Count/2))
            {
                // Dictionary<string, int> substringLengths = new Dictionary<string, int>();
                var substringCounts = new Dictionary<string, int>();

                foreach (string keyLHS in keys)
                    foreach (string keyRHS in keys)
                        foreach (string substring in LongestCommonSubstrings.GetLongestSubstring(keyLHS, keyRHS))
                            substringCounts[substring] = (substringCounts.ContainsKey(substring)
                                                              ? substringCounts[substring]
                                                              : 0) + 1;

                var allSubstringsSortedByCount = new List<string>(substringCounts.Keys);
                var allSubstringsSortedByLength = new List<string>(substringCounts.Keys);

                allSubstringsSortedByCount.Sort(
                    delegate(string x, string y) { return substringCounts[x].CompareTo(substringCounts[y]); }
                    );

                allSubstringsSortedByLength.Sort(delegate(string x, string y) { return x.Length.CompareTo(y.Length); }
                    );

                var allSubstringsSortedRankCombination = new List<string>(allSubstringsSortedByLength);

                allSubstringsSortedRankCombination.Sort(delegate(string x, string y)
                                                            {
                                                                return
                                                                    (allSubstringsSortedByCount.IndexOf(x) +
                                                                     allSubstringsSortedByLength.IndexOf(x)).CompareTo(
                                                                         allSubstringsSortedByCount.IndexOf(y) +
                                                                         allSubstringsSortedByLength.IndexOf(y)
                                                                        );
                                                            }
                    );

                int startIndex = keys[0].IndexOf(allSubstringsSortedRankCombination[0]);
                return originalStrings[keys[0]].Substring(startIndex, allSubstringsSortedRankCombination[0].Length);
            }
            return originalStrings[keys[0]];
        }
    }
}