using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml;

namespace MiniBar2k.Common
{
    public class fbcDefaults
    {
        public static int TimeOut { get { return 30000; } }
        public static int CacheSize { get { return 128; } }
        public static int PrecacheSize { get { return 32; } }
        public static int PrecacheCutoff { get { return 16; } }
        
        public static String SaveFile 
        { 
            get 
            {
                String fullAppName = Assembly.GetCallingAssembly().GetName().CodeBase;
                String fullAppPath = Path.GetDirectoryName(fullAppName);
                String fullFilename = Path.Combine(fullAppPath, "Minibar2k.xml");
                return fullFilename;
            } 
        }
    }

    public class fbcSettings
    {
        public String IP = "1.2.3.4";
        public int Port = 5;

        public fbcSettings()
        {
            try
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(fbcDefaults.SaveFile);
                XmlNode node = xmlDoc.SelectSingleNode("/Minibar2k");
                for (int i = 0; i < node.ChildNodes.Count; i++)
                {
                    if (node.ChildNodes[i].Name == "ip")
                    {
                        IP = node.ChildNodes[i].InnerText;
                    }

                    if (node.ChildNodes[i].Name == "port")
                    {
                        Port = int.Parse(node.ChildNodes[i].InnerText);
                    }
                }
            }
            catch
            {
            }
        }

