package org.xresloader.core.data.dst;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.xresloader.core.ProgramOptions;
import org.xresloader.core.data.err.ConvException;
import org.xresloader.core.scheme.SchemeConf;

/**
 * Created by owentou on 2014/10/10.
 */
public class DataDstXml extends DataDstJava {

    static Pattern xmlTagNameValidateRule = null;

    static boolean validateTagName(String in) {
        if (in == null) {
            return false;
        }

        if (in.isEmpty()) {
            return false;
        }

        if (in.length() >= 3 && in.substring(0, 3).equalsIgnoreCase("xml")) {
            return false;
        }

        if (xmlTagNameValidateRule == null) {
            xmlTagNameValidateRule = Pattern.compile("[a-zA-Z][^\\s:]*");
        }

        return xmlTagNameValidateRule.matcher(in).matches();
    }

    @Override
    public boolean init() {
        return true;
    }

    /**
     * @return 协议处理器名字
     */
    public String name() {
        return "xml";
    }

    @Override
    public final byte[] build(DataDstImpl compiler) throws ConvException {
        // pretty print
        OutputFormat of = null;
        if (ProgramOptions.getInstance().prettyIndent <= 0) {
            of = OutputFormat.createCompactFormat();
        } else {
            of = OutputFormat.createPrettyPrint();
            of.setIndentSize(ProgramOptions.getInstance().prettyIndent);
        }

        // build data
        DataDstObject data_obj = build_data(compiler);

        // build xml tree
        Document doc = DocumentHelper.createDocument();
        String encoding = SchemeConf.getInstance().getKey().getEncoding();
        if (null != encoding && false == encoding.isEmpty()) {
            doc.setXMLEncoding(encoding);
            of.setEncoding(encoding);
        }

        doc.setRootElement(DocumentHelper.createElement(ProgramOptions.getInstance().xmlRootName));
        doc.getRootElement().addComment("this file is generated by xresloader, please don't edit it.");

        Element header = DocumentHelper.createElement("header");
        Element body = DocumentHelper.createElement("body");
        Element data_message_type = DocumentHelper.createElement("data_message_type");

        writeData(header, data_obj.header, header.getName());

        // body
        for (Map.Entry<String, List<Object>> item : data_obj.body.entrySet()) {
            for (Object obj : item.getValue()) {
                Element xml_item = DocumentHelper.createElement(item.getKey());

                writeData(xml_item, obj, item.getKey());

                body.add(xml_item);
            }
        }

        writeData(body, data_obj.body, body.getName());

        writeData(data_message_type, data_obj.data_message_type, data_message_type.getName());

        doc.getRootElement().add(header);
        doc.getRootElement().add(body);
        doc.getRootElement().add(data_message_type);

        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            XMLWriter writer = new XMLWriter(bos, of);
            writer.write(doc);

            return bos.toByteArray();
        } catch (Exception e) {
            this.logErrorMessage("write xml failed, %s", e.getMessage());
            return null;
        }
    }

    @Override
    public final DataDstWriterNode compile() {
        this.logErrorMessage("xml can not be protocol description.");
        return null;
    }

    @SuppressWarnings("unchecked")
    private void writeData(Element sb, Object data, String wrapper_name) {
        // null
        if (null == data) {
            return;
        }

        // 数字
        // 枚举值已被转为Java Long,会在这里执行
        if (data instanceof Number) {
            sb.addText(data.toString());
            return;
        }

        // 布尔
        if (data instanceof Boolean) {
            sb.addText(((Boolean) data) ? "true" : "false");
            return;
        }

        // 字符串&二进制
        if (data instanceof String) {
            sb.addText(data.toString());
            return;
        }

        // 列表
        if (data instanceof List) {
            List<Object> ls = (List<Object>) data;

            int index = 0;
            for (Object obj : ls) {
                Element list_ele = DocumentHelper.createElement(wrapper_name);
                Attribute index_attr = DocumentHelper.createAttribute(list_ele, "for", String.valueOf(index));
                list_ele.add(index_attr);

                writeData(list_ele, obj, wrapper_name);
                sb.add(list_ele);
                ++index;
            }
            return;
        }

        // Hashmap
        if (data instanceof Map<?, ?>) {
            Map<?, ?> mp = (Map<?, ?>) data;
            ArrayList<Map.Entry<?, ?>> sorted_array = new ArrayList<Map.Entry<?, ?>>();
            sorted_array.ensureCapacity(mp.size());
            sorted_array.addAll(mp.entrySet());
            sorted_array.sort((l, r) -> {
                if (l.getValue() instanceof Integer && r.getValue() instanceof Integer) {
                    return ((Integer) l.getValue()).compareTo((Integer) r.getValue());
                }

                if (l.getKey() instanceof Integer && r.getKey() instanceof Integer) {
                    return ((Integer) l.getKey()).compareTo((Integer) r.getKey());
                } else if (l.getKey() instanceof Long && r.getKey() instanceof Long) {
                    return ((Long) l.getKey()).compareTo((Long) r.getKey());
                } else {
                    return l.getKey().toString().compareTo(r.getKey().toString());
                }
            });

            boolean hasInvalidTagName = false;
            for (Map.Entry<?, ?> item : sorted_array) {
                if (!(item.getKey() instanceof String)) {
                    hasInvalidTagName = true;
                    break;
                }

                if (!validateTagName((String) item.getKey())) {
                    hasInvalidTagName = true;
                    break;
                }
            }

            if (data instanceof SpecialInnerHashMap<?, ?>) {
                sb.addAttribute("mode", "map");
            } else {
                sb.addAttribute("mode", "message");
            }

            for (Map.Entry<?, ?> item : sorted_array) {
                // 数据会多一层,这里去除
                if (item.getValue() instanceof List) {
                    if (item.getKey() instanceof Number) {
                        writeData(sb, item.getValue(), item.getKey().toString());
                    } else {
                        writeData(sb, item.getValue(), item.getKey().toString());
                    }
                } else {
                    Element xml_item;

                    if (hasInvalidTagName || data instanceof SpecialInnerHashMap<?, ?>) {
                        String realTypeName;
                        if (item.getKey() instanceof Integer) {
                            realTypeName = "int32";
                        } else if (item.getKey() instanceof Long) {
                            realTypeName = "int64";
                        } else {
                            realTypeName = item.getKey().getClass().getSimpleName().toLowerCase();
                        }

                        xml_item = DocumentHelper.createElement(realTypeName);
                        writeData(xml_item, item.getValue(), realTypeName);
                        xml_item.addAttribute("key", item.getKey().toString());
                        xml_item.addAttribute("type", realTypeName);
                    } else {
                        xml_item = DocumentHelper.createElement(item.getKey().toString());
                        writeData(xml_item, item.getValue(), item.getKey().toString());
                    }

                    sb.add(xml_item);
                }
            }

            return;
        }

        ProgramOptions.getLoger().error("%s not support.", data.toString());
    }

    /**
     * 转储常量数据
     * 
     * @return 常量数据,不支持的时候返回空
     */
    public final byte[] dumpConst(HashMap<String, Object> data) throws ConvException, IOException {
        // pretty print
        OutputFormat of = null;
        if (ProgramOptions.getInstance().prettyIndent <= 0) {
            of = OutputFormat.createCompactFormat();
        } else {
            of = OutputFormat.createPrettyPrint();
            of.setIndentSize(ProgramOptions.getInstance().prettyIndent);
        }

        // build xml tree
        Document doc = DocumentHelper.createDocument();
        String encoding = SchemeConf.getInstance().getKey().getEncoding();
        if (null != encoding && false == encoding.isEmpty()) {
            doc.setXMLEncoding(encoding);
            of.setEncoding(encoding);
        }

        doc.setRootElement(DocumentHelper.createElement(ProgramOptions.getInstance().xmlRootName));
        doc.getRootElement().addComment("this file is generated by xresloader, please don't edit it.");

        writeData(doc.getRootElement(), data, "");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        XMLWriter writer = new XMLWriter(bos, of);
        writer.write(doc);

        return bos.toByteArray();
    }
}