using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace MUDClientEssentials
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        //this "fancy" user interface actually communicates with servers through this object
        private MUDServerConnection serverConnection;

        #region startup, establishing a connection

        //obligatory constructor
        public MainWindow()
        {
            InitializeComponent();
        }

        //when the main window loads, prompt the user for connection info
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            bool successfulConnection;
            do
            {
                //prompts
                string address = TextInputDialog.PromptUser("Server Address", "Input a MUD server address:");
                string portString = TextInputDialog.PromptUser("Server Port", "Enter the server's port number:");

                //convert port to int, default to port=23 in the event of any parsing issue
                int port;
                if (!int.TryParse(portString, out port))
                    port = 23;

                //attempt a connection
                successfulConnection = true;
                try
                {
                    //tell the user what we're doing first
                    this.appendText("Attempting a connection to " + address + ", port " + port.ToString() + "...");
                    
                    //then give it a shot
                    this.serverConnection = new MUDServerConnection(address, port);
                }

                //if there's any problem, start over with prompts again
                catch (Exception)
                {
                    this.appendText("Connection failed.  Please verify your internet connectivity and server information, then try again.");
                    successfulConnection = false;
                }
            }
            while (!successfulConnection);

            //now that we've connected, start listening for messages and disconnections 
            this.serverConnection.serverMessage += new MUDServerConnection.serverMessageEventHandler(serverConnection_serverMessage);
            this.serverConnection.disconnected += new MUDServerConnection.disconnectionEventHandler(serverConnection_disconnected);
            this.serverConnection.telnetMessage += new MUDServerConnection.serverTelnetEventHandler(serverConnection_telnetMessage);
        }

        #endregion

        #region receiving server text

        //when a telnet message arrives, display it in the special telnet output box
        void serverConnection_telnetMessage(string message)
        {
            //add the new message
            this.telnetOutputBox.AppendText(message + System.Environment.NewLine);

            //scroll down to ensure it's visible
            this.telnetOutputBox.ScrollToEnd();
        }

        //when a content message arrives, display it in the main output box
        void serverConnection_serverMessage(List<MUDTextRun> genericRuns)
        {
            //convert the generic "MUD Text Runs" to "WPF Runs" so that they can be displayed in the UI
            List<Run> wpfRuns = new List<Run>();
            {
                foreach(MUDTextRun genericRun in genericRuns)
                {
                    Run newRun = new Run(genericRun.Content);
                    newRun.Foreground = new SolidColorBrush(this.getColor(genericRun.ForegroundColor));
                    newRun.Background = new SolidColorBrush(this.getColor(genericRun.BackgroundColor));

                    wpfRuns.Add(newRun);
                }
            }
            
            //display them
            this.appendRuns(wpfRuns.ToArray());
        }

        //associates an actual color with each of the 15 color numbers used by servers
        //any modern client should make these user-customizable!
        //the color values used in this color theme come from the ANSI control sequence page on wikipedia. they're garish.
        private Color getColor(int colorNumber)
        {
            switch (colorNumber)
            {
                //colors 0 through 7 are basic colors
                case 0:
                    return Color.FromRgb(0, 0, 0);
                case 1:
                    return Color.FromRgb(128, 0, 0);
                case 2:
                    return Color.FromRgb(0, 128, 0);
                case 3:
                    return Color.FromRgb(128, 128, 0);
                case 4:
                    return Color.FromRgb(0, 0, 128);
                case 5:
                    return Color.FromRgb(128, 0, 128);
                case 6:
                    return Color.FromRgb(0, 128, 128);
                case 7:
                    return Color.FromRgb(192, 192, 192);

                //colors 8 through 15 are "intense" versions of the basic colors above
                //in this example, 7 is medium gray, and its corresponding "intense" version at 15 is bright white                
                case 8:
                    return Color.FromRgb(128, 128, 128);
                case 9:
                    return Color.FromRgb(255, 0, 0);
                case 10:
                    return Color.FromRgb(0, 255, 0);
                case 11:
                    return Color.FromRgb(255, 255, 0);
                case 12:
                    return Color.FromRgb(0, 0, 255);
                case 13:
                    return Color.FromRgb(255, 0, 255);
                case 14:
                    return Color.FromRgb(0, 255, 255);
                default: //case 15
                    return Color.FromRgb(255, 255, 255);                
            }
        }
                
        //displays plain text in the main output window (by turning it into a WPF run first)
        private void appendText(string message)
        {
            //add a line to the output box
            Run run = new Run(message);
            run.Foreground = new SolidColorBrush(Colors.White);
            run.Background = new SolidColorBrush(Colors.CornflowerBlue);
            this.appendRuns(run);
        }

        //displays rich text in the main output window
        private void appendRuns(params Run [] runs)
        {
            //create a new "paragraph" element, vertically separating this bunch of runs from the previous bunch
            Paragraph newParagraph = new Paragraph();
            
            //fill it with the provided runs
            newParagraph.Inlines.AddRange(runs);

            //add it to the document in the output box
            this.outputBox.Document.Blocks.Add(newParagraph);

            //automatically scroll to the bottom
            ScrollViewer descendantScrollViewer = findScrollViewerDescendant(this.outputBox);

            if (descendantScrollViewer != null)
                descendantScrollViewer.ScrollToEnd();
        }

        //helper for above, because FlowDocumentScrollViewer doesn't have a convenient ScrollToEnd() method
        //if this looks like black magic, that's because it is (this is not a fun area of WPF)
        private static ScrollViewer findScrollViewerDescendant(DependencyObject control)
        {
            if (control is ScrollViewer) return (control as ScrollViewer);

            int childCount = VisualTreeHelper.GetChildrenCount(control);
            for (int i = 0; i < childCount; i++)
            {
                ScrollViewer result = findScrollViewerDescendant(VisualTreeHelper.GetChild(control, i));
                if (result != null) return result;
            }

            return null;
        }

        #endregion

        #region sending text to the server

        //when user presses ENTER in the input box, send that text to the server
        private void inputBox_KeyDown(object sender, KeyEventArgs e)
        {
            //if the keystroke was ENTER
            if (e.Key == Key.Return)
            {
                //send text
                try
                {
                    this.serverConnection.SendText(this.inputBox.Text);
                }
                catch
                {
                    this.appendText("Failed to send the below command.  Your internet service may have been interrupted, or the server might have shut down.\r\n" + this.inputBox.Text);
                }

                //clear text for next command entry
                this.inputBox.Clear();
            }
        }

        #endregion

        #region server disconnection

        //when the server disconnects, notify the user via the output box
        void serverConnection_disconnected()
        {
            this.appendText("Disconnected.");
        }

        #endregion

        #region exiting the application

        //when the main window closes, make sure the connection is closed
        private void Window_Closed(object sender, EventArgs e)
        {            
            //make sure connection is closed
            this.serverConnection.Disconnect();
            
            //close the app
            Application.Current.Shutdown();
        }

        #endregion
    }
}