/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package org.teiid.translator.document;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * TODO: need to back this up with something like MapDB, to avoid OOM
 * Also need to write our own JSONPaser that returns this object directly.
 */
public class Document {
    private String name;
    private Map<String, Object> properties = new LinkedHashMap<String, Object>();
    private Map<String, List<Document>> children = new LinkedHashMap<String, List<Document>>();
    private boolean array;
    private Document parent;
    
    public Document() {
    }
    
    public Document(String name, boolean array, Document parent) {
        this.name = name;
        this.parent = parent;
        this.array = array;
    }    
    
    public boolean isArray() {
        return array;
    }

    static List<Map<String, Object>> crossjoinWith(
            List<Map<String, Object>> left, List<? extends Document> rightDocuments) {
        ArrayList<Map<String, Object>> joined = new ArrayList<Map<String,Object>>();
        for (Document right : rightDocuments) {
            List<Map<String,Object>> rightRows = right.flatten();            
            for (Map<String, Object> outer : left) {
                for (Map<String, Object> inner : rightRows) {
                    LinkedHashMap<String, Object> row = new LinkedHashMap<String, Object>();
                    row.putAll(outer);
                    row.putAll(inner);
                    joined.add(row);
                }
            }
        }
        return joined;
    }
    
    public List<Map<String, Object>> flatten(){
        List<Map<String, Object>> joined = new ArrayList<Map<String, Object>>();
        LinkedHashMap<String, Object> row = new LinkedHashMap<String, Object>();
        if (this.properties != null) {
            row.putAll(this.properties);
        }
        joined.add(row);            
        if (this.children != null && !this.children.isEmpty()) {
            for (List<? extends Document> childDoc:this.children.values()) {
                joined = crossjoinWith(joined, childDoc);
            }
        }
        return joined;
    }
    
    public Map<String, Object> getProperties(){
        return this.properties;
    }
    
    public Map<String, List<Document>> getChildren() {
        return children;
    }

    public List<? extends Document> getChildDocuments(String path) {
        if (this.children != null) {
            int index = path.indexOf('/');
            if (index != -1) {
                String parentName = path.substring(0, index);
                if (parentName.equals(this.name)) {                    
                    return this.children.get(path.substring(index+1));    
                } else {
                    // then this is the sibiling
                    return this.parent.getChildDocuments(parentName);
                }
            }
            List<? extends Document> children =  this.children.get(path);
            if (children == null && this.parent != null) {
                children = this.parent.getChildDocuments(path);
            }
            return children;
        }
        return null;
    }

    private static String name(String s1, String s2) {
        if (s1 != null) {
            StringBuilder sb = new StringBuilder();
            sb.append(s1).append("/").append(s2);
            return sb.toString();
        } else {
            return s2;
        }
    }
    
    public String getName() {
        if (this.parent != null) {
            return name(this.parent.getName(), this.name);
        }
        return name;
    }

    public void addProperty(String key, Object value) {
        if (this.parent == null) {
            this.properties.put(key, value);
        } else {
            this.properties.put(name(getName(), key), value);
        }
    }

    public void addArrayProperty(String key, Object value) {
        if (this.properties == null) {
            this.properties = new LinkedHashMap<String, Object>();
        }
        
        String propkey = this.parent == null?key:name(getName(), key);
        @SuppressWarnings("unchecked")
        List<Object> propValue = (List<Object>)this.properties.get(propkey);
        
        if (propValue == null) {
            propValue = new ArrayList<Object>();
            propValue.add(value);
        } else {
            propValue.add(value);
        }
        this.properties.put(propkey, propValue);
    }    
    
    public void addChildDocuments(String path, List<Document> child) {
        this.children.put(path, child);
    }
    
    public List<Document> addChildDocument(String path, Document child) {
        if (this.children == null) {
            this.children = new LinkedHashMap<String, List<Document>>();
        }
        if (children.get(path) == null) {
            children.put(path, new ArrayList<Document>());
        }
        this.children.get(path).add(child);
        return this.children.get(path);
    }    
           
    public String toString() {
        return this.name;
    }
    
    public Document getParent() {
        return this.parent;
    }
}
