using System;
using System.Net;
using System.IO;
using System.Text;
using System.Net.Sockets;
using System.Diagnostics;
using System.Xml;

using NJC.Common;

namespace NJC.With.MAPRT.Foundation
{
 #region Ftp機能クラス
 public class FtpClient
 {
  #region 変数定義  
  private string _server    = null;
  private string _username   = null;
  private string _password   = null;
  private string _message   = null;
  private string _result    = null;
  private int  _bytes    = 0;
  private int  _resultCode   = 0;
  private bool _loggedin   = false;
  private bool _binMode   = false;
  private Byte[] _buffer    = new Byte[512];
  private Socket _clientSocket  = null; 
  private string[] _strFileDirectory = null;

  #endregion
  
  #region FTP コマンドバリュー
  // Data connection already open; transfer starting
  private const int DATA_TRANSFER   = 125;
  // Command okay
  private const int COMMAND_OKEY   = 200;
  // File status okay: about to open data connection
  private const int OPEN_CONNECTION  = 150;
  //Command not implemented, superfluous at this site
  private const int SITE     = 202;
  // Service ready for new user
  private const int NEW_USER    = 220;
  // User logged in
  private const int USER_LOGIN   = 230;
  // Closing data connection
  private const int CLOSE_DATA   = 226;
  // Entering Passive Mode
  private const int ENTER_MODE   = 227;
  // Requested file action okay.
  private const int FILE_OKEY    = 250;
  // User name okay and need password
  private const int USERNAME_OKEY   = 331;
  // Requested file action pending further information
  private const int FILE_INFORMATION  = 350; 
  private const string Error_FileExist = "550";
  #endregion

  #region コンストラクタ
  /// <summary>
  /// コンストラクタ
  /// 配置ファイルから、Ftp接続情報を設定する
  /// </summary>
  public FtpClient()
  {            
   // get the server address from the NJC.With.Batch.Common.xml file
   _server  = ConfigurationManager.GetItem("FtpLogIn", "Server");

   // get the username address from the NJC.With.Batch.Common.xml file
   _username = ConfigurationManager.GetItem("FtpLogIn", "UserName");

   // get the password address from the NJC.With.Batch.Common.xml file
   _password = ConfigurationManager.GetItem("FtpLogIn", "PassWord");

   // get the File Directory from the NJC.With.Batch.Common.xml file
   _strFileDirectory = ConfigurationManager.GetItem("FilePath", "ImagePath").Split('\\');
  }
  #endregion

  #region 通信モード
  /// <summary>
  /// 通信モードを設定できる。
  /// ASCII BINARY二種がサポートする
  /// </summary>
  public bool BinaryMode
  {
   get
   {
    return this._binMode;
   }
   set
   {
    if (this._binMode == value)
    {
     return;
    }
    else
    {
     this._binMode = value;
    }

    if (value)
    {
     SendCommand("TYPE I");
    }
    else
    {
     SendCommand("TYPE A");
    }

    if (this._resultCode != COMMAND_OKEY)
    {

     throw new FTPException(this._result);
    }
   }
  }
  #endregion

  #region 登録
  /// <summary>
  /// Ftpサーバへ登録する
  /// </summary>
  public void Login()
  {
   int iTime = 0;
   
   // 3秒に停頓する
   while (iTime < 1000000000)
   {
    ++iTime;
   }

   if (this._loggedin)
   {
    this.Close();
   }

   IPAddress addr = null;
   IPEndPoint ep = null;

   try
   {
    //create a socket
    this._clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    //use DNS to analyzes the server to IpAddress
    addr = Dns.Resolve(this._server).AddressList[0];
    ep  = new IPEndPoint(addr, 21);
    
    //the client computer links the server computer
    this._clientSocket.Connect(ep);
   }
   catch(Exception ex)
   {
    // judge the if the client has already conneted the FTP Server or the client is not null
    if (this._clientSocket != null && this._clientSocket.Connected)
    {
     this._clientSocket.Close();
    }
    throw new FTPException("Couldn't connect to remote server", ex);
   }

   // get the result of the RFC
   this.ReadResponse();

   if (this._resultCode != NEW_USER)
   {
    this.Close();
    throw new FTPException(this._result);
   }
   this.SendCommand("USER " + _username);

   if (!(this._resultCode == USERNAME_OKEY || this._resultCode == USER_LOGIN))
   {
    this.CleanUp();
    throw new FTPException(this._result);
   }

   if (this._resultCode != USER_LOGIN)
   {
    this.SendCommand("PASS " + _password);
    if (!(this._resultCode == USER_LOGIN || this._resultCode == SITE))
    {
     this.CleanUp();
     throw new FTPException(this._result);
    }
   }

   this._loggedin = true;
  }
  #endregion

