/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */
package org.jvnet.glassfish.comms.clb.core.common.chr.dcr;

import org.jvnet.glassfish.comms.util.LogUtil;

import java.util.StringTokenizer;
import java.util.logging.Level;


/**
 * Analogous to StringTokenizer, will break the string where address boundaries
 * are according to SIP semantics. Note however that the returned value is an
 * object that wraps the string and a classification code (0: incomplete, 1:
 * naked URI, 2: name-addr).
 */
class AddressTokenizer {
    private static LogUtil logger = new LogUtil(LogUtil.CLB_LOG_DOMAIN);
    private StringTokenizer st;
    private StringBuilder sb = new StringBuilder(50);

    AddressTokenizer(String s) {
        if (s == null) {
            s = "";
        }

        st = new StringTokenizer(s, ",");
    }

    /**
     * Test whether the token in the StringBuilder is a complete URI. It may be
     * a partial URI, if the StringTokenizer broke off where there is a legally
     * embedded comma within the URI.
     *
     * @return 0: not complete, 1: complete addr-spec, 2: complete name-addr
     */
    private int isComplete() {
        Scanstate state = Scanstate.WHITESPACE;
        Scanstate surroundingState = null;
        int p = 0;
        char c;

        while (true) {
            if (p >= sb.length()) {
                switch (state) {
                case WHITESPACE:
                case SCHEME_OR_DISPLAYNAME:
                case ANGLED_STRING:
                case QUOTED_STRING:
                case QUOTED_PAIR:

                    if (logger.isLoggable(Level.FINE)) {
                        logger.logMsg(Level.FINE, "NOT COMPLETE: " + sb);
                    }

                    return 0;

                case PASSWORD_OR_PORT:
                case HOST_AND_MORE:
                case USER_OR_HOST:

                    if (logger.isLoggable(Level.FINE)) {
                        logger.logMsg(Level.FINE, "URI is complete: " + sb);
                    }

                    return 1;

                case BEYOND_NAME_ADDR:

                    if (logger.isLoggable(Level.FINE)) {
                        logger.logMsg(Level.FINE, "name-addr is complete: " +
                            sb);
                    }

                    return 2;
                }
            }

            c = sb.charAt(p);

            switch (state) {
            case WHITESPACE:

                if (!Character.isWhitespace(c)) {
                    switch (c) {
                    case '"':
                        surroundingState = state;
                        state = newstate(Scanstate.QUOTED_STRING, p);

                        break;

                    case '<':
                        state = newstate(Scanstate.ANGLED_STRING, p);

                        break;

                    default:
                        state = newstate(Scanstate.SCHEME_OR_DISPLAYNAME, p);
                    }
                }

                break;

            case QUOTED_STRING:

                switch (c) {
                case '"':
                    state = newstate(surroundingState, p);

                    break;

                case '\\':
                    state = newstate(Scanstate.QUOTED_PAIR, p);

                    break;
                }

                break;

            case QUOTED_PAIR:
                state = newstate(Scanstate.QUOTED_STRING, p);

                break;

            case ANGLED_STRING:

                switch (c) {
                case '>':
                    state = newstate(Scanstate.BEYOND_NAME_ADDR, p);

                    break;
                }

                break;

            case BEYOND_NAME_ADDR:
                break;

            case SCHEME_OR_DISPLAYNAME:

                switch (c) {
                case ':':
                    state = newstate(Scanstate.USER_OR_HOST, p);

                    break;

                case '<':
                    state = newstate(Scanstate.ANGLED_STRING, p);

                    break;
                }

                break;

            case USER_OR_HOST:

                switch (c) {
                case '@':
                    state = newstate(Scanstate.HOST_AND_MORE, p);

                    break;

                case ':':
                    state = newstate(Scanstate.PASSWORD_OR_PORT, p);

                    break;
                }

                break;

            case PASSWORD_OR_PORT:

                switch (c) {
                case '@':
                    state = newstate(Scanstate.HOST_AND_MORE, p);

                    break;
                }

                break;

            case HOST_AND_MORE:
                break;
            }

            p++;
        }
    }

    /**
     * Log scanner state changes.
     *
     * @param x new scanstate
     * @param p position in StringBuilder
     * @return x is returned
     */
    private Scanstate newstate(Scanstate x, int p) {
        if (logger.isLoggable(Level.FINE)) {
            logger.logMsg(Level.FINE, "pos: " + p + ", state <- " + x);
        }

        return x;
    }

    boolean hasMoreTokens() {
        return st.hasMoreTokens();
    }

    Token nextToken() {
        while (true) {
            if (!st.hasMoreTokens()) {
                Token result = new Token(sb.toString(), 0);
                sb.setLength(0);

                return result;
            } else {
                if (sb.length() > 0) {
                    sb.append(',');
                }

                sb.append(st.nextToken());

                int code = isComplete();

                if (code > 0) {
                    Token result = new Token(sb.toString(), code);
                    sb.setLength(0);

                    return result;
                }
            }
        }
    }

    class Token {
        String token;
        int code;

        Token(String t, int x) {
            token = t;
            code = x;
        }
    }
    private enum Scanstate {WHITESPACE,
        QUOTED_STRING,
        QUOTED_PAIR,
        ANGLED_STRING,
        SCHEME_OR_DISPLAYNAME,
        PASSWORD_OR_PORT,
        USER_OR_HOST,
        HOST_AND_MORE,
        BEYOND_NAME_ADDR;
    }
}
