package railo.runtime.net.mail;

import java.io.UnsupportedEncodingException;
import java.net.IDN;
import java.util.ArrayList;
import java.util.Iterator;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeUtility;

import railo.commons.lang.StringUtil;
import railo.runtime.exp.PageException;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
import railo.runtime.type.Array;
import railo.runtime.type.Struct;
import railo.runtime.type.util.ListUtil;

public final class MailUtil {

	public static String encode(String text,String encoding) throws UnsupportedEncodingException {
		//print.ln(StringUtil.changeCharset(text,encoding));
		return MimeUtility.encodeText(text,encoding,"Q");
	}
	
	public static String decode(String text) throws UnsupportedEncodingException {
		return MimeUtility.decodeText(text);
	}



    public static InternetAddress toInternetAddress(Object emails) throws MailException, UnsupportedEncodingException, PageException {

        if ( emails instanceof String )
            return parseEmail( emails );

        InternetAddress[] addresses = toInternetAddresses( emails );
        if ( addresses != null && addresses.length > 0 )
            return addresses[0];

        return null;
    }


    public static InternetAddress[] toInternetAddresses(Object emails) throws MailException, UnsupportedEncodingException, PageException {

        if (emails instanceof String )
            return fromList((String) emails);
        
        else if ( Decision.isArray(emails) ) 
            return fromArray(Caster.toArray(emails));
        
        else if ( Decision.isStruct(emails) ) 
            return new InternetAddress[]{ fromStruct(Caster.toStruct(emails)) };
        
        else
            throw new MailException("e-mail defintions must be one of the following types [string,array,struct], not ["+emails.getClass().getName()+"]");
    }


    private static InternetAddress[] fromArray(Array array) throws MailException, PageException, UnsupportedEncodingException {

        Iterator it = array.valueIterator();
        Object el;
        ArrayList<InternetAddress> pairs = new ArrayList();

        while(it.hasNext()){
            el=it.next();
            if ( Decision.isStruct( el ) ) {

                pairs.add( fromStruct(Caster.toStruct(el)) );
            }
            else {

                InternetAddress addr = parseEmail( Caster.toString(el) );
                if ( addr != null )
                    pairs.add( addr );
            }
        }

        return pairs.toArray( new InternetAddress[ pairs.size() ] );
    }


    private static InternetAddress fromStruct( Struct sct ) throws MailException, UnsupportedEncodingException {

        String name = Caster.toString(sct.get("label",null),null);
        if ( name == null )
            name=Caster.toString(sct.get("name",null),null);

        String email = Caster.toString(sct.get("email",null),null);
        if ( email == null )
            email = Caster.toString(sct.get("e-mail",null),null);
        if ( email == null )
            email = Caster.toString(sct.get("mail",null),null);

        if( StringUtil.isEmpty(email) )
            throw new MailException("missing e-mail definition in struct");

        if(name==null) name="";

        return new InternetAddress( email, name );
    }


    private static InternetAddress[] fromList( String strEmails ) {

        if ( StringUtil.isEmpty( strEmails, true ) )
            return new InternetAddress[0];

        Array raw = ListUtil.listWithQuotesToArray(strEmails, ",;", "\"");

        Iterator<Object> it = raw.valueIterator();
        ArrayList<InternetAddress> al = new ArrayList();

        while( it.hasNext() ) {

            InternetAddress addr = parseEmail( it.next() );

            if( addr != null )
                al.add( addr );
        }

        return al.toArray( new InternetAddress[ al.size() ] );
    }


    /**
     * returns true if the passed value is a in valid email address format
     * @param value
     * @return
     */
    public static boolean isValidEmail( Object value ) {

        InternetAddress addr = parseEmail( value );

        if ( addr != null ) {

	        String address = addr.getAddress();

	        if ( address.contains( ".." ) )
		        return false;

	        int pos = address.indexOf( '@' );

	        if ( pos < 1 || pos == address.length() - 1 )
		        return false;

	        String local  = address.substring(0, pos);
	        String domain = address.substring(pos + 1);

	        if ( domain.charAt( 0 ) == '.'
			  || local.charAt(  0 ) == '.'
			  || local.charAt( local.length() - 1 ) == '.' )
		        return false;

	        pos = domain.lastIndexOf( '.' );

	        if ( pos > 0 && pos < domain.length() - 2 )         // test TLD to be at least 2 chars all alpha characters
	            return StringUtil.isAllAlpha( domain.substring( pos + 1 ) );
        }

        return false;
    }


    /**
     * returns an InternetAddress object or null if the parsing fails.  to be be used in multiple places.
     * @param value
     * @return
     */
    public static InternetAddress parseEmail( Object value ) {

        String str = Caster.toString( value, "" );

        if ( str.indexOf( '@' ) > -1 ) {

            try {

                InternetAddress addr = new InternetAddress( str );

                fixIDN( addr );
                return addr;
            }
            catch ( AddressException ex ) {}
        }

        return null;
    }


    /**
     * converts IDN to ASCII if needed
     * @param addr
     */
    public static void fixIDN( InternetAddress addr ) {

        String address = addr.getAddress();
        int pos = address.indexOf( '@' );

        if ( pos > 0 && pos < address.length() - 1 ) {

            String domain = address.substring( pos + 1 );

            if ( !StringUtil.isAscii( domain ) ) {

                domain = IDN.toASCII( domain );
                addr.setAddress( address.substring( 0, pos ) + "@" + domain );
            }
        }
    }

}
