<?php
/**
 * Created by PhpStorm.
 * User: Terry
 * Date: 8/30/2017
 * Time: 7:24 AM
 *
 * Adapted as PHP Class by Terry SoRelle from Dominic Sayers is_email.php, https://github.com/dominicsayers/isemail
 * Following comments from original source
 *
 * To validate an email address according to RFCs 5321, 5322 and others
 *
 * Copyright © 2008-2016, Dominic Sayers					<br>
 * Test schema documentation Copyright © 2011, Daniel Marschall			<br>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *     - Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     - Neither the name of Dominic Sayers nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @package	is_email
 * @author	Dominic Sayers <dominic@sayers.cc>
 * @copyright	2008-2016 Dominic Sayers
 * @license	https://www.opensource.org/licenses/bsd-license.php BSD License
 * @link	https://www.dominicsayers.com/isemail
 * @version	3.0.7 - Changed my link to https://isemail.info throughout
 */

namespace Tops\mail;


class TSayersEmailValidator
{
    // constants converted from 'define' in original code, by Terry SoRelle
    
    /*:diagnostic constants start:*/
    // This part of the code is generated using data from test/meta.xml. Beware of making manual alterations
    // Categories
    const ISEMAIL_VALID_CATEGORY = 1;
    const ISEMAIL_DNSWARN = 7;
    const ISEMAIL_RFC5321 = 15;
    const ISEMAIL_CFWS = 31;
    const ISEMAIL_DEPREC = 63;
    const ISEMAIL_RFC5322 = 127;
    const ISEMAIL_ERR = 255;

    // Diagnoses
    // Address is valid
    const ISEMAIL_VALID = 0;
    // Address is valid but a DNS check was not successful
    const ISEMAIL_DNSWARN_NO_MX_RECORD = 5;
    const ISEMAIL_DNSWARN_NO_RECORD = 6;
    // Address is valid for SMTP but has unusual elements
    const ISEMAIL_RFC5321_TLD = 9;
    const ISEMAIL_RFC5321_TLDNUMERIC = 10;
    const ISEMAIL_RFC5321_QUOTEDSTRING = 11;
    const ISEMAIL_RFC5321_ADDRESSLITERAL = 12;
    const ISEMAIL_RFC5321_IPV6DEPRECATED = 13;
    // Address is valid within the message but cannot be used unmodified for the envelope
    const ISEMAIL_CFWS_COMMENT = 17;
    const ISEMAIL_CFWS_FWS = 18;
    // Address contains deprecated elements but may still be valid in restricted contexts
    const ISEMAIL_DEPREC_LOCALPART = 33;
    const ISEMAIL_DEPREC_FWS = 34;
    const ISEMAIL_DEPREC_QTEXT = 35;
    const ISEMAIL_DEPREC_QP = 36;
    const ISEMAIL_DEPREC_COMMENT = 37;
    const ISEMAIL_DEPREC_CTEXT = 38;
    const ISEMAIL_DEPREC_CFWS_NEAR_AT = 49;
    // The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid.
    const ISEMAIL_RFC5322_DOMAIN = 65;
    const ISEMAIL_RFC5322_TOOLONG = 66;
    const ISEMAIL_RFC5322_LOCAL_TOOLONG = 67;
    const ISEMAIL_RFC5322_DOMAIN_TOOLONG = 68;
    const ISEMAIL_RFC5322_LABEL_TOOLONG = 69;
    const ISEMAIL_RFC5322_DOMAINLITERAL = 70;
    const ISEMAIL_RFC5322_DOMLIT_OBSDTEXT = 71;
    const ISEMAIL_RFC5322_IPV6_GRPCOUNT = 72;
    const ISEMAIL_RFC5322_IPV6_2X2XCOLON = 73;
    const ISEMAIL_RFC5322_IPV6_BADCHAR = 74;
    const ISEMAIL_RFC5322_IPV6_MAXGRPS = 75;
    const ISEMAIL_RFC5322_IPV6_COLONSTRT = 76;
    const ISEMAIL_RFC5322_IPV6_COLONEND = 77;
    // Address is invalid for any purpose
    const ISEMAIL_ERR_EXPECTING_DTEXT = 129;
    const ISEMAIL_ERR_NOLOCALPART = 130;
    const ISEMAIL_ERR_NODOMAIN = 131;
    const ISEMAIL_ERR_CONSECUTIVEDOTS = 132;
    const ISEMAIL_ERR_ATEXT_AFTER_CFWS = 133;
    const ISEMAIL_ERR_ATEXT_AFTER_QS = 134;
    const ISEMAIL_ERR_ATEXT_AFTER_DOMLIT = 135;
    const ISEMAIL_ERR_EXPECTING_QPAIR = 136;
    const ISEMAIL_ERR_EXPECTING_ATEXT = 137;
    const ISEMAIL_ERR_EXPECTING_QTEXT = 138;
    const ISEMAIL_ERR_EXPECTING_CTEXT = 139;
    const ISEMAIL_ERR_BACKSLASHEND = 140;
    const ISEMAIL_ERR_DOT_START = 141;
    const ISEMAIL_ERR_DOT_END = 142;
    const ISEMAIL_ERR_DOMAINHYPHENSTART = 143;
    const ISEMAIL_ERR_DOMAINHYPHENEND = 144;
    const ISEMAIL_ERR_UNCLOSEDQUOTEDSTR = 145;
    const ISEMAIL_ERR_UNCLOSEDCOMMENT = 146;
    const ISEMAIL_ERR_UNCLOSEDDOMLIT = 147;
    const ISEMAIL_ERR_FWS_CRLF_X2 = 148;
    const ISEMAIL_ERR_FWS_CRLF_END = 149;
    const ISEMAIL_ERR_CR_NO_LF = 150;
    // End of generated code
    /*:diagnostic constants end:*/

    // function control
    const ISEMAIL_THRESHOLD = 16;

	// Email parts
	const ISEMAIL_COMPONENT_LOCALPART = 0;
	const ISEMAIL_COMPONENT_DOMAIN	= 1;
	const ISEMAIL_COMPONENT_LITERAL = 2;
	const ISEMAIL_CONTEXT_COMMENT = 3;
	const ISEMAIL_CONTEXT_FWS = 4;
	const ISEMAIL_CONTEXT_QUOTEDSTRING = 5;
	const ISEMAIL_CONTEXT_QUOTEDPAIR = 6;

	// Miscellaneous string constants
	const ISEMAIL_STRING_AT					= '@';
	const ISEMAIL_STRING_BACKSLASH			= '\\';
	const ISEMAIL_STRING_DOT				= '.';
	const ISEMAIL_STRING_DQUOTE				= '"';
	const ISEMAIL_STRING_OPENPARENTHESIS	= '(';
	const ISEMAIL_STRING_CLOSEPARENTHESIS	= ')';
	const ISEMAIL_STRING_OPENSQBRACKET		= '[';
	const ISEMAIL_STRING_CLOSESQBRACKET		= ']';
	const ISEMAIL_STRING_HYPHEN 			= '-';
	const ISEMAIL_STRING_COLON				= ':';
	const ISEMAIL_STRING_DOUBLECOLON		= '::';
	const ISEMAIL_STRING_SP					= ' ';
	const ISEMAIL_STRING_HTAB				= "\t";
	const ISEMAIL_STRING_CR					= "\r";
	const ISEMAIL_STRING_LF					= "\n";
	const ISEMAIL_STRING_IPV6TAG			= 'IPv6:';
	// US-ASCII visible characters not valid for atext (https://tools.ietf.org/html/rfc5322#section-3.2.3)
	const ISEMAIL_STRING_SPECIALS			= '()<>[]:;@\\,."'; 