  #region 接続はクローズする
  /// <summary>
  /// Ftp接続をクローズする
  /// </summary>
  public void Close()
  {
   if (this._clientSocket != null )
   {
    // Quit ftp server
    this.SendCommand("QUIT");
   }

   this.CleanUp();
  }
  #endregion

  #region Ftpサーバから、ファイルをダウンロード/アップロードする
  /// <summary>
  /// Ftpサーバから、CSVファイルをダウンロードする
  /// ローカルファイル名がEmptyになった場合、サーバ側
  /// ファイル名と同じく設定する
  /// </summary>
  /// <param name="remFileName">サーバ側ファイル名</param>
  /// <param name="locFileName">ローカルファイル名</param>
  /// <param name="resume"></param>
  public void Download(string remFileName, string locFileName)
  {
   if (!this._loggedin)
   {
    this.Login();
   }

   this.BinaryMode = true;

   if (locFileName.Equals(""))
   {
    locFileName = remFileName;
   }
   FileStream output = null;

   if (!File.Exists(locFileName))
   {
    output = File.Create(locFileName);
   }
   else
   {
    output = new FileStream(locFileName, FileMode.Open);
   }

   Socket cSocket = CreateDataSocket();

   this.SendCommand("RETR " + remFileName);
   
   while ( true )
   {
    this._bytes = cSocket.Receive(_buffer, _buffer.Length, 0);
    output.Write(this._buffer,0,this._bytes);

    if ( this._bytes <= 0)
    {
     break;
    }
   }

   output.Close();

   if ( cSocket.Connected )
   {
    cSocket.Close();
   }
   this.ReadResponse();
  }


  /// <summary>
  ///
  /// </summary>
  public void SavePicture(string fileName, string strDirName, string strDirNameTwo)
  {
   if (!this._loggedin)
   {
    this.Login();
   }
   Socket cSocket = null ;

   try
   {
    this.BinaryMode = true;
   }
   catch(Exception)
   {
    // file not exist
   }
   // open stream to read file
   FileStream input = new FileStream(fileName, FileMode.Open);

   // dont create untill we know that we need it
   cSocket = this.CreateDataSocket();

   // サーバ上での格納場所は「(Withシステム用イメージ格納ディレクトリ)/イメージNo/イメージNo枝番」でデ
   // ィレクトリを作成し、その配下にイメージファイルを格納する。
   foreach (string strFileDirectory in _strFileDirectory)
   {
    SendCommand("MKD  " + @strFileDirectory);
    SendCommand("CWD " + @strFileDirectory);
   }
   SendCommand("MKD  " + @strDirName);
   SendCommand("CWD " + @strDirName);
   SendCommand("MKD  " + @strDirNameTwo);
   SendCommand("CWD " + @strDirNameTwo);

   // イメージファイルを新規に指定された場合は、指定したイメージファイルをサーバ上にアップロードして格納
   SendCommand("STOR " + Path.GetFileName(fileName));

   while ((_bytes = input.Read(_buffer,0,_buffer.Length)) > 0)
   {
    cSocket.Send(_buffer, _bytes, 0);
   }
   
   input.Close();

   if (this._resultCode != DATA_TRANSFER && this._resultCode != OPEN_CONNECTION)
   {
    throw new FTPException(this._result);
   }
   
   if (cSocket.Connected)
   {
    cSocket.Close();
   }

   this.ReadResponse();

   if (this._resultCode != CLOSE_DATA && this._resultCode != FILE_OKEY)
   {
    throw new FTPException(this._result);
   }
  }

