//
// System.Web.Mail.SmtpStream.cs
//
// Author(s):
//   Per Arneng <pt99par@student.bth.se>
//
//

//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.IO;
using System.Collections;
using System.Text;
using System.Security.Cryptography;
using System.Diagnostics;

namespace System.Web.Mail {

    internal class SmtpStream {
	
	protected Stream stream;
	protected Encoding encoding;
	protected SmtpResponse lastResponse;
	protected string command = "";

	public SmtpStream( Stream stream ) {
	    this.stream = stream;
	    encoding = new ASCIIEncoding();
	}
	
	public Stream Stream {
	    get { return stream; }
	}
	
	public SmtpResponse LastResponse {
	    get { return lastResponse; }
	}
	
	public void WriteRset() {
	    command = "RSET";
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	
	}
	
	public void WriteAuthLogin()
	{
		command = "AUTH LOGIN";
		WriteLine( command );
		ReadResponse();		
	}

	public bool WriteStartTLS( ) 
	{ 
		command = "STARTTLS";
		WriteLine( command );
		ReadResponse();
		return LastResponse.StatusCode == 220;

	}

	public void WriteEhlo( string hostName ) 
	{ 
		command = "EHLO " + hostName;
		WriteLine( command );
		ReadResponse();
		CheckForStatusCode( 250 );
	
	}
	
	public void WriteHelo( string hostName ) { 
	    command = "HELO " + hostName;
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	    
	}
	
	public void WriteMailFrom( string from ) {
	    command = "MAIL FROM: <" + from + ">";
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	    
	}
	
	public void WriteRcptTo( string to ) {
	    command = "RCPT TO: <" + to + ">";  
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	    	    
	}
	

	public void WriteData() {
	    command = "DATA";
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 354 );
	
	}
	
	public void WriteQuit() {
	    command = "QUIT";
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 221 );
	
	}
		
	public void WriteBoundary( string boundary ) {
	
	    WriteLine( "\r\n--{0}" , boundary );
	
	}
	
	public void WriteFinalBoundary( string boundary ) {
	
	    WriteLine( "\r\n--{0}--" , boundary );
	
	}
	
	// single dot by itself
	public void WriteDataEndTag() {
	    command = "\r\n.";
	    WriteLine( command );
	    ReadResponse();
	    CheckForStatusCode( 250 );
	
	}
	
	
	public void WriteHeader( MailHeader header ) {
	    // write the headers
	    foreach( string key in header.Data.AllKeys )
		WriteLine( "{0}: {1}" , key , header.Data[ key ] );
	    
	    // write the header end tag
	    WriteLine( "" );
	}
	
	public void CheckForStatusCode( int statusCode ) {
	    
	    if( LastResponse.StatusCode != statusCode ) {
		
		string msg = "" + 
		    "Server reponse: '" + lastResponse.RawResponse + "';" +
		    "Status code: '" +  lastResponse.StatusCode + "';" + 
		    "Expected status code: '" + statusCode + "';" + 
		    "Last command: '" + command + "'";
		
		throw new SmtpException( msg ); 
					
	    }
	}
	
	
	// write buffer's bytes to the stream
	public void WriteBytes( byte[] buffer ) {
	    stream.Write( buffer , 0 , buffer.Length );
	}
	
	// writes a formatted line to the server
	public void WriteLine( string format ,  params object[] args ) {
	    WriteLine( String.Format( format , args ) );
	}
	
	// writes a line to the server
	public void WriteLine( string line ) {
	    byte[] buffer = encoding.GetBytes( line + "\r\n" );
	    
	    stream.Write( buffer , 0 , buffer.Length );

		Debug.WriteLine ("smtp: {0}", line);
	}
	
	// read a line from the server
	public void ReadResponse( ) {
		
		byte[] buffer = new byte [512];
		int position = 0;
		bool lastLine = false;

		do {
			int readLength = stream.Read (buffer , position , buffer.Length - position);
			if (readLength > 0) { 
				int available = position + readLength - 1;
				if (available > 4 && (buffer [available] == '\n' || buffer [available] == '\r'))
					for (int index = available - 3; ; index--) {
						if (index < 0 || buffer [index] == '\n' || buffer [index] == '\r') {
							lastLine = buffer [index + 4] == ' ';
							break;
						}
					}

				// move position
				position += readLength;

				// check if buffer is full
				if (position == buffer.Length) {
					byte [] newBuffer = new byte [buffer.Length * 2];
					Array.Copy (buffer, 0, newBuffer, 0, buffer.Length);
					buffer = newBuffer;
				}
			}
		} while(!lastLine);

		string line = encoding.GetString (buffer , 0 , position - 1);
			
	    // parse the line to the lastResponse object
	    lastResponse = SmtpResponse.Parse (line);

		Debug.WriteLine ("smtp: {0}", line);
	}

    }


}
