//------------------------------------------------------------------------------
// <copyright file="FileStager.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Utility for staging HPC Basic Profile data
// </summary>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Mail;
using System.IO;
using System.Collections;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Text.RegularExpressions;

namespace FileStaging
{
    class FileStager
    {
        
        static ArrayList domains = new ArrayList();

        /// <summary>
        /// Console application's main entry point
        /// </summary>
        /// <param name="args">Commandline arguments</param>
        static int Main(string[] args)
        {
            // Dump the HPCBP environment variables if requested
            if (args.Length > 0 && args[0].StartsWith("-debug"))
            {
                IDictionary list = System.Environment.GetEnvironmentVariables();
                foreach (string name in list.Keys)
                {
                    if (name.StartsWith("HPCBP_"))
                    {
                        Console.Write("[" + name + "=" + System.Environment.GetEnvironmentVariable(name) + "] ");
                    }
                }
                Console.WriteLine();
            }

            foreach (String domain in args)
            {
                if (!domain.StartsWith("-debug"))
                {
                    domains.Add(new Regex(domain));
                }
            }
                
            // Obtain the credential for this task
            String credentialElementXML = StagingCredentials.Credentials;
            
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(credentialElementXML);
            String username = null;
            String password = null;
            if (doc.DocumentElement.NamespaceURI.CompareTo("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd") == 0)
            {
                foreach (XmlNode child in doc.DocumentElement.ChildNodes)
                {
                    if (child.Name.CompareTo("Username") == 0)
                    {
                        username = child.InnerText;
                    }
                    if (child.Name.CompareTo("Password") == 0)
                    {
                        password = child.InnerText;
                    }
                }
            }
            else
            {
                return NotAuthorizedFault("The credential type " + doc.DocumentElement.NamespaceURI + " is not known");
            }

            // Set up the SSL certificate acceptance policy for the FTP over SSL
            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(myCertificateValidation);

            // Perform the specified file staging action
            try
            {
                string action = System.Environment.GetEnvironmentVariable("HPCBP_ACTION");
                if (action.CompareTo("COPY-IN") == 0)
                {
                    string source = System.Environment.GetEnvironmentVariable("HPCBP_SOURCE");
                    string local = System.Environment.GetEnvironmentVariable("HPCBP_LOCAL");
                    string fileCreation = System.Environment.GetEnvironmentVariable("HPCBP_CREATION");

                    Uri sourceUri = new Uri(source);
                    if (sourceUri.Scheme == Uri.UriSchemeFtp || sourceUri.Scheme == Uri.UriSchemeHttp || sourceUri.Scheme == Uri.UriSchemeHttps)
                    {
                        return getFileUsingFTPorHTTP(sourceUri, local, username, password, fileCreation);
                    }
                    else if (sourceUri.Scheme == Uri.UriSchemeFile)
                    {
                        // URIs based file://stevenn-test1/C/users/stevenn/myfile.txt
                        try
                        {
                            if (fileCreation.CompareTo("OVERWRITE") == 0)
                            {
                                File.Copy(@"//" + sourceUri.Host + sourceUri.AbsolutePath, local, true);
                            }
                            else if (fileCreation.CompareTo("XX") == 0)
                            {
                                File.Copy(@"//" + sourceUri.Host + sourceUri.AbsolutePath, local, false);
                            }
                            else if (fileCreation.CompareTo("APPEND") == 0)
                            {
                                // Something special
                            }
                        }
                        catch (UnauthorizedAccessException ex)
                        {
                            return NotAuthorizedFault(ex.ToString());
                        }
                        catch
                        {
                        }
                    }
                    else
                    {
                        return UnsupportedProtocolFault("Unable to act on source URI protocol " + sourceUri);
                    }
                }
                else if (action.CompareTo("COPY-OUT") == 0)
                {
                    string local = System.Environment.GetEnvironmentVariable("HPCBP_LOCAL");
                    string dest = System.Environment.GetEnvironmentVariable("HPCBP_TARGET");
                    Uri destUri = new Uri(dest);
                    if (destUri.Scheme == Uri.UriSchemeFtp || destUri.Scheme == Uri.UriSchemeHttp || destUri.Scheme == Uri.UriSchemeHttps)
                    {
                        return putFileUsingFTPorHTTP(local, destUri, username, password);
                    }
                    else if (destUri.Scheme == Uri.UriSchemeFile)
                    {
                        // TODO FIX - no overwrite on stage out? 
                        File.Copy(local, @"//" + destUri.Host + destUri.AbsolutePath, true);
                    }
                    else
                    {
                        return UnsupportedProtocolFault("Unable to act on destination URI protocol " + destUri);
                    }
                }
                else
                {
                    return UnsupportedProtocolFault("Internal error - unable to parse requested action: " + action);
                }
            }
            catch (System.NullReferenceException ex)
            {
                return UnknownFault("Requested environment variable not set? " + ex);
            }
            return 1;
        }