        public void Save()
        {
            XmlWriter writer = XmlWriter.Create(fbcDefaults.SaveFile);
            writer.WriteStartDocument();
            writer.WriteStartElement("Minibar2k");
            writer.WriteElementString("ip", IP);
            writer.WriteElementString("port", Port.ToString());
            writer.WriteEndElement();
            writer.WriteEndDocument();
            writer.Flush();
            writer.Close();
        }
    }

    public class fbcException : ApplicationException
    {
        public fbcException(string message) : base("Minibar2k error: " + message)
        {
        }
    }

    public enum fbcVolEnum
    {
        up = 0,
        down = 1,
        mute = 2
    }

    public enum fbcOrderEnum
    {
        def = 0,
        repeatplaylist = 1,
        repeattrack = 2,
        shuffletrack = 3,
        shufflealbum = 4,
        shuffledirectory = 5
    }

    public enum fbcThreadState
    {
        connecting = 0,
        connected = 1,
        disconnecting = 2,
        disconnected = 3
    }

    public enum fbcPlayState
    {
        play = 0,
        pause = 1,
        stop = 2
    }

    public enum fbcTitleType
    {
        none = 0,
        title = 1,
        nrtitle = 2
    }

    public class fbcBaseEntry
    {
        // QueueIndex here
        public int PlaylistIndex;
        public int TrackIndex;
        // CurrentPositionSec here
        public String Codec;
        public String Bitrate;
        public float LengthSec;
        public String Artist;
        public String Album;
        public int? TrackNumber;
        public String Title;
        public String RawPath;
        // cache
        public fbcTitleType DisplayTitleType = fbcTitleType.none;
        public String DisplayTitle = "";
    }

    public class fbcPlayEntry : fbcBaseEntry
    {
        public float CurrentPositionSec;
    }

    public class fbcQueueEntry : fbcBaseEntry
    {
        public int QueueIndex;
    }

    public class fbcPlaylist
    {
        public int PlaylistIndex;
        public String Title;
        public int Entries;
    }

    public delegate void fbcConnectionStateEvent(object sender, fbcThreadState state);
    public delegate void fbcSyncEvent(object sender, bool isafter);

    public delegate void fbcNewLineEvent(object sender, String line);

    public delegate void fbcPlayEvent(object sender, fbcPlayState playstate, fbcPlayEntry entry);
    public delegate void fbcVolumeEvent(object sender, float volume);
    public delegate void fbcOrderEvent(object sender, fbcOrderEnum order);
    public delegate void fbcPlaylistEvent(object sender, fbcPlaylist[] lists, int currentindex);
    public delegate void fbcSearchEvent(object sender, fbcBaseEntry[] entries);
    public delegate void fbcListEvent(object sender, fbcBaseEntry[] entries);
    public delegate void fbcQueueEvent(object sender, fbcQueueEntry[] entries);

    public class FoobarController : IDisposable
    {
        private bool isDisposed = false;

        private Queue<String> messageQueue = new Queue<String>();

        private ThreadStart tcpThreadStart = null;
        private Thread tcpThread = null;
        private fbcThreadState tcpThreadState = fbcThreadState.disconnected;
        private fbcThreadState oldThreadState = fbcThreadState.disconnected;
        private Object tcpThreadStateLock = new Object();

        private TcpClient client = null;
        private NetworkStream stream = null;

        private List<fbcPlaylist> playlistList = new List<fbcPlaylist>();
        private List<fbcBaseEntry> searchList = new List<fbcBaseEntry>();
        private List<fbcBaseEntry> listList = new List<fbcBaseEntry>();
        private List<fbcQueueEntry> queueList = new List<fbcQueueEntry>();
        private int playlistCurrent = 0;

        private bool showalbums = true;

        public FoobarManager Manager = null;

        //------------- CONSTRUCTORS, DESTRUCTORS, IDISPOSABLE

        public FoobarController()
        {
            Manager = new FoobarManager(this, fbcDefaults.CacheSize);
        }

        ~FoobarController()
        {
            Dispose(false);
        }

        private void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    // cleanup managed objects
                }
                Disconnect();
                isDisposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        //------------- THREAD-RELATED MEMBERS

        private void tcpThreadMain()
        {
            String CRLF = "\r\n";
            String LF = "\n";

            byte[] buffer = new byte[32768];
            int bytesRead = 0;
            String message = "";
            String tmpmessage = "";
            String[] messages = null;
            bool queueLast = false;

            try
            {
                client = new TcpClient();
                client.NoDelay = true;

//  Enter ye .Net Compact Framework 2.0. Contrary to 1.0, we do have the TimeOut
//  settings. Using them, however, results in errors. DOH!
//
//                client.ReceiveTimeout = fbcDefaults.TimeOut;
//                client.SendTimeout = fbcDefaults.TimeOut;

                fbcSettings settings = new fbcSettings();

                client.Connect(settings.IP, settings.Port);
                stream = client.GetStream();
//                stream.ReadTimeout = fbcDefaults.TimeOut;
//                stream.WriteTimeout = fbcDefaults.TimeOut;

                if ((stream.CanRead) && (stream.CanWrite))
                {
                    lock (tcpThreadStateLock)
                    {
                        tcpThreadState = fbcThreadState.connected;
                    }

                    while (true)
                    {
                        lock (stream)
                        {
                            while (stream.DataAvailable)
                            {
                                try
                                {
                                    bytesRead = stream.Read(buffer, 0, 32768);
                                    message = String.Concat(message, Encoding.UTF8.GetString(buffer, 0, bytesRead));
                                }
                                catch
                                {
                                    throw new fbcException("streamread");
                                }
                            }

                            if (message.Length > 0)
                            {
                                queueLast = (message.LastIndexOf(CRLF) == message.Length - 2);
                                tmpmessage = message.Replace(CRLF, LF);
                                messages = tmpmessage.Split(LF[0]);

                                if (messages.Length > 0)
                                {
                                    for (int i = 0; i < messages.Length; i++)
                                    {
                                        if ((i < messages.Length - 1) || (queueLast))
                                        {
                                            Enqueue((string)messages[i]);
                                        }
                                    }

                                    if (queueLast)
                                    {
                                        message = "";
                                    }
                                    else
                                    {
                                        if (messages.Length > 0)
                                        {
                                            message = (string)messages[messages.Length - 1];
                                        }
                                        else
                                        {
                                            message = "";
                                        }
                                    }
                                }
                                else
                                {
                                    message = "";
                                }
                            }
                        }

                        Thread.Sleep(0);
                    }
                }
            }
            catch (Exception ex) 
            {
                Enqueue(String.Concat("---|", ex.Message));
            }
            finally
            {
                lock (tcpThreadStateLock)
                {
                    tcpThreadState = fbcThreadState.disconnecting;
                }

                stream = null;

                if (client != null)
                {
                    try 
                    {
                        client.Close();
                    }
                    catch 
                    { 
                    }
                }

                client = null;

                lock (tcpThreadStateLock)
                {
                    tcpThreadState = fbcThreadState.disconnected;
                }
            }
        }

        private void Enqueue(String add)
        {
            lock (messageQueue)
            {
                messageQueue.Enqueue(add);
            }
        }

        private String Dequeue()
        {
            String res = "";
            lock (messageQueue)
            {
                res = messageQueue.Dequeue();
            }
            return res;
        }

        private bool Queued()
        {
            bool res = false;
            lock (messageQueue)
            {
                res = (messageQueue.Count > 0);
            }
            return res;
        }

        //------------- UTILITY METHODS

        private String FloatToString(float data)
        {
            String tmpstring = String.Format("{0:#######0.00}", data);
            return tmpstring.Replace(",", ".");
        }

        private float StringToFloat(String data)
        {
            NumberFormatInfo format = new NumberFormatInfo();
            format.CurrencyDecimalSeparator = ".";
            format.CurrencyGroupSeparator = ",";

            return float.Parse(data, format);
        }

        private bool AllowEvent()
        {
            bool ok = false;

            lock (tcpThreadStateLock)
            {
                if (tcpThreadState == fbcThreadState.connected)
                {
                    ok = true;
                }
            }

            return ok;
        }

        private String FixString(String data)
        {
            if (data == "?")
            {
                return "";
            }
            else
            {
                return data;
            }
        }

        private void DataToEntry(String[] data, fbcBaseEntry entry,
                                    int? PlaylistIndex, int? TrackIndex,
                                    int? Codec, int? Bitrate, int? LengthSec,
                                    int? Artist, int? Album, 
                                    int? TrackNumber, int? Title, int? RawPath)
        {
            if (PlaylistIndex != null) { entry.PlaylistIndex = int.Parse(data[(int)PlaylistIndex]); }
            if (TrackIndex != null) { entry.TrackIndex = int.Parse(data[(int)TrackIndex]); }
            if (Codec != null) { entry.Codec = FixString(data[(int)Codec]); }
            if (Bitrate != null) { entry.Bitrate = FixString(data[(int)Bitrate]); }
            if (LengthSec != null) { entry.LengthSec = StringToFloat(data[(int)LengthSec]); }
            if (Artist != null) { entry.Artist = FixString(data[(int)Artist]); }
            if (Album != null) { entry.Album = FixString(data[(int)Album]); }
            if (TrackNumber != null)
            {
                if (data[(int)TrackNumber].Length > 0)
                {
                    try
                    {
                        entry.TrackNumber = int.Parse(data[(int)TrackNumber]);
                    }
                    catch
                    {
                        entry.TrackNumber = null;
                    }
                }
                else
                {
                    entry.TrackNumber = null;
                }
            }
            if (Title != null) { entry.Title = FixString(data[(int)Title]); }
            if (RawPath != null) { entry.RawPath = FixString(data[(int)RawPath]); }
        }

        private void SendString(String data)
        {
            try
            {
                bool canSend = false;
                int requestStart = System.Environment.TickCount;

                lock (tcpThreadStateLock)
                {
                    canSend = ((tcpThreadState == fbcThreadState.connected) && (stream != null));

                    if (canSend)
                    {
                        canSend = stream.CanWrite;
                    }
                }

                if (canSend)
                {
                    String tmpstring = data + "\r\n";
                    byte[] tmpbuffer = Encoding.UTF8.GetBytes(tmpstring);

                    Tick();

                    lock (stream)
                    {
                        stream.Write(tmpbuffer, 0, tmpbuffer.Length);
                    }

                    while (!Queued())
                    {
                        lock (tcpThreadStateLock)
                        {
                            if (tcpThreadState != fbcThreadState.connected) { break; }
                        }

                        Thread.Sleep(0);

                        if ((System.Environment.TickCount < requestStart) || (System.Environment.TickCount > requestStart + fbcDefaults.TimeOut))
                        {
                            throw new fbcException("Connection lost");
                        }
                    }
                }
            }
            catch
            {
//                throw new fbcException("Connection lost");
                try
                {
                    Disconnect();
                }
                catch
                {
                }
            }
        }

        //------------- DATA HANDLERS

        private void HandleLinePlay(String[] data)
        {
            if (data.Length >= 12)
            {
                fbcPlayEntry entry = new fbcPlayEntry();
                DataToEntry(data, entry, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11);
                entry.CurrentPositionSec = StringToFloat(data[3]);

                switch (data[0])
                {
                    case "111": DoPlay(fbcPlayState.play, entry); break;
                    case "112": DoPlay(fbcPlayState.stop, entry); break;
                    case "113": DoPlay(fbcPlayState.pause, entry); break;
                }
            }
        }

        private void HandleLineVolume(String[] data)
        {
            if (data.Length >= 2)
            {
                DoVolume(StringToFloat(data[1]));
            }
        }

        private void HandleLineOrder(String[] data)
        {
            if (data.Length >= 2)
            {
                switch (data[1])
                {
                    case "Default": DoOrder(fbcOrderEnum.def); break;
                    case "Repeat (playlist)": DoOrder(fbcOrderEnum.repeatplaylist); break;
                    case "Repeat (track)": DoOrder(fbcOrderEnum.repeattrack); break;
                    case "Shuffle (tracks)": DoOrder(fbcOrderEnum.shuffletrack); break;
                    case "Shuffle (albums)": DoOrder(fbcOrderEnum.shufflealbum); break;
                    case "Shuffle (directories)": DoOrder(fbcOrderEnum.shuffledirectory); break;
                }
            }
        }

        private void HandleLinePlaylist(String[] data)
        {
            if (data.Length >= 2)
            {
                if (data[0] == "400")
                {
                    playlistList.Clear();
                    playlistList.Capacity = int.Parse(data[1]);

                    if (playlistList.Capacity == 0) { DoPlaylist(new fbcPlaylist[0], 0); }
                }
                else if (data.Length >= 4)
                {
                    fbcPlaylist playlist = new fbcPlaylist();
                    playlist.PlaylistIndex = int.Parse(data[1]);
                    playlist.Title = data[2];
                    playlist.Entries = int.Parse(data[3]);

                    if (data[0] == "402")
                    {
                        playlistCurrent = playlist.PlaylistIndex;
                    }

                    if (playlistList.Count < playlistList.Capacity)
                    {
                        playlistList.Add(playlist);

                        if (playlistList.Count == playlistList.Capacity)
                        {
                            DoPlaylist(playlistList.ToArray(), playlistCurrent);
                        }
                    }
                }
            }
        }

        private void HandleLineSearch(String[] data)
        {
            if (data.Length >= 3)
            {
                if (data[0] == "500")
                {
                    searchList.Clear();
                    searchList.Capacity = int.Parse(data[2]);

                    if (searchList.Capacity == 0) { DoSearch(new fbcBaseEntry[0]); }
                }
                else if (data.Length >= 11)
                {
                    fbcBaseEntry entry = new fbcBaseEntry();
                    DataToEntry(data, entry, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

                    if (searchList.Count < searchList.Capacity)
                    {
                        searchList.Add(entry);

                        if (searchList.Count == searchList.Capacity)
                        {
                            DoSearch(searchList.ToArray());
                        }
                    }
                }
            }
        }

        private void HandleLineList(String[] data)
        {
            if (data.Length >= 3)
            {
                if (data[0] == "600")
                {
                    listList.Clear();
                    listList.Capacity = int.Parse(data[2]);

                    if (listList.Capacity == 0) { DoList(new fbcBaseEntry[0]); }
                }
                else if (data.Length >= 11)
                {
                    fbcBaseEntry entry = new fbcBaseEntry();
                    DataToEntry(data, entry, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

                    if (listList.Count < listList.Capacity)
                    {
                        listList.Add(entry);

                        if (listList.Count == listList.Capacity)
                        {
                            DoList(listList.ToArray());
                        }
                    }
                }
            }
        }

        private void HandleLineQueue(String[] data)
        {
            if (data.Length >= 2)
            {
                if (data[0] == "800")
                {
                    queueList.Clear();
                    queueList.Capacity = int.Parse(data[1]);

                    if (queueList.Capacity == 0) { DoQueue(new fbcQueueEntry[0]); }
                }
                else if (data.Length >= 12)
                {
                    fbcQueueEntry entry = new fbcQueueEntry();
                    DataToEntry(data, entry, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
                    entry.QueueIndex = int.Parse(data[1]);

                    if (queueList.Count < queueList.Capacity)
                    {
                        queueList.Add(entry);

                        if (queueList.Count == queueList.Capacity)
                        {
                            DoQueue(queueList.ToArray());
                        }
                    }
                }
            }
        }

        private void HandleLineInfo(String[] data)
        {
            if (Manager != null) { Manager.ControllerOnError(this); }
        }

        private void HandleLine(String data)
        {
            if (data.Length > 0)
            {
                DoNewLine(data);

                String[] splitData = data.Split('|');

                if (splitData.Length > 0)
                {
                    if (splitData[0].Length > 0)
                    {
                        char msgClass = splitData[0][0];

                        switch (msgClass)
                        {
                            case '1': HandleLinePlay(splitData); break;
                            case '2': HandleLineVolume(splitData); break;
                            case '3': HandleLineOrder(splitData); break;
                            case '4': HandleLinePlaylist(splitData); break;
                            case '5': HandleLineSearch(splitData); break;
                            case '6': HandleLineList(splitData); break;
                            case '8': HandleLineQueue(splitData); break;
                            case '9': HandleLineInfo(splitData); break;
                            case '-': Disconnect(); /*throw new fbcException(splitData[1]);*/ break;
                        }
                    }
                }
            }
        }

        //------------- EVENT CALLERS

        private void DoConnectionStateChange(fbcThreadState state)
        {
            if (OnConnectionStateChange != null) { OnConnectionStateChange(this, state); }
        }

        private void DoSync(bool isafter)
        {
            if (Manager != null) { Manager.ControllerOnSync(this, isafter); }
            if (OnSync != null) { OnSync(this, isafter); }
        }

        private void DoNewLine(String line)
        {
            if (OnNewLine != null) { OnNewLine(this, line); }
        }

        private void DoPlay(fbcPlayState playstate, fbcPlayEntry entry)
        {
            if (AllowEvent()) {
                if (Manager != null) { Manager.ControllerOnPlay(this, playstate, entry); }
                if (OnPlay != null) { OnPlay(this, playstate, entry); } 
            }
        }

        private void DoVolume(float volume)
        {
            if (AllowEvent()) {
                if (Manager != null) { Manager.ControllerOnVolume(this, volume); }
                if (OnVolume != null) { OnVolume(this, volume); } 
            }
        }

        private void DoOrder(fbcOrderEnum order)
        {
            if (AllowEvent()) {
                if (Manager != null) { Manager.ControllerOnOrder(this, order); }
                if (OnOrder != null) { OnOrder(this, order); } 
            }
        }

        private void DoPlaylist(fbcPlaylist[] lists, int currentindex)
        {
            if (AllowEvent()) {
                if (Manager != null) { Manager.ControllerOnPlaylist(this, lists, currentindex); }
                if (OnPlaylist != null) { OnPlaylist(this, lists, currentindex); } 
            }
        }

        private void DoSearch(fbcBaseEntry[] entries)
        {
            if (AllowEvent()) {
                if (Manager != null) { Manager.ControllerOnSearch(this, entries); }
                if (OnSearch != null) { OnSearch(this, entries); } 
            }
        }

        private void DoList(fbcBaseEntry[] entries)
        {
            if (AllowEvent()) {
                if (Manager != null) { Manager.ControllerOnList(this, entries); }
                if (OnList != null) { OnList(this, entries); } 
            }
        }

        private void DoQueue(fbcQueueEntry[] entries)
        {
            if (AllowEvent()) {
                if (Manager != null) { Manager.ControllerOnQueue(this, entries); }
                if (OnQueue != null) { OnQueue(this, entries); } 
            }
        }

        //------------- PUBLIC METHODS

        public void Tick()
        {
            bool doCSC = false;

            lock (tcpThreadStateLock)
            {
                if (tcpThreadState != oldThreadState)
                {
                    oldThreadState = tcpThreadState;
                    doCSC = true;
                }
            }

            if (doCSC)
            {
                DoConnectionStateChange(oldThreadState);
            }

            while (Queued())
            {
                String data = Dequeue();

                if (data.Length > 0)
                {
                    HandleLine(data);
                }
            }
        }

        public bool ShowAlbums(bool? show)
        {
            if (show != null)
            {
                showalbums = (bool)show;
                Manager.ClearCachedTitles();
            }

            return showalbums;
        }

        public String FormatTitle(int PlaylistIndex, int TrackIndex, bool shownumber)
        {
            if (!isConnected())
            {
                return "<not connected>";
            }

            fbcBaseEntry entry = null;

            if (PlaylistIndex >= 0)
            {
                entry = Manager.Entry(PlaylistIndex, TrackIndex);
            }
            else
            {
                entry = Manager.Queue(false)[TrackIndex];
            }

            if (entry != null)
            {
                if (
                        ((entry.DisplayTitleType == fbcTitleType.title) && (!shownumber)) ||
                        ((entry.DisplayTitleType == fbcTitleType.nrtitle) && (shownumber))
                    )
                {
                    return entry.DisplayTitle;
                }
                else
                {
                    StringBuilder sb = new StringBuilder();

                    if (shownumber)
                    {
                        int count = Manager.Playlists()[PlaylistIndex].Entries.ToString().Length;
                        int countcur = TrackIndex.ToString().Length;

                        if (countcur < count)
                        {
                            for (int i = 0; i < count - countcur; i++)
                            {
                                sb.Append("0");
                            }
                        }
                        sb.Append(TrackIndex.ToString());
                        sb.Append(". ");
                    }

                    if (entry.Artist.Length > 0)
                    {
                        sb.Append(entry.Artist);
                        sb.Append(" - ");
                    }

                    if (showalbums)
                    {
                        if (entry.Album.Length > 0)
                        {
                            sb.Append("[");
                            sb.Append(entry.Album);
                            if (entry.TrackNumber != null)
                            {
                                sb.Append(" #");
                                sb.Append(entry.TrackNumber.ToString());
                            }
                            sb.Append("] ");
                        }
                    }

                    if (entry.Title.Length > 0)
                    {
                        sb.Append(entry.Title);
                    }
                    else
                    {
                        if (sb.Length > 0)
                        {
                            sb.Append("<no title>");
                        }
                        else
                        {
                            sb.Append(entry.RawPath);
                        }
                    }

                    if (!shownumber) {
                        entry.DisplayTitleType = fbcTitleType.title;
                    } else {
                        entry.DisplayTitleType = fbcTitleType.nrtitle;
                    }
                    entry.DisplayTitle = sb.ToString();

                    return entry.DisplayTitle;
                }
            }
            else
            {
                return "<invalid entry, please resync>";
            }
        }

        public bool isConnected()
        {
            lock (tcpThreadStateLock)
            {
                return (tcpThreadState == fbcThreadState.connected);
            }
        }

        public void Connect()
        {
            Resync();
        }

        public void Disconnect()
        {
            Exit();
        }

        public void Resync()
        {
            DoSync(false);

            playlistList.Clear();
            searchList.Clear();
            listList.Clear();
            queueList.Clear();
            playlistCurrent = 0;

            lock (tcpThreadStateLock)
            {
                if (tcpThreadState == fbcThreadState.disconnected)
                {
                    tcpThreadState = fbcThreadState.connecting;
                    tcpThreadStart = new ThreadStart(tcpThreadMain);
                    tcpThread = new Thread(tcpThreadStart);
                    tcpThread.Name = "Minibar2k tcpThread";
                    tcpThread.Start();
                }
            }

            while (true)
            {
                lock (tcpThreadStateLock)
                {
                    if (tcpThreadState != fbcThreadState.connecting)
                    {
                        break;
                    }
                }

                Application.DoEvents();
                Thread.Sleep(0);
            }

            lock (tcpThreadStateLock)
            {
                if (tcpThreadState == fbcThreadState.connected)
                {
                    while (Queued()) { Dequeue(); }
                    while (!Queued())
                    {
                        SendString("?\r\n");
                        Thread.Sleep(250);
                    }
                    while (Queued()) { Dequeue(); }
                }
            }

            DoSync(true);
        }

        public void Exit()
        {
            lock (tcpThreadStateLock)
            {
                if (stream != null)
                {
                    if (tcpThread != null)
                    {
                        tcpThread.Abort();
                    }
                }
            }

            lock (tcpThreadStateLock)
            {
                tcpThreadState = fbcThreadState.disconnected;
            }
        }

        //------------- COMMANDS

        public void Next(int? playlist)
        {
            StringBuilder data = new StringBuilder("next");
            if (playlist != null)
            {
                data.Append(" ");
                data.Append((int)playlist);
            }

            SendString(data.ToString());
        }

        public void Prev(int? playlist)
        {
            StringBuilder data = new StringBuilder("prev");
            if (playlist != null)
            {
                data.Append(" ");
                data.Append((int)playlist);
            }

            SendString(data.ToString());
        }

        public void Rand(int? playlist)
        {
            StringBuilder data = new StringBuilder("rand");
            if (playlist != null)
            {
                data.Append(" ");
                data.Append((int)playlist);
            }

            SendString(data.ToString());
        }

        public void Play(int? playlist, int? track)
        {
            StringBuilder data = new StringBuilder("play");
            if (playlist != null)
            {
                data.Append(" ");
                data.Append((int)playlist);
            }
            if (track != null)
            {
                data.Append(" ");
                data.Append(track);
            }

            SendString(data.ToString());
        }

        public void Pause()
        {
            SendString("pause");
        }

        public void Stop()
        {
            SendString("stop");
        }

        public void Seek(bool delta, float secs)
        {
            StringBuilder data = new StringBuilder("seek");
            if (delta)
            {
                data.Append(" delta");
            }
            data.Append(" ");
            data.Append(FloatToString(secs));

            SendString(data.ToString());
        }

        public void List()
        {
            SendString("list");
        }

        public void List(int playlist)
        {
            StringBuilder data = new StringBuilder("list ");
            data.Append(playlist);

            SendString(data.ToString());
        }

        public void List(int? playlist, int fromtrack, int totrack)
        {
            StringBuilder data = new StringBuilder("list");
            if (playlist != null)
            {
                data.Append(" ");
                data.Append((int)playlist);
            }
            data.Append(" ");
            data.Append(fromtrack);
            data.Append(" ");
            data.Append(totrack);

            SendString(data.ToString());
        }

        public void Search(int? playlist, String search)
        {
            StringBuilder data = new StringBuilder("search");
            if (playlist != null)
            {
                data.Append(" ");
                data.Append((int)playlist);
            }
            data.Append(" ");
            data.Append(search);

            SendString(data.ToString());
        }

        public void ListInfo()
        {
            SendString("listinfo all");
        }

        public void ServerInfo()
        {
            SendString("serverinfo");
        }

        public void TrackInfo()
        {
            SendString("trackinfo");
        }

        public void Vol()
        {
            SendString("vol");
        }

        public void Vol(float db)
        {
            StringBuilder data = new StringBuilder("vol ");
            data.Append(FloatToString(db));

            SendString(data.ToString());
        }

        public void Vol(fbcVolEnum voltype)
        {
            StringBuilder data = new StringBuilder("vol ");
            switch (voltype)
            {
                case fbcVolEnum.up: data.Append("up"); break;
                case fbcVolEnum.down: data.Append("down"); break;
                case fbcVolEnum.mute: data.Append("mute"); break;
            }

            SendString(data.ToString());
        }

        public void Order()
        {
            SendString("order");
        }

        public void Order(fbcOrderEnum ordertype)
        {
            StringBuilder data = new StringBuilder("order ");
            switch (ordertype)
            {
                case fbcOrderEnum.def: data.Append("default"); break;
                case fbcOrderEnum.repeatplaylist: data.Append("repeatplaylist"); break;
                case fbcOrderEnum.repeattrack: data.Append("repeattrack"); break;
                case fbcOrderEnum.shuffletrack: data.Append("shuffletrack"); break;
                case fbcOrderEnum.shufflealbum: data.Append("shufflealbum"); break;
                case fbcOrderEnum.shuffledirectory: data.Append("shuffledirectory"); break;
            }

            SendString(data.ToString());
        }

        public void Queue()
        {
            SendString("queue");
        }

        public void Queue(int? playlist, int track)
        {
            StringBuilder data = new StringBuilder("queue");
            if (playlist != null)
            {
                data.Append(" ");
                data.Append((int)playlist);
            }
            data.Append(" ");
            data.Append(track);

            SendString(data.ToString());
        }

        public void QueueDel(int? track)
        {
            StringBuilder data = new StringBuilder("queue del ");
            if (track != null)
            {
                data.Append((int)track);
            }
            else
            {
                data.Append("all");
            }

            SendString(data.ToString());
        }

        //------------- EVENTS

        public event fbcConnectionStateEvent OnConnectionStateChange;
        public event fbcSyncEvent OnSync;

        public event fbcNewLineEvent OnNewLine;

        public event fbcPlayEvent OnPlay;
        public event fbcVolumeEvent OnVolume;
        public event fbcOrderEvent OnOrder;
        public event fbcPlaylistEvent OnPlaylist;
        public event fbcSearchEvent OnSearch;
        public event fbcListEvent OnList;
        public event fbcQueueEvent OnQueue;
    }
}