    /**
     * @param $email
     * @param bool $checkDNS
     * @param bool $errorlevel
     * @param array $parsedata
     * @return bool|int
     *
     * From original Sayers function is_email
     */
    public static function is_email($email, $checkDNS = false, $errorlevel = false, &$parsedata = array())
    {
        // Check that $email is a valid address. Read the following RFCs to understand the constraints:
        // 	(https://tools.ietf.org/html/rfc5321)
        // 	(https://tools.ietf.org/html/rfc5322)
        // 	(https://tools.ietf.org/html/rfc4291#section-2.2)
        // 	(https://tools.ietf.org/html/rfc1123#section-2.1)
        // 	(https://tools.ietf.org/html/rfc3696) (guidance only)
// version 2.0: Enhance $diagnose parameter to $errorlevel
// version 3.0: Introduced status categories
// revision 3.01: BUG: $parsedata was passed by value instead of by reference

        if (is_bool($errorlevel)) {
            $threshold = self::ISEMAIL_VALID;
            $diagnose = (bool)$errorlevel;
        } else {
            $diagnose = true;

            switch ((int)$errorlevel) {
                case E_WARNING:
                    $threshold = self::ISEMAIL_THRESHOLD;
                    break;    // For backward compatibility
                case E_ERROR:
                    $threshold = self::ISEMAIL_VALID;
                    break;    // For backward compatibility
                default:
                    $threshold = (int)$errorlevel;
            }
        }

        $return_status = array(self::ISEMAIL_VALID);

        // Parse the address into components, character by character
        $raw_length = strlen($email);
        $context = self::ISEMAIL_COMPONENT_LOCALPART;    // Where we are
        $context_stack = array($context);        // Where we have been
        $context_prior = self::ISEMAIL_COMPONENT_LOCALPART;    // Where we just came from
        $token = '';                // The current character
        $token_prior = '';                // The previous character
        $parsedata = array(
            self::ISEMAIL_COMPONENT_LOCALPART => '',
            self::ISEMAIL_COMPONENT_DOMAIN => ''
        );            // For the components of the address

        $atomlist = array(
            self::ISEMAIL_COMPONENT_LOCALPART => array(''),
            self::ISEMAIL_COMPONENT_DOMAIN => array('')
        );            // For the dot-atom elements of the address
        $element_count = 0;
        $element_len = 0;
        $hyphen_flag = false;            // Hyphen cannot occur at the end of a subdomain
        $end_or_die = false;            // CFWS can only appear at the end of the element

//-echo "<table style=\"clear:left;\">"; // debug
        for ($i = 0; $i < $raw_length; $i++) {
            $token = $email[$i];
//-echo "<tr><td><strong>$context|",(($end_or_die) ? 'true' : 'false'),"|$token|" . max($return_status) . "</strong></td>"; // debug

            switch ($context) {
                //-------------------------------------------------------------
                // local-part
                //-------------------------------------------------------------
                case self::ISEMAIL_COMPONENT_LOCALPART:
                    // https://tools.ietf.org/html/rfc5322#section-3.4.1
                    //   local-part      =   dot-atom / quoted-string / obs-local-part
                    //
                    //   dot-atom        =   [CFWS] dot-atom-text [CFWS]
                    //
                    //   dot-atom-text   =   1*atext *("." 1*atext)
                    //
                    //   quoted-string   =   [CFWS]
                    //                       DQUOTE *([FWS] qcontent) [FWS] DQUOTE
                    //                       [CFWS]
                    //
                    //   obs-local-part  =   word *("." word)
                    //
                    //   word            =   atom / quoted-string
                    //
                    //   atom            =   [CFWS] 1*atext [CFWS]
                    switch ($token) {
                        // Comment
                        case self::ISEMAIL_STRING_OPENPARENTHESIS:
                            if ($element_len === 0)
                                // Comments are OK at the beginning of an element
                                $return_status[] = ($element_count === 0) ? self::ISEMAIL_CFWS_COMMENT : self::ISEMAIL_DEPREC_COMMENT;
                            else {
                                $return_status[] = self::ISEMAIL_CFWS_COMMENT;
                                $end_or_die = true;    // We can't start a comment in the middle of an element, so this better be the end
                            }

                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_COMMENT;
                            break;
                        // Next dot-atom element
                        case self::ISEMAIL_STRING_DOT:
                            if ($element_len === 0)
                                // Another dot, already?
                                $return_status[] = ($element_count === 0) ? self::ISEMAIL_ERR_DOT_START : self::ISEMAIL_ERR_CONSECUTIVEDOTS;    // Fatal error
                            else
                                // The entire local-part can be a quoted string for RFC 5321
                                // If it's just one atom that is quoted then it's an RFC 5322 obsolete form
                                if ($end_or_die) $return_status[] = self::ISEMAIL_DEPREC_LOCALPART;

                            $end_or_die = false;    // CFWS & quoted strings are OK again now we're at the beginning of an element (although they are obsolete forms)
                            $element_len = 0;
                            $element_count++;
                            $parsedata[self::ISEMAIL_COMPONENT_LOCALPART] .= $token;
                            $atomlist[self::ISEMAIL_COMPONENT_LOCALPART][$element_count] = '';

                            break;
                        // Quoted string
                        case self::ISEMAIL_STRING_DQUOTE:
                            if ($element_len === 0) {
                                // The entire local-part can be a quoted string for RFC 5321
                                // If it's just one atom that is quoted then it's an RFC 5322 obsolete form
                                $return_status[] = ($element_count === 0) ? self::ISEMAIL_RFC5321_QUOTEDSTRING : self::ISEMAIL_DEPREC_LOCALPART;

                                $parsedata[self::ISEMAIL_COMPONENT_LOCALPART] .= $token;
                                $atomlist[self::ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
                                $element_len++;
                                $end_or_die = true;    // Quoted string must be the entire element
                                $context_stack[] = $context;
                                $context = self::ISEMAIL_CONTEXT_QUOTEDSTRING;
                            } else {
                                $return_status[] = self::ISEMAIL_ERR_EXPECTING_ATEXT;    // Fatal error
                            }

                            break;
                        // Folding White Space
                        case self::ISEMAIL_STRING_CR:
                        case self::ISEMAIL_STRING_SP:
                        case self::ISEMAIL_STRING_HTAB:
                            if (($token === self::ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== self::ISEMAIL_STRING_LF))) {
                                $return_status[] = self::ISEMAIL_ERR_CR_NO_LF;
                                break;
                            }    // Fatal error

                            if ($element_len === 0)
                                $return_status[] = ($element_count === 0) ? self::ISEMAIL_CFWS_FWS : self::ISEMAIL_DEPREC_FWS;
                            else
                                $end_or_die = true;    // We can't start FWS in the middle of an element, so this better be the end

                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_FWS;
                            $token_prior = $token;

                            break;
                        // @
                        case self::ISEMAIL_STRING_AT:
                            // At this point we should have a valid local-part
                            if (count($context_stack) !== 1) die('Unexpected item on context stack');

                            if ($parsedata[self::ISEMAIL_COMPONENT_LOCALPART] === '')
                                $return_status[] = self::ISEMAIL_ERR_NOLOCALPART;    // Fatal error
                            elseif ($element_len === 0) $return_status[] = self::ISEMAIL_ERR_DOT_END;    // Fatal error
                            // https://tools.ietf.org/html/rfc5321#section-4.5.3.1.1
                            //   The maximum total length of a user name or other local-part is 64
                            //   octets.
                            elseif (strlen($parsedata[self::ISEMAIL_COMPONENT_LOCALPART]) > 64)
                                $return_status[] = self::ISEMAIL_RFC5322_LOCAL_TOOLONG;
                            // https://tools.ietf.org/html/rfc5322#section-3.4.1
                            //   Comments and folding white space
                            //   SHOULD NOT be used around the "@" in the addr-spec.
                            //
                            // https://tools.ietf.org/html/rfc2119
                            // 4. SHOULD NOT   This phrase, or the phrase "NOT RECOMMENDED" mean that
                            //    there may exist valid reasons in particular circumstances when the
                            //    particular behavior is acceptable or even useful, but the full
                            //    implications should be understood and the case carefully weighed
                            //    before implementing any behavior described with this label.
                            elseif (($context_prior === self::ISEMAIL_CONTEXT_COMMENT) || ($context_prior === self::ISEMAIL_CONTEXT_FWS))
                                $return_status[] = self::ISEMAIL_DEPREC_CFWS_NEAR_AT;

                            // Clear everything down for the domain parsing
                            $context = self::ISEMAIL_COMPONENT_DOMAIN;    // Where we are
                            $context_stack = array($context);        // Where we have been
                            $element_count = 0;
                            $element_len = 0;
                            $end_or_die = false;            // CFWS can only appear at the end of the element

                            break;
                        // atext
                        default:
                            // https://tools.ietf.org/html/rfc5322#section-3.2.3
                            //    atext           =   ALPHA / DIGIT /    ; Printable US-ASCII
                            //                        "!" / "#" /        ;  characters not including
                            //                        "$" / "%" /        ;  specials.  Used for atoms.
                            //                        "&" / "'" /
                            //                        "*" / "+" /
                            //                        "-" / "/" /
                            //                        "=" / "?" /
                            //                        "^" / "_" /
                            //                        "`" / "{" /
                            //                        "|" / "}" /
                            //                        "~"
                            if ($end_or_die) {
                                // We have encountered atext where it is no longer valid
                                switch ($context_prior) {
                                    case self::ISEMAIL_CONTEXT_COMMENT:
                                    case self::ISEMAIL_CONTEXT_FWS:
                                        $return_status[] = self::ISEMAIL_ERR_ATEXT_AFTER_CFWS;
                                        break;
                                    case self::ISEMAIL_CONTEXT_QUOTEDSTRING:
                                        $return_status[] = self::ISEMAIL_ERR_ATEXT_AFTER_QS;
                                        break;
                                    default:
                                        die ("More atext found where none is allowed, but unrecognised prior context: $context_prior");
                                }
                            } else {
                                $context_prior = $context;
                                $ord = ord($token);

                                if (($ord < 33) || ($ord > 126) || ($ord === 10) || (!is_bool(strpos(self::ISEMAIL_STRING_SPECIALS, $token))))
                                    $return_status[] = self::ISEMAIL_ERR_EXPECTING_ATEXT;    // Fatal error

                                $parsedata[self::ISEMAIL_COMPONENT_LOCALPART] .= $token;
                                $atomlist[self::ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
                                $element_len++;
                            }
                    }

                    break;
                //-------------------------------------------------------------
                // Domain
                //-------------------------------------------------------------
                case self::ISEMAIL_COMPONENT_DOMAIN:
                    // https://tools.ietf.org/html/rfc5322#section-3.4.1
                    //   domain          =   dot-atom / domain-literal / obs-domain
                    //
                    //   dot-atom        =   [CFWS] dot-atom-text [CFWS]
                    //
                    //   dot-atom-text   =   1*atext *("." 1*atext)
                    //
                    //   domain-literal  =   [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
                    //
                    //   dtext           =   %d33-90 /          ; Printable US-ASCII
                    //                       %d94-126 /         ;  characters not including
                    //                       obs-dtext          ;  "[", "]", or "\"
                    //
                    //   obs-domain      =   atom *("." atom)
                    //
                    //   atom            =   [CFWS] 1*atext [CFWS]


                    // https://tools.ietf.org/html/rfc5321#section-4.1.2
                    //   Mailbox        = Local-part "@" ( Domain / address-literal )
                    //
                    //   Domain         = sub-domain *("." sub-domain)
                    //
                    //   address-literal  = "[" ( IPv4-address-literal /
                    //                    IPv6-address-literal /
                    //                    General-address-literal ) "]"
                    //                    ; See Section 4.1.3

                    // https://tools.ietf.org/html/rfc5322#section-3.4.1
                    //      Note: A liberal syntax for the domain portion of addr-spec is
                    //      given here.  However, the domain portion contains addressing
                    //      information specified by and used in other protocols (e.g.,
                    //      [RFC1034], [RFC1035], [RFC1123], [RFC5321]).  It is therefore
                    //      incumbent upon implementations to conform to the syntax of
                    //      addresses for the context in which they are used.
                    // is_email() author's note: it's not clear how to interpret this in
                    // the context of a general email address validator. The conclusion I
                    // have reached is this: "addressing information" must comply with
                    // RFC 5321 (and in turn RFC 1035), anything that is "semantically
                    // invisible" must comply only with RFC 5322.
                    switch ($token) {
                        // Comment
                        case self::ISEMAIL_STRING_OPENPARENTHESIS:
                            if ($element_len === 0)
                                // Comments at the start of the domain are deprecated in the text
                                // Comments at the start of a subdomain are obs-domain
                                // (https://tools.ietf.org/html/rfc5322#section-3.4.1)
                                $return_status[] = ($element_count === 0) ? self::ISEMAIL_DEPREC_CFWS_NEAR_AT : self::ISEMAIL_DEPREC_COMMENT;
                            else {
                                $return_status[] = self::ISEMAIL_CFWS_COMMENT;
                                $end_or_die = true;    // We can't start a comment in the middle of an element, so this better be the end
                            }

                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_COMMENT;
                            break;
                        // Next dot-atom element
                        case self::ISEMAIL_STRING_DOT:
                            if ($element_len === 0)
                                // Another dot, already?
                                $return_status[] = ($element_count === 0) ? self::ISEMAIL_ERR_DOT_START : self::ISEMAIL_ERR_CONSECUTIVEDOTS;    // Fatal error
                            elseif ($hyphen_flag)
                                // Previous subdomain ended in a hyphen
                                $return_status[] = self::ISEMAIL_ERR_DOMAINHYPHENEND;    // Fatal error
                            else
                                // Nowhere in RFC 5321 does it say explicitly that the
                                // domain part of a Mailbox must be a valid domain according
                                // to the DNS standards set out in RFC 1035, but this *is*
                                // implied in several places. For instance, wherever the idea
                                // of host routing is discussed the RFC says that the domain
                                // must be looked up in the DNS. This would be nonsense unless
                                // the domain was designed to be a valid DNS domain. Hence we
                                // must conclude that the RFC 1035 restriction on label length
                                // also applies to RFC 5321 domains.
                                //
                                // https://tools.ietf.org/html/rfc1035#section-2.3.4
                                // labels          63 octets or less
                                if ($element_len > 63) $return_status[] = self::ISEMAIL_RFC5322_LABEL_TOOLONG;

                            $end_or_die = false;    // CFWS is OK again now we're at the beginning of an element (although it may be obsolete CFWS)
                            $element_len = 0;
                            $element_count++;
                            $atomlist[self::ISEMAIL_COMPONENT_DOMAIN][$element_count] = '';
                            $parsedata[self::ISEMAIL_COMPONENT_DOMAIN] .= $token;

                            break;
                        // Domain literal
                        case self::ISEMAIL_STRING_OPENSQBRACKET:
                            if ($parsedata[self::ISEMAIL_COMPONENT_DOMAIN] === '') {
                                $end_or_die = true;    // Domain literal must be the only component
                                $element_len++;
                                $context_stack[] = $context;
                                $context = self::ISEMAIL_COMPONENT_LITERAL;
                                $parsedata[self::ISEMAIL_COMPONENT_DOMAIN] .= $token;
                                $atomlist[self::ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
                                $parsedata[self::ISEMAIL_COMPONENT_LITERAL] = '';
                            } else {
                                $return_status[] = self::ISEMAIL_ERR_EXPECTING_ATEXT;    // Fatal error
                            }

                            break;
                        // Folding White Space
                        case self::ISEMAIL_STRING_CR:
                        case self::ISEMAIL_STRING_SP:
                        case self::ISEMAIL_STRING_HTAB:
                            if (($token === self::ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== self::ISEMAIL_STRING_LF))) {
                                $return_status[] = self::ISEMAIL_ERR_CR_NO_LF;
                                break;
                            }    // Fatal error

                            if ($element_len === 0)
                                $return_status[] = ($element_count === 0) ? self::ISEMAIL_DEPREC_CFWS_NEAR_AT : self::ISEMAIL_DEPREC_FWS;
                            else {
                                $return_status[] = self::ISEMAIL_CFWS_FWS;
                                $end_or_die = true;    // We can't start FWS in the middle of an element, so this better be the end
                            }

                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_FWS;
                            $token_prior = $token;
                            break;
                        // atext
                        default:
                            // RFC 5322 allows any atext...
                            // https://tools.ietf.org/html/rfc5322#section-3.2.3
                            //    atext           =   ALPHA / DIGIT /    ; Printable US-ASCII
                            //                        "!" / "#" /        ;  characters not including
                            //                        "$" / "%" /        ;  specials.  Used for atoms.
                            //                        "&" / "'" /
                            //                        "*" / "+" /
                            //                        "-" / "/" /
                            //                        "=" / "?" /
                            //                        "^" / "_" /
                            //                        "`" / "{" /
                            //                        "|" / "}" /
                            //                        "~"

                            // But RFC 5321 only allows letter-digit-hyphen to comply with DNS rules (RFCs 1034 & 1123)
                            // https://tools.ietf.org/html/rfc5321#section-4.1.2
                            //   sub-domain     = Let-dig [Ldh-str]
                            //
                            //   Let-dig        = ALPHA / DIGIT
                            //
                            //   Ldh-str        = *( ALPHA / DIGIT / "-" ) Let-dig
                            //
                            if ($end_or_die) {
                                // We have encountered atext where it is no longer valid
                                switch ($context_prior) {
                                    case self::ISEMAIL_CONTEXT_COMMENT:
                                    case self::ISEMAIL_CONTEXT_FWS:
                                        $return_status[] = self::ISEMAIL_ERR_ATEXT_AFTER_CFWS;
                                        break;
                                    case self::ISEMAIL_COMPONENT_LITERAL:
                                        $return_status[] = self::ISEMAIL_ERR_ATEXT_AFTER_DOMLIT;
                                        break;
                                    default:
                                        die ("More atext found where none is allowed, but unrecognised prior context: $context_prior");
                                }
                            }

                            $ord = ord($token);
                            $hyphen_flag = false;    // Assume this token isn't a hyphen unless we discover it is

                            if (($ord < 33) || ($ord > 126) || (!is_bool(strpos(self::ISEMAIL_STRING_SPECIALS, $token)))) {
                                $return_status[] = self::ISEMAIL_ERR_EXPECTING_ATEXT;    // Fatal error
                            } elseif ($token === self::ISEMAIL_STRING_HYPHEN) {
                                if ($element_len === 0) {
                                    // Hyphens can't be at the beginning of a subdomain
                                    $return_status[] = self::ISEMAIL_ERR_DOMAINHYPHENSTART;    // Fatal error
                                }

                                $hyphen_flag = true;
                            } elseif (!(($ord > 47 && $ord < 58) || ($ord > 64 && $ord < 91) || ($ord > 96 && $ord < 123))) {
                                // Not an RFC 5321 subdomain, but still OK by RFC 5322
                                $return_status[] = self::ISEMAIL_RFC5322_DOMAIN;
                            }

                            $parsedata[self::ISEMAIL_COMPONENT_DOMAIN] .= $token;
                            $atomlist[self::ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
                            $element_len++;
                    }

                    break;
                //-------------------------------------------------------------
                // Domain literal
                //-------------------------------------------------------------
                case self::ISEMAIL_COMPONENT_LITERAL:
                    // https://tools.ietf.org/html/rfc5322#section-3.4.1
                    //   domain-literal  =   [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
                    //
                    //   dtext           =   %d33-90 /          ; Printable US-ASCII
                    //                       %d94-126 /         ;  characters not including
                    //                       obs-dtext          ;  "[", "]", or "\"
                    //
                    //   obs-dtext       =   obs-NO-WS-CTL / quoted-pair
                    switch ($token) {
                        // End of domain literal
                        case self::ISEMAIL_STRING_CLOSESQBRACKET:
                            if ((int)max($return_status) < self::ISEMAIL_DEPREC) {
                                // Could be a valid RFC 5321 address literal, so let's check

                                // https://tools.ietf.org/html/rfc5321#section-4.1.2
                                //   address-literal  = "[" ( IPv4-address-literal /
                                //                    IPv6-address-literal /
                                //                    General-address-literal ) "]"
                                //                    ; See Section 4.1.3
                                //
                                // https://tools.ietf.org/html/rfc5321#section-4.1.3
                                //   IPv4-address-literal  = Snum 3("."  Snum)
                                //
                                //   IPv6-address-literal  = "IPv6:" IPv6-addr
                                //
                                //   General-address-literal  = Standardized-tag ":" 1*dcontent
                                //
                                //   Standardized-tag  = Ldh-str
                                //                     ; Standardized-tag MUST be specified in a
                                //                     ; Standards-Track RFC and registered with IANA
                                //
                                //   dcontent       = %d33-90 / ; Printable US-ASCII
                                //                  %d94-126 ; excl. "[", "\", "]"
                                //
                                //   Snum           = 1*3DIGIT
                                //                  ; representing a decimal integer
                                //                  ; value in the range 0 through 255
                                //
                                //   IPv6-addr      = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp
                                //
                                //   IPv6-hex       = 1*4HEXDIG
                                //
                                //   IPv6-full      = IPv6-hex 7(":" IPv6-hex)
                                //
                                //   IPv6-comp      = [IPv6-hex *5(":" IPv6-hex)] "::"
                                //                  [IPv6-hex *5(":" IPv6-hex)]
                                //                  ; The "::" represents at least 2 16-bit groups of
                                //                  ; zeros.  No more than 6 groups in addition to the
                                //                  ; "::" may be present.
                                //
                                //   IPv6v4-full    = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal
                                //
                                //   IPv6v4-comp    = [IPv6-hex *3(":" IPv6-hex)] "::"
                                //                  [IPv6-hex *3(":" IPv6-hex) ":"]
                                //                  IPv4-address-literal
                                //                  ; The "::" represents at least 2 16-bit groups of
                                //                  ; zeros.  No more than 4 groups in addition to the
                                //                  ; "::" and IPv4-address-literal may be present.
                                //
                                // is_email() author's note: We can't use ip2long() to validate
                                // IPv4 addresses because it accepts abbreviated addresses
                                // (xxx.xxx.xxx), expanding the last group to complete the address.
                                // filter_var() validates IPv6 address inconsistently (up to PHP 5.3.3
                                // at least) -- see https://bugs.php.net/bug.php?id=53236 for example
                                $max_groups = 8;
                                $matchesIP = array();
                                /*.mixed.*/
                                $index = false;
                                $addressliteral = $parsedata[self::ISEMAIL_COMPONENT_LITERAL];

                                // Extract IPv4 part from the end of the address-literal (if there is one)
                                if (preg_match('/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/', $addressliteral, $matchesIP) > 0) {
                                    $index = strrpos($addressliteral, $matchesIP[0]);
                                    if ($index !== 0) $addressliteral = substr($addressliteral, 0, $index) . '0:0'; // Convert IPv4 part to IPv6 format for further testing
                                }

                                if ($index === 0) {
                                    // Nothing there except a valid IPv4 address, so...
                                    $return_status[] = self::ISEMAIL_RFC5321_ADDRESSLITERAL;
                                } elseif (strncasecmp($addressliteral, self::ISEMAIL_STRING_IPV6TAG, 5) !== 0) {
                                    $return_status[] = self::ISEMAIL_RFC5322_DOMAINLITERAL;
                                } else {
                                    $IPv6 = substr($addressliteral, 5);
                                    $matchesIP = explode(self::ISEMAIL_STRING_COLON, $IPv6);    // Revision 2.7: Daniel Marschall's new IPv6 testing strategy
                                    $groupCount = count($matchesIP);
                                    $index = strpos($IPv6, self::ISEMAIL_STRING_DOUBLECOLON);

                                    if ($index === false) {
                                        // We need exactly the right number of groups
                                        if ($groupCount !== $max_groups)
                                            $return_status[] = self::ISEMAIL_RFC5322_IPV6_GRPCOUNT;
                                    } else {
                                        if ($index !== strrpos($IPv6, self::ISEMAIL_STRING_DOUBLECOLON))
                                            $return_status[] = self::ISEMAIL_RFC5322_IPV6_2X2XCOLON;
                                        else {
                                            if ($index === 0 || $index === (strlen($IPv6) - 2)) $max_groups++;    // RFC 4291 allows :: at the start or end of an address with 7 other groups in addition

                                            if ($groupCount > $max_groups)
                                                $return_status[] = self::ISEMAIL_RFC5322_IPV6_MAXGRPS;
                                            elseif ($groupCount === $max_groups)
                                                $return_status[] = self::ISEMAIL_RFC5321_IPV6DEPRECATED;    // Eliding a single "::"
                                        }
                                    }

                                    // Revision 2.7: Daniel Marschall's new IPv6 testing strategy
                                    if ((substr($IPv6, 0, 1) === self::ISEMAIL_STRING_COLON) && (substr($IPv6, 1, 1) !== self::ISEMAIL_STRING_COLON))
                                        $return_status[] = self::ISEMAIL_RFC5322_IPV6_COLONSTRT;    // Address starts with a single colon
                                    elseif ((substr($IPv6, -1) === self::ISEMAIL_STRING_COLON) && (substr($IPv6, -2, 1) !== self::ISEMAIL_STRING_COLON))
                                        $return_status[] = self::ISEMAIL_RFC5322_IPV6_COLONEND;    // Address ends with a single colon
                                    elseif (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0)
                                        $return_status[] = self::ISEMAIL_RFC5322_IPV6_BADCHAR;    // Check for unmatched characters
                                    else
                                        $return_status[] = self::ISEMAIL_RFC5321_ADDRESSLITERAL;
                                }
                            } else
                                $return_status[] = self::ISEMAIL_RFC5322_DOMAINLITERAL;


                            $parsedata[self::ISEMAIL_COMPONENT_DOMAIN] .= $token;
                            $atomlist[self::ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
                            $element_len++;
                            $context_prior = $context;
                            $context = (int)array_pop($context_stack);
                            break;
                        case self::ISEMAIL_STRING_BACKSLASH:
                            $return_status[] = self::ISEMAIL_RFC5322_DOMLIT_OBSDTEXT;
                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_QUOTEDPAIR;
                            break;
                        // Folding White Space
                        case self::ISEMAIL_STRING_CR:
                        case self::ISEMAIL_STRING_SP:
                        case self::ISEMAIL_STRING_HTAB:
                            if (($token === self::ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== self::ISEMAIL_STRING_LF))) {
                                $return_status[] = self::ISEMAIL_ERR_CR_NO_LF;
                                break;
                            }    // Fatal error

                            $return_status[] = self::ISEMAIL_CFWS_FWS;

                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_FWS;
                            $token_prior = $token;
                            break;
                        // dtext
                        default:
                            // https://tools.ietf.org/html/rfc5322#section-3.4.1
                            //   dtext           =   %d33-90 /          ; Printable US-ASCII
                            //                       %d94-126 /         ;  characters not including
                            //                       obs-dtext          ;  "[", "]", or "\"
                            //
                            //   obs-dtext       =   obs-NO-WS-CTL / quoted-pair
                            //
                            //   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control
                            //                       %d11 /             ;  characters that do not
                            //                       %d12 /             ;  include the carriage
                            //                       %d14-31 /          ;  return, line feed, and
                            //                       %d127              ;  white space characters
                            $ord = ord($token);

                            // CR, LF, SP & HTAB have already been parsed above
                            if (($ord > 127) || ($ord === 0) || ($token === self::ISEMAIL_STRING_OPENSQBRACKET)) {
                                $return_status[] = self::ISEMAIL_ERR_EXPECTING_DTEXT;    // Fatal error
                                break;
                            } elseif (($ord < 33) || ($ord === 127)) {
                                $return_status[] = self::ISEMAIL_RFC5322_DOMLIT_OBSDTEXT;
                            }

                            $parsedata[self::ISEMAIL_COMPONENT_LITERAL] .= $token;
                            $parsedata[self::ISEMAIL_COMPONENT_DOMAIN] .= $token;
                            $atomlist[self::ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
                            $element_len++;
                    }

                    break;
                //-------------------------------------------------------------
                // Quoted string
                //-------------------------------------------------------------
                case self::ISEMAIL_CONTEXT_QUOTEDSTRING:
                    // https://tools.ietf.org/html/rfc5322#section-3.2.4
                    //   quoted-string   =   [CFWS]
                    //                       DQUOTE *([FWS] qcontent) [FWS] DQUOTE
                    //                       [CFWS]
                    //
                    //   qcontent        =   qtext / quoted-pair
                    switch ($token) {
                        // Quoted pair
                        case self::ISEMAIL_STRING_BACKSLASH:
                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_QUOTEDPAIR;
                            break;
                        // Folding White Space
                        // Inside a quoted string, spaces are allowed as regular characters.
                        // It's only FWS if we include HTAB or CRLF
                        case self::ISEMAIL_STRING_CR:
                        case self::ISEMAIL_STRING_HTAB:
                            if (($token === self::ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== self::ISEMAIL_STRING_LF))) {
                                $return_status[] = self::ISEMAIL_ERR_CR_NO_LF;
                                break;
                            }    // Fatal error

                            // https://tools.ietf.org/html/rfc5322#section-3.2.2
                            //   Runs of FWS, comment, or CFWS that occur between lexical tokens in a
                            //   structured header field are semantically interpreted as a single
                            //   space character.

                            // https://tools.ietf.org/html/rfc5322#section-3.2.4
                            //   the CRLF in any FWS/CFWS that appears within the quoted-string [is]
                            //   semantically "invisible" and therefore not part of the quoted-string
                            $parsedata[self::ISEMAIL_COMPONENT_LOCALPART] .= self::ISEMAIL_STRING_SP;
                            $atomlist[self::ISEMAIL_COMPONENT_LOCALPART][$element_count] .= self::ISEMAIL_STRING_SP;
                            $element_len++;

                            $return_status[] = self::ISEMAIL_CFWS_FWS;
                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_FWS;
                            $token_prior = $token;
                            break;
                        // End of quoted string
                        case self::ISEMAIL_STRING_DQUOTE:
                            $parsedata[self::ISEMAIL_COMPONENT_LOCALPART] .= $token;
                            $atomlist[self::ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
                            $element_len++;
                            $context_prior = $context;
                            $context = (int)array_pop($context_stack);
                            break;
                        // qtext
                        default:
                            // https://tools.ietf.org/html/rfc5322#section-3.2.4
                            //   qtext           =   %d33 /             ; Printable US-ASCII
                            //                       %d35-91 /          ;  characters not including
                            //                       %d93-126 /         ;  "\" or the quote character
                            //                       obs-qtext
                            //
                            //   obs-qtext       =   obs-NO-WS-CTL
                            //
                            //   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control
                            //                       %d11 /             ;  characters that do not
                            //                       %d12 /             ;  include the carriage
                            //                       %d14-31 /          ;  return, line feed, and
                            //                       %d127              ;  white space characters
                            $ord = ord($token);

                            if (($ord > 127) || ($ord === 0) || ($ord === 10)) {
                                $return_status[] = self::ISEMAIL_ERR_EXPECTING_QTEXT;    // Fatal error
                            } elseif (($ord < 32) || ($ord === 127))
                                $return_status[] = self::ISEMAIL_DEPREC_QTEXT;

                            $parsedata[self::ISEMAIL_COMPONENT_LOCALPART] .= $token;
                            $atomlist[self::ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
                            $element_len++;
                    }

                    // https://tools.ietf.org/html/rfc5322#section-3.4.1
                    //   If the
                    //   string can be represented as a dot-atom (that is, it contains no
                    //   characters other than atext characters or "." surrounded by atext
                    //   characters), then the dot-atom form SHOULD be used and the quoted-
                    //   string form SHOULD NOT be used.
// To do
                    break;
                //-------------------------------------------------------------
                // Quoted pair
                //-------------------------------------------------------------
                case self::ISEMAIL_CONTEXT_QUOTEDPAIR:
                    // https://tools.ietf.org/html/rfc5322#section-3.2.1
                    //   quoted-pair     =   ("\" (VCHAR / WSP)) / obs-qp
                    //
                    //   VCHAR           =  %d33-126            ; visible (printing) characters
                    //   WSP             =  SP / HTAB           ; white space
                    //
                    //   obs-qp          =   "\" (%d0 / obs-NO-WS-CTL / LF / CR)
                    //
                    //   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control
                    //                       %d11 /             ;  characters that do not
                    //                       %d12 /             ;  include the carriage
                    //                       %d14-31 /          ;  return, line feed, and
                    //                       %d127              ;  white space characters
                    //
                    // i.e. obs-qp       =  "\" (%d0-8, %d10-31 / %d127)
                    $ord = ord($token);

                    if ($ord > 127)
                        $return_status[] = self::ISEMAIL_ERR_EXPECTING_QPAIR;    // Fatal error
                    elseif ((($ord < 31) && ($ord !== 9)) || ($ord === 127))    // SP & HTAB are allowed
                        $return_status[] = self::ISEMAIL_DEPREC_QP;

                    // At this point we know where this qpair occurred so
                    // we could check to see if the character actually
                    // needed to be quoted at all.
                    // https://tools.ietf.org/html/rfc5321#section-4.1.2
                    //   the sending system SHOULD transmit the
                    //   form that uses the minimum quoting possible.
// To do: check whether the character needs to be quoted (escaped) in this context
                    $context_prior = $context;
                    $context = (int)array_pop($context_stack);    // End of qpair
                    $token = self::ISEMAIL_STRING_BACKSLASH . $token;

                    switch ($context) {
                        case self::ISEMAIL_CONTEXT_COMMENT:
                            break;
                        case self::ISEMAIL_CONTEXT_QUOTEDSTRING:
                            $parsedata[self::ISEMAIL_COMPONENT_LOCALPART] .= $token;
                            $atomlist[self::ISEMAIL_COMPONENT_LOCALPART][$element_count] .= $token;
                            $element_len += 2;    // The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash
                            break;
                        case self::ISEMAIL_COMPONENT_LITERAL:
                            $parsedata[self::ISEMAIL_COMPONENT_DOMAIN] .= $token;
                            $atomlist[self::ISEMAIL_COMPONENT_DOMAIN][$element_count] .= $token;
                            $element_len += 2;    // The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash
                            break;
                        default:
                            die("Quoted pair logic invoked in an invalid context: $context");
                    }

                    break;
                //-------------------------------------------------------------
                // Comment
                //-------------------------------------------------------------
                case self::ISEMAIL_CONTEXT_COMMENT:
                    // https://tools.ietf.org/html/rfc5322#section-3.2.2
                    //   comment         =   "(" *([FWS] ccontent) [FWS] ")"
                    //
                    //   ccontent        =   ctext / quoted-pair / comment
                    switch ($token) {
                        // Nested comment
                        case self::ISEMAIL_STRING_OPENPARENTHESIS:
                            // Nested comments are OK
                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_COMMENT;
                            break;
                        // End of comment
                        case self::ISEMAIL_STRING_CLOSEPARENTHESIS:
                            $context_prior = $context;
                            $context = (int)array_pop($context_stack);

                            // https://tools.ietf.org/html/rfc5322#section-3.2.2
                            //   Runs of FWS, comment, or CFWS that occur between lexical tokens in a
                            //   structured header field are semantically interpreted as a single
                            //   space character.
                            //
                            // is_email() author's note: This *cannot* mean that we must add a
                            // space to the address wherever CFWS appears. This would result in
                            // any addr-spec that had CFWS outside a quoted string being invalid
                            // for RFC 5321.
//				if (($context === self::ISEMAIL_COMPONENT_LOCALPART) || ($context === self::ISEMAIL_COMPONENT_DOMAIN)) {
//					$parsedata[$context]			.= self::ISEMAIL_STRING_SP;
//					$atomlist[$context][$element_count]	.= self::ISEMAIL_STRING_SP;
//					$element_len++;
//				}

                            break;
                        // Quoted pair
                        case self::ISEMAIL_STRING_BACKSLASH:
                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_QUOTEDPAIR;
                            break;
                        // Folding White Space
                        case self::ISEMAIL_STRING_CR:
                        case self::ISEMAIL_STRING_SP:
                        case self::ISEMAIL_STRING_HTAB:
                            if (($token === self::ISEMAIL_STRING_CR) && ((++$i === $raw_length) || ($email[$i] !== self::ISEMAIL_STRING_LF))) {
                                $return_status[] = self::ISEMAIL_ERR_CR_NO_LF;
                                break;
                            }    // Fatal error

                            $return_status[] = self::ISEMAIL_CFWS_FWS;

                            $context_stack[] = $context;
                            $context = self::ISEMAIL_CONTEXT_FWS;
                            $token_prior = $token;
                            break;
                        // ctext
                        default:
                            // https://tools.ietf.org/html/rfc5322#section-3.2.3
                            //   ctext           =   %d33-39 /          ; Printable US-ASCII
                            //                       %d42-91 /          ;  characters not including
                            //                       %d93-126 /         ;  "(", ")", or "\"
                            //                       obs-ctext
                            //
                            //   obs-ctext       =   obs-NO-WS-CTL
                            //
                            //   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control
                            //                       %d11 /             ;  characters that do not
                            //                       %d12 /             ;  include the carriage
                            //                       %d14-31 /          ;  return, line feed, and
                            //                       %d127              ;  white space characters
                            $ord = ord($token);

                            if (($ord > 127) || ($ord === 0) || ($ord === 10)) {
                                $return_status[] = self::ISEMAIL_ERR_EXPECTING_CTEXT;    // Fatal error
                                break;
                            } elseif (($ord < 32) || ($ord === 127)) {
                                $return_status[] = self::ISEMAIL_DEPREC_CTEXT;
                            }
                    }

                    break;
                //-------------------------------------------------------------
                // Folding White Space
                //-------------------------------------------------------------
                case self::ISEMAIL_CONTEXT_FWS:
                    // https://tools.ietf.org/html/rfc5322#section-3.2.2
                    //   FWS             =   ([*WSP CRLF] 1*WSP) /  obs-FWS
                    //                                          ; Folding white space

                    // But note the erratum:
                    // https://www.rfc-editor.org/errata_search.php?rfc=5322&eid=1908:
                    //   In the obsolete syntax, any amount of folding white space MAY be
                    //   inserted where the obs-FWS rule is allowed.  This creates the
                    //   possibility of having two consecutive "folds" in a line, and
                    //   therefore the possibility that a line which makes up a folded header
                    //   field could be composed entirely of white space.
                    //
                    //   obs-FWS         =   1*([CRLF] WSP)
                    if ($token_prior === self::ISEMAIL_STRING_CR) {
                        if ($token === self::ISEMAIL_STRING_CR) {
                            $return_status[] = self::ISEMAIL_ERR_FWS_CRLF_X2;    // Fatal error
                            break;
                        }

                        if (isset($crlf_count)) {
                            if (++$crlf_count > 1)
                                $return_status[] = self::ISEMAIL_DEPREC_FWS;    // Multiple folds = obsolete FWS
                        } else $crlf_count = 1;
                    }

                    switch ($token) {
                        case self::ISEMAIL_STRING_CR:
                            if ((++$i === $raw_length) || ($email[$i] !== self::ISEMAIL_STRING_LF))
                                $return_status[] = self::ISEMAIL_ERR_CR_NO_LF;    // Fatal error

                            break;
                        case self::ISEMAIL_STRING_SP:
                        case self::ISEMAIL_STRING_HTAB:
                            break;
                        default:
                            if ($token_prior === self::ISEMAIL_STRING_CR) {
                                $return_status[] = self::ISEMAIL_ERR_FWS_CRLF_END;    // Fatal error
                                break;
                            }

                            if (isset($crlf_count)) unset($crlf_count);

                            $context_prior = $context;
                            $context = (int)array_pop($context_stack);    // End of FWS

                            // https://tools.ietf.org/html/rfc5322#section-3.2.2
                            //   Runs of FWS, comment, or CFWS that occur between lexical tokens in a
                            //   structured header field are semantically interpreted as a single
                            //   space character.
                            //
                            // is_email() author's note: This *cannot* mean that we must add a
                            // space to the address wherever CFWS appears. This would result in
                            // any addr-spec that had CFWS outside a quoted string being invalid
                            // for RFC 5321.
//				if (($context === self::ISEMAIL_COMPONENT_LOCALPART) || ($context === self::ISEMAIL_COMPONENT_DOMAIN)) {
//					$parsedata[$context]			.= self::ISEMAIL_STRING_SP;
//					$atomlist[$context][$element_count]	.= self::ISEMAIL_STRING_SP;
//					$element_len++;
//				}

                            $i--;    // Look at this token again in the parent context
                    }

                    $token_prior = $token;
                    break;
                //-------------------------------------------------------------
                // A context we aren't expecting
                //-------------------------------------------------------------
                default:
                    die("Unknown context: $context");
            }

//-echo "<td>$context|",(($end_or_die) ? 'true' : 'false'),"|$token|" . max($return_status) . "</td></tr>"; // debug
            if ((int)max($return_status) > self::ISEMAIL_RFC5322) break;    // No point going on if we've got a fatal error
        }

        // Some simple final tests
        if ((int)max($return_status) < self::ISEMAIL_RFC5322) {
            if ($context === self::ISEMAIL_CONTEXT_QUOTEDSTRING) $return_status[] = self::ISEMAIL_ERR_UNCLOSEDQUOTEDSTR;    // Fatal error
            elseif ($context === self::ISEMAIL_CONTEXT_QUOTEDPAIR) $return_status[] = self::ISEMAIL_ERR_BACKSLASHEND;        // Fatal error
            elseif ($context === self::ISEMAIL_CONTEXT_COMMENT) $return_status[] = self::ISEMAIL_ERR_UNCLOSEDCOMMENT;        // Fatal error
            elseif ($context === self::ISEMAIL_COMPONENT_LITERAL) $return_status[] = self::ISEMAIL_ERR_UNCLOSEDDOMLIT;        // Fatal error
            elseif ($token === self::ISEMAIL_STRING_CR) $return_status[] = self::ISEMAIL_ERR_FWS_CRLF_END;        // Fatal error
            elseif ($parsedata[self::ISEMAIL_COMPONENT_DOMAIN] === '') $return_status[] = self::ISEMAIL_ERR_NODOMAIN;            // Fatal error
            elseif ($element_len === 0) $return_status[] = self::ISEMAIL_ERR_DOT_END;            // Fatal error
            elseif ($hyphen_flag) $return_status[] = self::ISEMAIL_ERR_DOMAINHYPHENEND;        // Fatal error
            // https://tools.ietf.org/html/rfc5321#section-4.5.3.1.2
            //   The maximum total length of a domain name or number is 255 octets.
            elseif (strlen($parsedata[self::ISEMAIL_COMPONENT_DOMAIN]) > 255)
                $return_status[] = self::ISEMAIL_RFC5322_DOMAIN_TOOLONG;
            // https://tools.ietf.org/html/rfc5321#section-4.1.2
            //   Forward-path   = Path
            //
            //   Path           = "<" [ A-d-l ":" ] Mailbox ">"
            //
            // https://tools.ietf.org/html/rfc5321#section-4.5.3.1.3
            //   The maximum total length of a reverse-path or forward-path is 256
            //   octets (including the punctuation and element separators).
            //
            // Thus, even without (obsolete) routing information, the Mailbox can
            // only be 254 characters long. This is confirmed by this verified
            // erratum to RFC 3696:
            //
            // https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
            //   However, there is a restriction in RFC 2821 on the length of an
            //   address in MAIL and RCPT commands of 254 characters.  Since addresses
            //   that do not fit in those fields are not normally useful, the upper
            //   limit on address lengths should normally be considered to be 254.
            elseif (strlen($parsedata[self::ISEMAIL_COMPONENT_LOCALPART] . self::ISEMAIL_STRING_AT . $parsedata[self::ISEMAIL_COMPONENT_DOMAIN]) > 254)
                $return_status[] = self::ISEMAIL_RFC5322_TOOLONG;
            // https://tools.ietf.org/html/rfc1035#section-2.3.4
            // labels          63 octets or less
            elseif ($element_len > 63) $return_status[] = self::ISEMAIL_RFC5322_LABEL_TOOLONG;
        }

        // Check DNS?
        $dns_checked = false;

        if ($checkDNS && ((int)max($return_status) < self::ISEMAIL_DNSWARN) && function_exists('dns_get_record')) {
            // https://tools.ietf.org/html/rfc5321#section-2.3.5
            //   Names that can
            //   be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
            //   in Section 5) are permitted, as are CNAME RRs whose targets can be
            //   resolved, in turn, to MX or address RRs.
            //
            // https://tools.ietf.org/html/rfc5321#section-5.1
            //   The lookup first attempts to locate an MX record associated with the
            //   name.  If a CNAME record is found, the resulting name is processed as
            //   if it were the initial name. ... If an empty list of MXs is returned,
            //   the address is treated as if it was associated with an implicit MX
            //   RR, with a preference of 0, pointing to that host.
            //
            // is_email() author's note: We will regard the existence of a CNAME to be
            // sufficient evidence of the domain's existence. For performance reasons
            // we will not repeat the DNS lookup for the CNAME's target, but we will
            // raise a warning because we didn't immediately find an MX record.
            if ($element_count === 0) $parsedata[self::ISEMAIL_COMPONENT_DOMAIN] .= '.';        // Checking TLD DNS seems to work only if you explicitly check from the root

            $result = @dns_get_record($parsedata[self::ISEMAIL_COMPONENT_DOMAIN], DNS_MX);    // Not using checkdnsrr because of a suspected bug in PHP 5.3 (https://bugs.php.net/bug.php?id=51844)

            if ((is_bool($result) && !(bool)$result))
                $return_status[] = self::ISEMAIL_DNSWARN_NO_RECORD;            // Domain can't be found in DNS
            else {
                if (count($result) === 0) {
                    $return_status[] = self::ISEMAIL_DNSWARN_NO_MX_RECORD;        // MX-record for domain can't be found
                    $result = @dns_get_record($parsedata[self::ISEMAIL_COMPONENT_DOMAIN], DNS_A + DNS_CNAME);

                    if (count($result) === 0)
                        $return_status[] = self::ISEMAIL_DNSWARN_NO_RECORD;        // No usable records for the domain can be found
                } else $dns_checked = true;
            }
        }

        // Check for TLD addresses
        // -----------------------
        // TLD addresses are specifically allowed in RFC 5321 but they are
        // unusual to say the least. We will allocate a separate
        // status to these addresses on the basis that they are more likely
        // to be typos than genuine addresses (unless we've already
        // established that the domain does have an MX record)
        //
        // https://tools.ietf.org/html/rfc5321#section-2.3.5
        //   In the case
        //   of a top-level domain used by itself in an email address, a single
        //   string is used without any dots.  This makes the requirement,
        //   described in more detail below, that only fully-qualified domain
        //   names appear in SMTP transactions on the public Internet,
        //   particularly important where top-level domains are involved.
        //
        // TLD format
        // ----------
        // The format of TLDs has changed a number of times. The standards
        // used by IANA have been largely ignored by ICANN, leading to
        // confusion over the standards being followed. These are not defined
        // anywhere, except as a general component of a DNS host name (a label).
        // However, this could potentially lead to 123.123.123.123 being a
        // valid DNS name (rather than an IP address) and thereby creating
        // an ambiguity. The most authoritative statement on TLD formats that
        // the author can find is in a (rejected!) erratum to RFC 1123
        // submitted by John Klensin, the author of RFC 5321:
        //
        // https://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353
        //   However, a valid host name can never have the dotted-decimal
        //   form #.#.#.#, since this change does not permit the highest-level
        //   component label to start with a digit even if it is not all-numeric.
        if (!$dns_checked && ((int)max($return_status) < self::ISEMAIL_DNSWARN)) {
            if ($element_count === 0) $return_status[] = self::ISEMAIL_RFC5321_TLD;

            if (is_numeric($atomlist[self::ISEMAIL_COMPONENT_DOMAIN][$element_count][0]))
                $return_status[] = self::ISEMAIL_RFC5321_TLDNUMERIC;
        }

        $return_status = array_unique($return_status);
        $final_status = (int)max($return_status);

        if (count($return_status) !== 1) array_shift($return_status); // remove redundant self::ISEMAIL_VALID

        $parsedata['status'] = $return_status;

        if ($final_status < $threshold) $final_status = self::ISEMAIL_VALID;

        return ($diagnose) ? $final_status : ($final_status < self::ISEMAIL_THRESHOLD);
    }

}