        private static int NotAuthorizedFault(string detail)
        {
            WriteFault("NotAuthorizedFault", detail);
            return 2;
        }

        private static int UnsupportedProtocolFault(string detail)
        {
            WriteFault("UnsupportedProtocolFault", detail);
            return 3;
        }

        private static int FileNotFoundFault(string detail)
        {
            WriteFault("FileNotFoundFault", detail);
            return 4;
        }

        private static int LocalStagingFault(string detail)
        {
            WriteFault("LocalStagingFault", detail);
            return 5;
        }

        private static int RemoteStagingFault(string detail)
        {
            WriteFault("RemoteStagingFault", detail);
            return 6;
        }

        private static int UnknownFault(string detail)
        {
            WriteFault("UnknownFault", detail);
            return 7;
        }

        private static void WriteFault(string fault, string detail)
        {
            Console.Out.WriteLine(fault + ":" + detail);
            Console.Error.WriteLine(fault + ":" + detail);
        }

        static public bool myCertificateValidation(Object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors Errors)
        {
            //  If no domain restrictions are specified then accept any certificate
            if (domains.Count == 0)
            {
                return true;
            }

            foreach (Regex validDomain in domains)
            {
                if (validDomain.IsMatch(cert.Subject))
                {
                    return true;
                }
            }

            // If no acceptable domains found then reject request
            return false;
        }

