/*
 * Copyright 2003-2004 The Apache Software Foundation
 * 
 * Licensed 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.commons.attributes.compiler;

import java.util.ArrayList;
import java.util.List;

import org.apache.tools.ant.BuildException;

/**
 * Parser for attribute expressions.
 */
public class AttributeExpressionParser {
    
    public static class Argument {
        public final String field;
        public final String text;
        public final int length;
        
        public Argument (String field, String text, int length) {
            this.field = field;
            this.text = text;
            this.length = length;
        }
        
        public boolean equalsOrNull (String a, String b) {
            if (a == null) {
                return b == null;
            } else {
                return b != null && a.equals (b);
            }
        }
        
        public boolean equals (Object o) {
            return o instanceof Argument &&
                equalsOrNull (field, ((Argument) o).field) &&
                ((Argument) o).text.equals (text);
        }
        
        public String toString () {
            return "[Argument: " + field + ", " + text + ", " + length + "]";
        }
    }
    
    public static class ParseResult {
        public final List arguments = new ArrayList ();
        public final String className;
        
        public ParseResult (String className) {
            this.className = className;
        }
        
        public boolean equals (Object o) {
            return o instanceof ParseResult &&
                className.equals (((ParseResult) o).className) &&
                arguments.equals (((ParseResult) o).arguments);
        }
        
        public String toString () {
            return "[ParseResult: " + className + ", " + arguments + "]";
        }
    }
    
    protected static Argument nextArgument (String string, int startPos, String filename, int line) {
        if (string.charAt (startPos) == ')') {
            return null;
        }
        
        StringBuffer sb = new StringBuffer ();
        
        int i = startPos;
        int depth = 0;
        while (!( (string.charAt (i) == ',' || string.charAt (i) == ')') && depth == 0)) {
            switch (string.charAt (i)) {
            case '[':
            case '{':
            case '(': depth++; break;
                
            case ']':
            case '}':
            case ')': depth--; break;
                
            case '\'':
            case '"': {
                    // handle string literals
                    char endChar = string.charAt (i);
                    sb.append (string.charAt (i));
                    i++;
                    while (true) {
                        char ch = string.charAt (i);
                        if (ch == '\\') {
                            sb.append (ch);
                            i++;
                            ch = string.charAt (i);
                            sb.append (ch);
                        } else {
                            if (ch == endChar) {
                                break;
                            }
                            sb.append (ch);                            
                        }
                        i++;
                    }
                }
            }
            
            sb.append (string.charAt (i));
            i++;
            if (i == string.length ()) {
                throw new BuildException (filename + ":" + line + ": Unterminated argument: " + string);
            }
        }        
        
        if (string.charAt (i) == ',') {
            i++;
        }
        
        String text = sb.toString ();
        String field = null;
        int eqPos = text.indexOf ('=');
        if (eqPos > -1) {
            boolean identifier = true;
            for (int j = 0; j < eqPos; j++) {
                char ch = text.charAt (j);
                
                if (Character.isJavaIdentifierPart (ch) || ch == ' ') {
                } else {
                    identifier = false;
                }
            }
            
            if (identifier) {
                field = text.substring (0, eqPos).trim ();
                text = text.substring (eqPos + 1).trim ();
            }
        }
        
        Argument arg = new Argument (field, text, i - startPos);
        
        return arg;
    }
    
    public static ParseResult parse (String string, String filename, int line) {
        StringBuffer sb = new StringBuffer ();
        int i = 0;
        while (i < string.length () && (Character.isJavaIdentifierPart (string.charAt (i)) || string.charAt (i) == '.' || string.charAt (i) == ' ')) {
            sb.append (string.charAt (i));
            i++;
        }
        
        if (i == string.length () || string.charAt (i) != '(') {
            throw new BuildException (filename + ":" + line + ": Illegal expression: " + string);
        }
        
        ParseResult result = new ParseResult (sb.toString ());
        
        i++;
        Argument arg = null;
        boolean seenField = false;
        while ((arg = nextArgument (string, i, filename, line)) != null) {
            if (arg.field != null) {
                seenField = true;
            }
            
            if (seenField && arg.field == null) {
                throw new BuildException (filename + ":" + line + ": Un-named parameters must come before the named parameters: " + string);
            }
            
            result.arguments.add (arg);
            i += arg.length;
        }
        
        return result;
    }
}