/*
 * RHQ Management Platform
 * Copyright (C) 2005-2011 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.plugins.apache.parser;

import java.io.File;
import java.util.List;

import org.rhq.augeas.util.Glob;
import org.rhq.plugins.apache.util.AugeasNodeValueUtil;
import org.rhq.plugins.apache.util.RuntimeApacheConfiguration;

/**
 *
 * @author Filip Drabek
 * @author Lukas Krejci
 */
public class ApacheParserImpl implements ApacheParser {

    private final static String INCLUDE_DIRECTIVE = "Include";
    private static final String SERVER_ROOT_DIRECTIVE = "ServerRoot";
    private final ApacheDirectiveTree tree;
    private ApacheDirectiveStack stack;
    private String serverRootPath;
    private RuntimeApacheConfiguration.NodeInspector nodeInspector;
    
    /**
     * 
     * @param tree the tree that this parser will fill in
     * @param initialServerRootPath the initial server root path as detected by other means
     * @param nodeInspector the node inspector to determine the runtime configuration or null, if full configuration tree is needed
     */
    public ApacheParserImpl(ApacheDirectiveTree tree, String initialServerRootPath, RuntimeApacheConfiguration.NodeInspector nodeInspector) {
        stack = new ApacheDirectiveStack();
        this.serverRootPath = initialServerRootPath;
        this.tree = tree;
        stack.addDirective(this.tree.getRootNode());
        this.nodeInspector = nodeInspector;  
    }

    public void addDirective(ApacheDirective directive) throws Exception {
        if (stack.getLastDirective() == null) {
            //we're ignoring until the end of an ignored nested directive
            return;
        }
        
        if (directive.getName().equals(INCLUDE_DIRECTIVE)) {
            List<File> files = getIncludeFiles(directive.getValuesAsString());
            for (File fl : files) {
                if (fl.exists() && fl.isFile()) {
                    ApacheConfigReader.searchFile(fl.getAbsolutePath(), this);
                }
            }
        } else if (directive.getName().equals(SERVER_ROOT_DIRECTIVE)) {
            this.serverRootPath = AugeasNodeValueUtil.unescape(directive.getValuesAsString());
        }
        
        if (nodeInspector != null) {
            //let the inspector process this directive in case it sees something of interest
            nodeInspector.inspect(directive.getName(), directive.getValues(), directive.getValuesAsString());
        }
        
        directive.setParentNode(stack.getLastDirective());
        stack.getLastDirective().addChildDirective(directive);
    }

    public void endNestedDirective(ApacheDirective directive) {
        stack.removeLastDirective();
    }

    public void startNestedDirective(ApacheDirective directive) {
        if (nodeInspector != null) {            
            //now we have a node inspector so the tree construction is driven by it - we actually leave out the conditional
            //directives and replace them with their contents (if they are to be applied of course)...
            
            RuntimeApacheConfiguration.NodeInspectionResult res = nodeInspector.inspect(directive.getName(), directive.getValues(), directive.getValuesAsString());
            if (res == null || (res.nodeIsConditional && !res.shouldRecurseIntoConditionalNode)) {
                //ok, this node should be ignored, mark that fact with a null parent
                stack.addDirective(null);
            } else if (res.nodeIsConditional && res.shouldRecurseIntoConditionalNode) {
                //this is a little tricky..
                //we need to leave out this directive, because it is conditional and we should be replacing it with its contents
                //but also we need to keep the stack balanced (i.e. we're going down a nested directive, so we ought to put something
                //on the stack). By duplicating the last known directive on the stack we actually achieve both of the goals - the 
                //stack remains balanced and the child nodes get added to the parent of this directive (i.e. in the end it is going to
                //look like we've replaced the directive with its child directives).
                stack.addDirective(stack.getLastDirective());
            } else {
                //ok, a "normal" (non-conditional) nested node
                //we might be inside an ignored nested directive, so leave all the weaving stuff
                //out in that case...
                if (stack.getLastDirective() != null) {
                    directive.setParentNode(stack.getLastDirective());
                    stack.getLastDirective().addChildDirective(directive);
                }
                stack.addDirective(directive);
            }            
        } else {
            //there is no node inspector, so we have no "guidance" on the tree construction.
            //Just include all the nested structures so that a full configuration tree is built
            //with all the directives.
            
            //we might be inside an ignored nested directive, so leave all the weaving stuff
            //out in that case...
            if (stack.getLastDirective() != null) {
                directive.setParentNode(stack.getLastDirective());
                stack.getLastDirective().addChildDirective(directive);
            }
            stack.addDirective(directive);
        }
    }

    private List<File> getIncludeFiles(String foundInclude) {
        File check = new File(foundInclude);
        File root = new File(check.isAbsolute() ? Glob.rootPortion(foundInclude) : serverRootPath);

        return Glob.match(root, foundInclude, Glob.ALPHABETICAL_COMPARATOR);
    }
}
