/*
 * 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.brooklyn.core.plan;

import java.io.StringReader;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.brooklyn.api.catalog.CatalogItem;
import org.apache.brooklyn.api.catalog.CatalogItem.CatalogItemType;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.internal.AbstractBrooklynObjectSpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.core.mgmt.EntityManagementUtils;
import org.apache.brooklyn.entity.stock.BasicApplication;
import org.apache.brooklyn.entity.stock.BasicEntity;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.stream.ReaderInputStream;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/** Example implementation of {@link PlanToSpecTransformer} showing 
 * how implementations are meant to be written. */
public class XmlPlanToSpecTransformer implements PlanToSpecTransformer {
    
    // this is REPLACED by ExampleXmlTypePlanTransformer
    // TODO remove when PlanToSpecTransformer is removed
    
    @SuppressWarnings("unused")
    private ManagementContext mgmt;

    @Override
    public void setManagementContext(ManagementContext managementContext) {
        mgmt = managementContext;
    }

    @Override
    public String getShortDescription() {
        return "Dummy app structure created from the XML tree";
    }

    @Override
    public boolean accepts(String mime) {
        if ("test-xml".equals(mime)) return true;
        return false;
    }

    @SuppressWarnings("unchecked")
    @Override
    public EntitySpec<? extends Application> createApplicationSpec(String plan) {
        Document dom = parseXml(plan);
        EntitySpec<?> result = toEntitySpec(dom, 0);
        if (Application.class.isAssignableFrom(result.getType())) {
            return (EntitySpec<Application>) result;
        } else {
            return EntityManagementUtils.newWrapperApp().child(result);
        }
    }

    @SuppressWarnings({ "unchecked" })
    @Override
    public <T, SpecT extends AbstractBrooklynObjectSpec<? extends T, SpecT>> SpecT createCatalogSpec(CatalogItem<T, SpecT> item, Set<String> encounteredTypes) {
        if (item.getPlanYaml()==null) throw new PlanNotRecognizedException("Plan is null");
        if (item.getCatalogItemType()==CatalogItemType.ENTITY) {
            return (SpecT)toEntitySpec(parseXml(item.getPlanYaml()), 1);
        }
        if (item.getCatalogItemType()==CatalogItemType.TEMPLATE) {
            return (SpecT)toEntitySpec(parseXml(item.getPlanYaml()), 0);
        }
        throw new PlanNotRecognizedException("Type "+item.getCatalogItemType()+" not supported");
    }

    private Document parseXml(String plan) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        Document dom;
        
        try {
            //Using factory get an instance of document builder
            DocumentBuilder db = dbf.newDocumentBuilder();

            //parse using builder to get DOM representation of the XML file
            dom = db.parse(new ReaderInputStream(new StringReader(plan)));
            
        } catch (Exception e) {
            Exceptions.propagateIfFatal(e);
            throw new PlanNotRecognizedException(e);
        }
        return dom;
    }

    private EntitySpec<?> toEntitySpec(Node dom, int depth) {
        if (dom.getNodeType()==Node.DOCUMENT_NODE) {
            if (dom.getChildNodes().getLength()!=1) {
                // NB: <?...?>  entity preamble might break this
                throw new IllegalStateException("Document for "+dom+" has "+dom.getChildNodes().getLength()+" nodes; 1 expected.");
            }
            return toEntitySpec(dom.getChildNodes().item(0), depth);
        }
        
        EntitySpec<?> result = depth == 0 ? EntitySpec.create(BasicApplication.class) : EntitySpec.create(BasicEntity.class);
        result.displayName(dom.getNodeName());
        if (dom.getAttributes()!=null) {
            for (int i=0; i<dom.getAttributes().getLength(); i++)
                result.configure(dom.getAttributes().item(i).getNodeName(), dom.getAttributes().item(i).getTextContent());
        }
        if (dom.getChildNodes()!=null) {
            for (int i=0; i<dom.getChildNodes().getLength(); i++) {
                Node item = dom.getChildNodes().item(i);
                if (item.getNodeType()==Node.ELEMENT_NODE) {
                    result.child(toEntitySpec(item, depth+1));
                }
            }
        }
        return result;
    }

}