  /// <summary>
  /// Delete Picture
  /// </summary>
  public bool DeletePicture(string fileName, string strDirName, string strDirNameTwo)
  {
   // Begin Connect
   if (!this._loggedin)
   {
    this.Login();
   }

   try
   {
    this.BinaryMode = true;
   }
   catch(Exception)
   {
    // file not exist
   }

   // 法人リスク事例情報の削除時にイメージファイル群も削除する必要があるが、
   // この場合は「(Withシステム用イメージ格納ディレクトリ)/イメージNo」配下をディレクトリ毎全て削除すればよい。
   string strSave = @ConfigurationManager.GetItem("FilePath", "ImagePath").ToString() + @"\"
    + strDirName + @"\" + strDirNameTwo;

   SendCommand("CWD " + strSave);
   // Check File Exist
   if (_message.Trim().Substring(0,3) == Error_FileExist)
   {
    return false;
   }
   SendCommand("DELE " + Path.GetFileName(fileName));

   // Check File Exist
   if (_message.Trim().Substring(0,3) == Error_FileExist)
   {
    return false;
   }
   SendCommand("CDUP ");
   if (_message.Trim().Substring(0,3) == Error_FileExist)
   {
    return false;
   }
   SendCommand("RMD " + strDirNameTwo);
   
   if (this._resultCode != CLOSE_DATA && this._resultCode != FILE_OKEY)
   {
    throw new FTPException(this._result);
   }

   return true;
  }
 
  #region Ftp通信内容を解析する
  /// <summary>
  /// Ftpサーバから返事を解析
  /// </summary>
  private void ReadResponse()
  {
   this._message = "";
   this._result = this.ReadLine();

   if (this._result.Length > 3)
   {
    this._resultCode = int.Parse( this._result.Substring(0, 3)); 
   }
   else
   {
    this._result = null;
   }
  }

  /// <summary>
  /// Ftpサーバから返事をとる
  /// </summary>
  /// <returns></returns>
  private string ReadLine()
  {
   while(true)
   {
    _bytes = _clientSocket.Receive(this._buffer, this._buffer.Length, 0);
    _message += Encoding.ASCII.GetString(this._buffer, 0, this._bytes);

    if (this._bytes < this._buffer.Length)
    {
     break;
    }
   }
    
   return _message;
  }
  #endregion

  #region アップロード(ダウンロード)に必要なネットワーク処理を用意する
  /// <summary>
  /// Ftpサーバへコマンドを送信する
  /// Ftpサーバからの返事も受ける
  /// </summary>
  /// <param name="command"></param>
  private void SendCommand(String command)
  {
   // 送信データを片付ける
   Byte[] cmdBytes = Encoding.GetEncoding("Shift_JIS").GetBytes(command + "\r\n") ;

   // 送信する
   _clientSocket.Send(cmdBytes, cmdBytes.Length, SocketFlags.OutOfBand);

   // 受信する
   this.ReadResponse();
  }

  /// <summary>
  /// ネットワーク通信のため、ソケットをクリエートする
  /// </summary>
  /// <returns>Connected socket</returns>
  private Socket CreateDataSocket()
  {
   this.SendCommand("PASV");

   if (this._resultCode != ENTER_MODE)
   {
    throw new FTPException(this._result);
   }

   int index1 = this._result.IndexOf('(');
   int index2 = this._result.IndexOf(')');

   string ipData = this._result.Substring(index1 + 1,index2 - index1 - 1);

   int[] parts = new int[6];

   int len = ipData.Length;
   int partCount = 0;
   string buf="";

   for (int i = 0; i < len && partCount <= 6; i++)
   {
    char ch = char.Parse(ipData.Substring(i,1));

    if (char.IsDigit(ch))
    {
     buf+=ch;
    }
    else if (ch != ',')
    {
     throw new FTPException("Malformed PASV result: " + _result);
    }
    if ( ch == ',' || i+1 == len )
    {
     try
     {
      parts[partCount++] = int.Parse(buf);
      buf = "";
     }
     catch (Exception ex)
     {
      throw new FTPException("Malformed PASV result (not supported?): " + this._result, ex);
     }
    }
   }

   string ipAddress = parts[0] + "."+ parts[1]+ "." + parts[2] + "." + parts[3];
   int port = (parts[4] << 8) + parts[5];

   Socket socket = null;
   IPEndPoint ep = null;

   try
   {
    socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
    ep = new IPEndPoint(Dns.Resolve(ipAddress).AddressList[0], port);
    socket.Connect(ep);
   }
   catch(Exception ex)
   {
    // doubtfull....
    if ( socket != null && socket.Connected ) socket.Close();

    throw new FTPException("Can't connect to remote server", ex);
   }

   return socket;
  }
  #endregion
   
