using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using Microsoft.Singularity.Smtp;

namespace Microsoft.Singularity.Smtp.Clients
{
    public class SmtpClient
    {
        public const int _port = 25;
        public static bool verbose = false;

        //////////////////////////////////////////////////////////////////////////////
        //

        private static void Usage()
        {
            Console.WriteLine("Usage:");
            Console.WriteLine("    cli [operation] [options]");
            Console.WriteLine("Options:");
            Console.WriteLine("    /s:server       Send to server.");
            Console.WriteLine("    /?              Print this help information.");
        }

        public static int Main(string[] args)
        {
            bool needHelp = false;
            String client = Dns.GetHostName();
            String server = "127.0.0.1";
            String[] files = null;

            if (args.Length == 0) {
                needHelp = true;
            }

            Console.WriteLine("Self: {0}", client);

            for (int a = 0; a < args.Length  && !needHelp; a++) {
                String arg = args[a];

                if (arg[0] == '/') {
                    int colon = arg.IndexOf(':');
                    String option;
                    String values;

                    if (colon > 1) {
                        option = arg.Substring(1, colon - 1).ToLower();
                        values = arg.Substring(colon + 1);
                    }
                    else {
                        option = arg.Substring(1).ToLower();
                        values = null;
                    }

                    switch (option[0]) {
                        // Commands:
                        case '?':   // Help
                            needHelp = true;
                            break;

                        case 's':   // Server
                        case 'S':
                            server = values;
                            break;

                        case 'v':   // Verbose
                            verbose = true;
                            break;

                        default:
                            Console.WriteLine("Unknown option: {0}", arg);
                            needHelp = true;
                            break;
                    }
                }
                else {
                    String file = Path.GetFileName(arg);
                    String dir = Path.GetDirectoryName(arg);
                    String path = (dir == String.Empty) ?
                        Directory.GetCurrentDirectory() :
                        Path.GetFullPath(dir);
                    files = Directory.GetFiles(path, file);
                }
            }

            if (files == null) {
                needHelp = true;
            }

            if (needHelp) {
                Usage();
                return 1;
            }

            // Send the file.

            String line;
            Socket socket = new Socket(AddressFamily.InterNetwork,
                                       SocketType.Stream,
                                       ProtocolType.Tcp);
            try {
                IPAddress addr = IPAddress.Parse(server);
                socket.Connect(new IPEndPoint(addr, _port));
            } catch (SocketException e) {
                Console.WriteLine("Socket connect failure: {0}", e.Message);
                return 2;
            }

            Session session = new Session(socket);

            session.Verbose = verbose;

            try {
                // Establish SMPT connection.
                line = session.ReadLine7();
                if (session.Result != 220) {
                    Console.WriteLine(":: Aborting due to {0}", session.Result);
                    return session.Quit();
                }

                session.WriteLine7("EHLO ", client);

                for (;;) {
                    line = session.ReadLine7();
                    if (session.Result != 250) {
                        Console.WriteLine(":: Aborting due to {0}", session.Result);
                        return session.Quit();
                    }

                    if (line[3] != '-') {
                        break;
                    }
                }

                // Send files.

                foreach (string file in files) {
                    Console.WriteLine(":: {0}", file);
                    String[] lines = File.ReadAllLines(file);

#if false
                    // Sanitize the data.
                    for (int i = 0; i < lines.Length; i++) {
                        String data = lines[i];

                        if (data.Length >= 2 &&
                            data[data.Length - 2] == '\r' &&
                            data[data.Length - 1] == '\n') {

                            lines[i] = data.Substring(0, data.Length - 2);
                        }
                        else if (data.Length >= 1 &&
                                 data[data.Length - 1] == '\n') {

                            lines[i] = data.Substring(0, data.Length - 1);
                        }
                        else if (data.Length >= 1 &&
                                 data[data.Length - 1] == '\r') {

                            lines[i] = data.Substring(0, data.Length - 1);
                        }
                    }
#endif

                    session.WriteLine7("NOOP ", file);
                    line = session.ReadLine7();
                    if (session.Result != 250) {
                        Console.WriteLine(":: Aborting due to {0}", session.Result);
                        return session.Quit();
                    }

                    string from = FindFrom(lines).ToLower();
                    session.WriteLine7("MAIL FROM: <", from, ">");
                    line = session.ReadLine7();
                    if (session.Result != 250) {
                        Console.WriteLine(":: Aborting due to {0}", session.Result);
                        return session.Quit();
                    }

                    string to = FindTo("To: ", lines);
                    string cc = FindTo("Cc: ", lines);
                    string bcc = FindTo("Bcc: ", lines);
                    if (to == null) {
                        to = cc;
                        cc = null;
                    }
                    if (to == null) {
                        to = bcc;
                        bcc = null;
                    }

                    if (cc != null) {
                        to = to + "," + cc;
                    }
                    if (bcc != null) {
                        to = to + "," + bcc;
                    }

                    if (to == null || to.Length == 0) {
                        Console.WriteLine(":: Couldn't find receipients.");
                        return session.Quit();
                    }

                    string[] rcpts = to.Split(new Char[]{','});
                    for (int i = 0; i < rcpts.Length; i++) {
                        rcpts[i] = rcpts[i].Trim().ToLower();
                        if (rcpts[i].Length == 0) {
                            rcpts[i] = null;
                        }
                    }

                    for (int i = 0; i < rcpts.Length; i++) {
                        for (int j = i + 1; j < rcpts.Length; j++) {
                            if (rcpts[i] == rcpts[j]) {
                                rcpts[j] = null;
                            }
                        }
                    }

                    foreach (string rcpt in rcpts) {
                        if (rcpt != null) {
                            session.WriteLine7("RCPT TO: <", rcpt, ">");
                            line = session.ReadLine7();
                            if (session.Result != 250) {
                                Console.WriteLine(":: Aborting due to {0}", session.Result);
                                return session.Quit();
                            }
                        }
                    }

                    session.WriteLine7("DATA");
                    line = session.ReadLine7();
                    if (session.Result != 354) {
                        Console.WriteLine(":: Aborting due to {0}", session.Result);
                        return session.Quit();
                    }

                    foreach (string data in lines) {
                        if (data.Length > 0 && data[0] == '.') {
                            session.WritePrefix((byte)'.');
                        }
                        session.WriteLine7(data);
                    }
                    session.WriteLine7(".");
                    line = session.ReadLine7();
                    if (session.Result != 250) {
                        Console.WriteLine(":: Aborting due to {0}", session.Result);
                        return session.Quit();
                    }
                }
                session.Quit();
            }
            catch (SocketException e) {
                Console.WriteLine("Caught socket exception: {0}", e.Message);
            }

            return 0;
        }

        //////////////////////////////////////////////////////////////////////////////

        public static String FindFrom(String[] lines)
        {
            foreach (string line in lines) {
                if (line.Length == 0) {
                    return null; // Header ended.
                }
                if (line.StartsWith("From: ")) {
                    return line.Substring(6);
                }
            }
            return null;
        }

        public static string FindTo(String prefix, String[] lines)
        {
            for (int i = 0; i < lines.Length; i++) {
                if (lines[i].Length == 0) {
                    return null; // Header ended.
                }
                if (lines[i].StartsWith(prefix)) {
                    string to = lines[i++].Substring(prefix.Length);
                    while (lines[i].Length > 0 && (lines[i][0] == ' ' || lines[i][0] == '\t')) {
                        to = to + lines[i++];
                    }
                    return to;
                }
            }
            return null;
        }

    }
}


