/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/tools/stores/XStore.java,v 1.3 2004/07/30 06:52:06 ozeigermann Exp $
 * $Revision: 1.3 $
 * $Date: 2004/07/30 06:52:06 $
 *
 * ====================================================================
 *
 * Copyright 1999-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.slide.store.tamino.tools.stores;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.tools.stores.XDomainConstants;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.util.Configuration;
import org.apache.slide.util.JDom;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XException;
import org.apache.slide.util.XUri;
import org.jdom.Element;

/**
 ** Represents a domain file store element. Also provices access to the corresponding scope element.
 **
 ** @author michael.hartmeier@softwareag.com
 ** @version $Revision: 1.3 $
 **/
public class XStore {
    private final String name;
    
    private final String parentStoreClass;
    public final Parameters parms;
    private final XStoreAspects aspects;
    private final XUri scope;
    
    /** null: no objectNode */
    private String objectNodeClass;
    
    /** List of elements that will be appended to the xml data element **/
    private final List fixedData;
    
    public static final String DEFAULT_OBJECT_NODE_CLASS = SubjectNode.class.getName();
    
    public XStore(
        String name, String parentStoreClass, XStoreAspects aspects, Parameters parms, XUri scope)
    {
        this.name = name;
        this.parentStoreClass = parentStoreClass;
        this.aspects = aspects;
        this.parms = parms;
        this.scope = scope;
        this.objectNodeClass = null;
        this.fixedData = new ArrayList();
    }
    
    public String getObjectNodeClass() {
        return objectNodeClass;
    }
    
    public void setObjectData(String objectNodeClass, Iterator elements) {
        this.objectNodeClass = objectNodeClass;
        while (elements.hasNext()) {
            fixedData.add(((Element) elements.next()).clone());
        }
    }
    
    public void clearObjectData() {
        fixedData.clear();
    }

    public String getName() {
        return name;
    }
    
    public String getParentStoreClass() {
        return parentStoreClass;
    }
    
    public XStoreAspects getAspects() {
        return aspects;
    }
    
    public XUri getScope() {
        return scope;
    }
    
    //-- Tamino parameter
    // TODO: move somewhere else (StoreGruops!?) - not necessarily available
    
    public String getTaminoCollectionUrl() {
        return buildTaminoCollectionUrl(getTaminoBase(), getTaminoDatabase(), getTaminoCollection());
    }
    
    /**
     * Method getDatabaseUri
     *
     * @param    taminoBase          a  String
     * @param    taminoDb            a  String
     *
     * @return   a String
     *
     */
    public static String buildTaminoDbUrl( String taminoBase, String taminoDb ) {
        String databaseUri = null;
        if( taminoBase != null && taminoDb != null ) {
            StringBuffer sb = new StringBuffer( taminoBase );
            if( !taminoBase.endsWith("/") )
                sb.append( '/' );
            sb.append( taminoDb );
            databaseUri = sb.toString();
        }
        return databaseUri;
    }

    /**
     * Method buildTaminoCollectionUri
     *
     * @param    taminoBase          a  String
     * @param    taminoDb            a  String
     * @param    collection          a  String
     *
     * @return   a String, never null
     *
     */
    public static String buildTaminoCollectionUrl( String taminoBase, String taminoDb, String collection ) {
        if( taminoBase == null || taminoDb == null || collection == null ) {
            throw new XAssertionFailed(taminoBase + " " + taminoDb + " " + collection);
        }
        StringBuffer sb = new StringBuffer( buildTaminoDbUrl(taminoBase, taminoDb) );
        sb.append( '/' );
        sb.append( collection );
        return sb.toString();
    }

    // TODO: use Parameter class?
    // TODO: move somewhere else, not every store has these arguments; expose parms file instead
    
    public String getTaminoDbUrl() {
        return buildTaminoDbUrl( getTaminoBase(), getTaminoDatabase());
    }
    
    public String getTaminoBase() {
        return getParameter(XGlobals.TAMINO_BASE);
    }
    public String getTaminoDatabase() {
        return getParameter(XGlobals.TAMINO_DATABASE);
    }
    public String getTaminoCollection() {
        return getParameter(XGlobals.TAMINO_COLLECTION);
    }
    
    public String getParameter(String name) {
        return parms.get(name, null);
    }
    
    public boolean useBinding() {
        return getBindingBoolean(getParameter(XGlobals.USE_BINDING));
    }
    
    public static boolean getBindingBoolean(String value) {
        if (value == null) {
            return Configuration.useGlobalBinding();
        }
        return "true".equalsIgnoreCase(value);
    }
    
    //-- xml
    
    public void addXmlDefinition(Element definition) {
        definition.addContent(createStoreElement());
        definition.addContent(createScopeElement());
    }
    
    public void addXmlData(Element data) {
        Element objectnode;
        Iterator iter;
        
        if (objectNodeClass != null) {
            try {
                objectnode = XObjectnode.locate(scope, data, objectNodeClass);
            } catch (XException e) {
                throw new XAssertionFailed(e);
            }
            iter = fixedData.iterator();
            while (iter.hasNext()) {
                objectnode.addContent((Element) ((Element) iter.next()).clone());
            }
        } else {
            // no object node for this store
        }
    }
    