        private static int getFileUsingFTPorHTTP(Uri sourceUri, string localFilename, string username, string password, string fileCreation)
        {
            if (String.IsNullOrEmpty(localFilename))
            {
                return FileNotFoundFault("Unparseable destination for source:" + sourceUri);
            }

            // If file already exists am I allowed to overwrite?
            if (fileCreation.CompareTo("NOOVERWRITE") == 0 && File.Exists(localFilename))
            {
                // File exists and I am not allowed to overwrite then no need to transfer the file in
                Console.Out.WriteLine("Completed " + sourceUri + " -> " + localFilename + " - file already in place and not overwriting");
                return 0;
            }

            Stream bitStream = null;
            FileStream fileStream = null;
            int ret = 0;

            try
            {
                // Open the connection
                WebRequest request = WebRequest.Create(sourceUri);
                request.Credentials = new NetworkCredential(username, password);
                if (sourceUri.Scheme == Uri.UriSchemeFtp)
                {
                    request.Method = WebRequestMethods.Ftp.DownloadFile;
                    ((FtpWebRequest)request).EnableSsl = true;
                }
                WebResponse response = request.GetResponse();
                bitStream = response.GetResponseStream();

                if (fileCreation.CompareTo("OVERWRITE") == 0)
                {
                    // Will create a new file if none exists and will overwrite an exsiting file
                    fileStream = new FileStream(localFilename, FileMode.Create);
                }
                else if (fileCreation.CompareTo("APPEND") == 0)
                {
                    // Will open an existing file for appending
                    fileStream = new FileStream(localFilename, FileMode.Append);
                }
                else
                {
                    // Should never get here
                    return 1;
                }

                byte[] buffer = new byte[1024];
                Int32 bytesRead = 0;
                while ((bytesRead = bitStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    fileStream.Write(buffer, 0, bytesRead);
                }
            }
            catch (System.Net.WebException ex)
            {
                if (ex.Message.Contains("Not logged in"))
                {
                    ret = NotAuthorizedFault("The username (" + username + ") failed to gain access to source " + sourceUri);
                }
                else
                {
                    ret = UnknownFault(ex.Message);
                }
            }
            catch (System.IO.IOException ex)
            {
                // TODO: Check exception text
                if (ex.Message.Contains("not enough space"))
                {
                    //ret = InsufficientStorageSpaceFault("There is not enough space to write to file: "+ localFilename);
                }
                else if (ex.Message.Contains("access file"))
                {
                    ret = NotAuthorizedFault("Unable to access file (" + localFilename + ") " + ex);
                }
                else
                {
                    ret = UnknownFault(ex.Message);
                }
            }
            catch (System.UnauthorizedAccessException ex)
            {
                ret = NotAuthorizedFault("Unable to access file (" + localFilename + ") " + ex);
            }
            catch (Exception ex)
            {
                ret = UnknownFault(ex.Message);
            }
            finally
            {
                if (bitStream != null) bitStream.Close();
                if (fileStream != null) fileStream.Close();
            }
            if (ret == 0)
            {
                Console.Out.WriteLine("Completed " + sourceUri + " -> " + localFilename);
            }
            return ret;
        }


        private static int putFileUsingFTPorHTTP(string localFilename, Uri destUri, string username, string password)
        {
            if (String.IsNullOrEmpty(localFilename))
            {
                return FileNotFoundFault("Unparseable source for destination:" + destUri);
            }

            Stream bitStream = null;
            FileStream fileStream = null;
            int ret = 0;

            try
            {
                FileInfo file = new FileInfo(localFilename);
                fileStream = file.OpenRead();

                // Open the connection
                WebRequest request = WebRequest.Create(destUri);
                if (destUri.Scheme == Uri.UriSchemeFtp)
                {
                    request.Method = WebRequestMethods.Ftp.UploadFile;
                    ((FtpWebRequest)request).UseBinary = true;
                    ((FtpWebRequest)request).EnableSsl = true;
                }
                if (destUri.Scheme == Uri.UriSchemeHttp || destUri.Scheme == Uri.UriSchemeHttps)
                {
                    request.Method = WebRequestMethods.Http.Post;
                    request.ContentType = "application/x-www-form-urlencoded";
                    request.ContentLength = file.Length;
                }

                request.Credentials = new NetworkCredential(username, password);
                bitStream = request.GetRequestStream();

                byte[] buffer = new byte[1024];
                Int32 bytesRead = 0;
                while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    bitStream.Write(buffer, 0, bytesRead);
                }
            }
            catch (System.Net.WebException ex)
            {
                if (ex.Message.Contains("Not logged in"))
                {
                    ret = NotAuthorizedFault("The username (" + username + ") failed to gain access to destination " + destUri);
                }
                else
                {
                    ret = UnknownFault(ex.Message);
                }
            }
            catch (System.IO.IOException ex)
            {
                ret = NotAuthorizedFault("Unable to access file (" + localFilename + ") " + ex);
            }
            catch (System.UnauthorizedAccessException ex)
            {
                ret = NotAuthorizedFault("Unable to access file (" + localFilename + ") " + ex);
            }
            catch (Exception ex)
            {
                ret = UnknownFault(ex.Message);
            }
            finally
            {
                if (bitStream != null) bitStream.Close();
                if (fileStream != null) fileStream.Close();
            }
            if (ret == 0)
            {
                Console.Out.WriteLine("Completed " + localFilename + " -> " + destUri);
            }
            return ret;
        }
    }
}

