/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *  
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License. 
 *  
 */
package org.apache.directory.studio.openldap.config.model.overlay;


import java.text.ParseException;

import org.apache.directory.api.util.Position;
import org.apache.directory.api.util.Strings;


/**
 * This class represents 'olcRwmMap' value.
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class OlcRwmMapValue
{
    /** The constant string for '*' */
    private static final String STAR_STRING = "*";

    /** The type */
    private OlcRwmMapValueTypeEnum type;

    /** The local name */
    private String localName;

    /** The foreign name */
    private String foreignName;


    /**
     * Gets the type.
     *
     * @return the type
     */
    public OlcRwmMapValueTypeEnum getType()
    {
        return type;
    }


    /**
     * Gets the local name.
     *
     * @return the local name
     */
    public String getLocalName()
    {
        return localName;
    }


    /**
     * Gets the foreign name.
     *
     * @return the foreign name
     */
    public String getForeignName()
    {
        return foreignName;
    }


    /**
     * Indicates if the local name is the '*' constant.
     *
     * @return <code>true</code> if the local name is the '*' constant,
     *         <code>false</code> if not.
     */
    public boolean isLocalNameStart()
    {
        return STAR_STRING.equals( localName );
    }


    /**
     * Indicates if the foreign name is the '*' constant.
     *
     * @return <code>true</code> if the foreign name is the '*' constant,
     *         <code>false</code> if not.
     */
    public boolean isLocalForeignStart()
    {
        return STAR_STRING.equals( foreignName );
    }


    /**
     * Sets the type.
     *
     * @param type the type
     */
    public void setType( OlcRwmMapValueTypeEnum type )
    {
        this.type = type;
    }


    /**
     * Sets the local name.
     *
     * @param localName the local name
     */
    public void setLocalName( String localName )
    {
        this.localName = localName;
    }


    /**
     * Sets the foreign name.
     *
     * @param foreignName the foreign name
     */
    public void setForeignName( String foreignName )
    {
        this.foreignName = foreignName;
    }


    /**
     * {@inheritDoc}
     */
    public String toString()
    {
        StringBuilder sb = new StringBuilder();

        // Type
        sb.append( type );

        // Local Name
        if ( ( localName != null ) && ( localName.length() > 0 ) )
        {
            sb.append( ' ' );
            sb.append( localName );
        }

        // Foreign Name
        if ( ( foreignName != null ) && ( foreignName.length() > 0 ) )
        {
            sb.append( ' ' );
            sb.append( foreignName );
        }

        return sb.toString();
    }


    /**
     * Parses a OlcValSortValue value.
     * 
     * @param s
     *            the string to be parsed
     * @return the associated OlcValSortValue object
     * @throws ParseException
     *             if there are any recognition errors (bad syntax)
     */
    public static synchronized OlcRwmMapValue parse( String s ) throws ParseException
    {
        if ( s == null )
        {
            return null;
        }

        // Trimming the value
        s = Strings.trim( s );

        // Getting the chars of the string
        char[] chars = new char[s.length()];
        s.getChars( 0, s.length(), chars, 0 );

        // Creating the position
        Position pos = new Position();
        pos.start = 0;
        pos.end = 0;
        pos.length = chars.length;

        return parseInternal( chars, pos );
    }


    /**
     * Parses the given string.
     *
     * @param chars the characters
     * @param pos the position
     * @return the associated OlcValSortValue object
     * @throws ParseException
     */
    private static OlcRwmMapValue parseInternal( char[] chars, Position pos ) throws ParseException
    {
        OlcRwmMapValue value = new OlcRwmMapValue();

        // Empty (trimmed) string?
        if ( chars.length == 0 )
        {
            return null;
        }

        do
        {
            // Type
            String typeString = getQuotedOrNotQuotedOptionValue( chars, pos );

            if ( ( typeString != null ) && ( !typeString.isEmpty() ) )
            {
                OlcRwmMapValueTypeEnum type = OlcRwmMapValueTypeEnum.fromString( typeString );

                if ( type != null )
                {
                    value.setType( type );
                }
                else
                {
                    throw new ParseException( "Could not identify keyword '" + typeString
                        + "' as a valid type.", pos.start );
                }
            }
            else
            {
                throw new ParseException( "Could not find the 'type' value", pos.start );
            }

            // First Name
            String firstName = getQuotedOrNotQuotedOptionValue( chars, pos );

            if ( ( firstName == null ) || ( firstName.isEmpty() ) )
            {
                throw new ParseException( "Could not find any 'localName' or 'foreignName' value", pos.start );
            }

            // Second Name
            String secondName = getQuotedOrNotQuotedOptionValue( chars, pos );

            if ( ( secondName == null ) || ( secondName.isEmpty() ) )
            {
                // Local Name is optional
                // If we got only one name, it's the foreign name
                value.setForeignName( firstName );
            }
            else
            {
                value.setLocalName( firstName );
                value.setForeignName( secondName );
            }
        }
        while ( ( pos.start != pos.length ) && ( ( Strings.charAt( chars, pos.start ) ) != '\0' ) );

        return value;
    }


    private static String getQuotedOrNotQuotedOptionValue( char[] chars, Position pos ) throws ParseException
    {
        if ( pos.start != pos.length )
        {
            char quoteChar = '\0';
            boolean isInQuotes = false;
            char c = Strings.charAt( chars, pos.start );
            char[] v = new char[chars.length - pos.start];
            int current = 0;

            do
            {
                if ( ( current == 0 ) && !isInQuotes )
                {
                    // Whitespace
                    if ( Character.isWhitespace( c ) )
                    {
                        // We ignore all whitespaces until we find the start of the value
                        pos.start++;
                        continue;
                    }
                    // Double quotes (") or single quotes (')
                    else if ( ( c == '"' ) || ( c == '\'' ) )
                    {
                        isInQuotes = true;
                        quoteChar = c;
                        pos.start++;
                        continue;
                    }
                    // Any other char is part of a value
                    else
                    {
                        v[current++] = c;
                        pos.start++;
                    }
                }
                else
                {
                    if ( isInQuotes )
                    {
                        // Double quotes (") or single quotes (')
                        if ( quoteChar == c )
                        {
                            isInQuotes = false;
                            pos.start++;
                            continue;
                        }
                        // Checking for escaped quotes
                        else if ( c == '\\' )
                        {
                            // Double quotes (")
                            if ( ( quoteChar == '"' ) && ( Strings.areEquals( chars, pos.start, "\\\"" ) >= 0 ) )
                            {
                                v[current++] = '"';
                                pos.start += 2;
                                continue;
                            }
                            // Single quotes (')
                            else if ( ( quoteChar == '\'' ) && ( Strings.areEquals( chars, pos.start, "\\'" ) >= 0 ) )
                            {
                                v[current++] = '\'';
                                pos.start += 2;
                                continue;
                            }
                        }
                        // Any other char is part of a value
                        else
                        {
                            v[current++] = c;
                            pos.start++;
                        }
                    }
                    else
                    {
                        // Whitespace
                        if ( Character.isWhitespace( c ) )
                        {
                            // Once we have found the start of the value, the first whitespace is the exit
                            break;
                        }
                        // Any other char is part of a value
                        else
                        {
                            v[current++] = c;
                            pos.start++;
                        }
                    }
                }
            }
            while ( ( c = Strings.charAt( chars, pos.start ) ) != '\0' );

            // Checking the resulting value
            if ( current == 0 )
            {
                throw new ParseException( "Couldn't find a value.", pos.start );
            }

            char[] value = new char[current];
            System.arraycopy( v, 0, value, 0, current );

            // Getting the value as a String
            return new String( value );
        }

        return null;
    }
}