    private Element createStoreElement() {
        Element result;
        
        result = new Element(XDomainConstants.STORE);
        result.setAttribute(XDomainConstants.NAME, name);
        result.setAttribute(XDomainConstants.CLAZZNAME, parentStoreClass);
        aspects.addXml(result);
        parms.addXml(result);
        return result;
    }
    
    private Element createScopeElement() {
        Element result = new Element( XDomainConstants.SCOPE );
        result.setAttribute( XDomainConstants.STORE, name );
        result.setAttribute( XDomainConstants.MATCH, scope.toString() );
        return result;
    }
    
    
    /** @return list of XStores **/
    public static List extractListFromXml(Element definition)
        throws XException
    {
        List stores;
        Iterator iter;
        List scopes;
        Element item;
        List found;
        
        // clone store - otherwise, I get concurrent modifications because the scopes list is modified
        stores = new ArrayList(definition.getChildren(XDomainConstants.STORE));
        scopes = definition.getChildren(XDomainConstants.SCOPE);
        iter = stores.iterator();
        found = new ArrayList();
        while( iter.hasNext() ) {
            item = (Element) iter.next();
            found.add(extractFromXml(item, scopes));
        }
        definition.getChildren(XDomainConstants.STORE).clear();
        if (scopes.size() != 0) {
            throw new XException("scopes remaining"); // TODO: improved error message
        }
        return found;
    }
    
    public static void extractXmlData(List stores, Element data) throws XException {
        XStore store;
        Element objectnode;
        
        while (!stores.isEmpty()) {
            store = remove(stores, true);
            objectnode = XObjectnode.locate(store.scope, data, null);
            if (objectnode != null) {
                objectnode.detach();
                removeImplicitChildObjectNodes(objectnode);
                store.setObjectData(JDom.getAttribute(objectnode, XDomainConstants.CLAZZNAME), objectnode.getChildren().iterator());
            } else {
                // don't throw an exception, I've seen plenty of domain file where stores don't have
                // a corresponding objectNode
                System.out.println("warning: Missing objectnode initialization for store "+store.getName()+" (scope "+store.getScope()+") in data section.");
            }
        }
    }

    /**
     ** Object nodes are created implicitly, thus I have to remove that implicitly.
     ** Otherwise, removing the last store ends up with useless /history, ... objectnodes.
     **
     ** TODO: this check isn't correct - we have to check parents, and the classname
     ** must not be hard-coded
     **/
    private static void removeImplicitChildObjectNodes(Element objectNode) {
        Iterator iter;
        Element ele;
        String cls;
        
        iter = objectNode.getChildren().iterator();
        while (iter.hasNext()) {
            ele = (Element) iter.next();
            if (ele.getName().equals(XDomainConstants.OBJECTNODE) && ele.getChildren().size() == 0) {
                cls = ele.getAttributeValue(XDomainConstants.CLAZZNAME);
                if (SubjectNode.class.getName().equals(cls)) {
                    iter.remove();
                }
            }
        }
    }
    
    public static void addXmlData(List stores, Element data) {
        XStore store;
        
        while (!stores.isEmpty()) {
            store = remove(stores, false);
            store.addXmlData(data);
        }
    }

    /**
     ** @pre stores.size() > 0
     **/
    private static XStore remove(List stores, boolean deepest) {
        int i;
        int max;
        int resultIdx;
        XUri resultScope;
        XUri currentScope;
        XStore p;
        
        max = stores.size();
        if (max == 0) {
            throw new XAssertionFailed("" + max);
        }
        resultIdx = 0;
        resultScope = ((XStore) stores.get(0)).getScope();
        for (i = 1; i < max; i++) {
            p = (XStore) stores.get(i);
            currentScope = p.getScope();
            if (isBetter(resultScope, currentScope, deepest)) {
                resultIdx = i;
                resultScope = currentScope;
            }
        }
        return (XStore) stores.remove(resultIdx);
    }

    private static boolean isBetter(XUri resultScope, XUri currentScope, boolean deepest) {
        if (deepest) {
            return resultScope.compare(currentScope) == XUri.PREFIX;
        } else {
            return currentScope.compare(resultScope) == XUri.PREFIX;
        }
    }

    public static XStore extractFromXml(Element store, List scopes) throws XException {
        String name;
        String parentStoreClass;
        
        name = JDom.getAttribute(store, XDomainConstants.NAME);
        parentStoreClass = store.getAttributeValue(XDomainConstants.CLAZZNAME);
        return new XStore(name, parentStoreClass,
                                  XStoreAspects.extractFromXml((Element) store.clone()), Parameters.fromXml(store),
                                  extractScope(scopes, name));
    }
    
    public static XUri extractScope(List scopes, String name) throws XException {
        Iterator iter;
        Element s;
        
        iter = scopes.iterator();
        while (iter.hasNext()) {
            s = (Element) iter.next();
            if (name.equals(s.getAttributeValue(XDomainConstants.STORE))) {
                iter.remove();
                return new XUri(JDom.getAttribute(s, XDomainConstants.MATCH));
            }
        }
        throw new XException("no scope for store " + name);
    }
}