  #region 資源解放を行う
  /// <summary>
  /// ソケットをクローズして、資源の解放
  /// </summary>
  private void CleanUp()
  {
   if (this._clientSocket!=null)
   {
    //close the commuication  between the client and the server
    this._clientSocket.Close();
    this._clientSocket = null;
   }

   this._loggedin = false;
  }
  #endregion
 }
 
 #endregion

 #region FTP例外クラス 機能しません
 /// <summary>
 /// FTPException の概要の説明です。
 /// 只今、処理は入っていない。
 /// </summary>
 public class FTPException : ApplicationException
 {
  /// <summary>
  /// FTPException クラスのコンストラクタ。
  /// </summary>
  public FTPException()
  {

  }

  /// <summary>
  /// 1 つの文字列メッセージを受け付けるコンストラクタ。
  /// </summary>
  /// <param name="message"></param>
  public FTPException(string message) : base(message)
  {

  }
  
  /// <summary>
  /// 1 つの文字列メッセージと、このカスタム例外クラスによってラッピングされる
  /// 内側例外を受け付けるコンストラクタ。
  /// </summary>
  /// <param name="message"></param>
  /// <param name="innerException"></param>
  public FTPException(string message, Exception innerException) : base(message, innerException)
  {

  }
 }

 
 #endregion
 
 #region 配置情報取得 機能しません
 /// <summary>
 /// 配置情報取得
 /// </summary>
 public class ConfigurationManager
 {
  /// <summary>
  /// バッチ配置ファイル
  /// デフォルトは: DataFiles\Config\SectionValue\FtpConfig.xml
  /// 勿論カスタマイズができます。
  /// </summary>
  private string _batchConfigFile;

  /// <summary>
  /// Construct
  /// </summary>
  public ConfigurationManager()
  {
   _batchConfigFile = "FtpConfig";
  }

  /// <summary>
  /// 外部から配置ファイルの取得ができます
  /// Path + 拡張子は必要ではありません
  /// Path = DataFiles\Config\SectionValue
  /// 拡張子 = ".xml"
  /// </summary>
  public string ConfigFile
  {
   get
   {
    return _batchConfigFile;
   }
  }

  /// <summary>
  /// セクションネッム+キーによって、対応バリューを取得する
  /// </summary>
  /// <param name="sectionName">セクションネッム</param>
  /// <param name="key">キー</param>
  /// <returns>バリュー</returns>
  public static string GetItem(string sectionName, string key)
  {
   string strItem = NJC.Common.SectionValueItemCache.GetAppSetting("FtpConfig", sectionName, key);

   //キーによって、バリューの取得ができない場合は [""] を返却する
   return strItem == null ? String.Empty : strItem;
  }

  /// <summary>
  /// セクションネッム+キーによって、対応バリューを取得する
  /// </summary>
  /// <param name="fileName">Configファイル名</param>
  /// <param name="sectionName">セクションネッム</param>
  /// <param name="key">キー</param>
  /// <returns>バリュー</returns>
  public static string GetItem(string fileName, string sectionName, string key)
  {
   string strItem = NJC.Common.SectionValueItemCache.GetAppSetting(fileName, sectionName, key);

   //キーによって、バリューの取得ができない場合は [""] を返却する
   return strItem == null ? String.Empty : strItem;
  }
 }
}
 #endregion
#endregion