/*
 * Copyright 2008-2009 the original author or authors.
 *
 * 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 net.hasor.core.setting.xml;
import net.hasor.core.Settings;
import net.hasor.core.XmlNode;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * @version : 2013-7-13
 * @author 赵永春 (zyc@byshell.org)
 */
public class SaxXmlParser extends DefaultHandler {
    private Settings                    dataContainer     = null;
    private Map<String, StringBuilder>  xmlText           = new HashMap<>();
    private Map<String, DefaultXmlNode> currentXmlPropert = new HashMap<>();

    public SaxXmlParser(final Settings dataContainer) {
        this.dataContainer = dataContainer;
    }

    private StringBuilder getText(final String xmlns) {
        if (!this.xmlText.containsKey(xmlns)) {
            this.xmlText.put(xmlns, new StringBuilder(""));
        }
        return this.xmlText.get(xmlns);
    }

    private void cleanText(final String xmlns) {
        this.xmlText.remove(xmlns);
    }

    private DefaultXmlNode getCurrentXmlPropert(final String xmlns) {
        return this.currentXmlPropert.get(xmlns);
    }

    private void setCurrentXmlPropert(final String xmlns, final DefaultXmlNode xmlProperty) {
        this.currentXmlPropert.put(xmlns, xmlProperty);
    }

    private String curXmlns = null;

    public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException {
        DefaultXmlNode xmlProperty = this.getCurrentXmlPropert(uri);
        if (xmlProperty == null) {
            xmlProperty = new DefaultXmlNode(null, "root");
            this.setCurrentXmlPropert(uri, xmlProperty);
        }
        DefaultXmlNode thisXmlNode = new DefaultXmlNode(xmlProperty, localName);
        xmlProperty.addChildren(thisXmlNode);
        this.setCurrentXmlPropert(uri, thisXmlNode);
        //
        for (int i = 0; i < attributes.getLength(); i++) {
            String attName = attributes.getLocalName(i);
            String attValue = attributes.getValue(i);
            this.getCurrentXmlPropert(uri).addAttribute(attName, attValue);
        }
        this.curXmlns = uri;
    }

    public void endElement(final String uri, final String localName, final String qName) throws SAXException {
        StringBuilder strBuffer = this.getText(uri);
        //
        DefaultXmlNode currentNode = this.getCurrentXmlPropert(uri);
        currentNode.setText(strBuffer.toString().trim());
        this.setCurrentXmlPropert(uri, (DefaultXmlNode) currentNode.getParent());
        //
        this.cleanText(uri);
        this.curXmlns = uri;
    }

    public void characters(final char[] ch, final int start, final int length) throws SAXException {
        if (this.curXmlns == null) {
            return;
        }
        StringBuilder strBuffer = this.getText(this.curXmlns);
        String content = new String(ch, start, length);
        strBuffer.append(content);
    }

    public void endDocument() throws SAXException {
        for (Entry<String, DefaultXmlNode> ent : this.currentXmlPropert.entrySet()) {
            String currentXmlns = ent.getKey();
            DefaultXmlNode currentXml = ent.getValue();
            // 1.将XmlTree转换为map映射
            HashMap<String, List<Object>> dataMap = new HashMap<>();
            this.convertType(dataMap, currentXml.getChildren(), "");
            // 2.弃掉转换过程中根节点名称
            HashMap<String, List<Object>> finalReturnData = new HashMap<>();
            for (Entry<String, List<Object>> data : dataMap.entrySet()) {
                String keyStr = data.getKey();
                List<Object> valStr = data.getValue();
                //
                if (keyStr.indexOf(".") > 0) {
                    keyStr = keyStr.substring(keyStr.indexOf(".") + 1);
                    finalReturnData.put(keyStr.toLowerCase(), valStr);
                } else {
                    finalReturnData.put(".", valStr);
                }
            }
            dataMap = finalReturnData;
            // 3.输出映射结果，多各值的话会按照顺序依次add到最终结果中。
            for (String key : dataMap.keySet()) {
                String $key = key.toLowerCase();
                List<Object> $var = dataMap.get(key);
                if ($var != null && $var.size() > 0) {
                    for (Object var : $var) {
                        this.dataContainer.addSetting($key, var, currentXmlns);
                    }
                }
            }
        }
    }

    /** 转换成Key Value形式 */
    protected void convertType(final Map<String, List<Object>> returnData, final List<XmlNode> xmlPropertyList, final String parentAttName) {
        if (xmlPropertyList != null) {
            for (XmlNode xmlNode : xmlPropertyList) {
                DefaultXmlNode impl = (DefaultXmlNode) xmlNode;
                // 1.put本级
                String key = "".equals(parentAttName) ? impl.getName() : parentAttName + "." + impl.getName();
                convertTypePut(returnData, key, impl);
                // 2.put属性
                for (Entry<String, String> ent : impl.getAttributeMap().entrySet()) {
                    String putKey = key + "." + ent.getKey();
                    convertTypePut(returnData, putKey, ent.getValue());
                }
                // 3.put孩子
                this.convertType(returnData, xmlNode.getChildren(), key);
            }
        }
    }

    private void convertTypePut(final Map<String, List<Object>> returnData, String key, Object value) {
        List<Object> dataList = null;
        if (returnData.containsKey(key)) {
            dataList = returnData.get(key);
        } else {
            dataList = new ArrayList<>();
            returnData.put(key, dataList);
        }
        dataList.add(value);
    }
}