/**
 *
 * Copyright (c) 2006-2016, Speedment, Inc. All Rights Reserved.
 *
 * 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 com.speedment.internal.util.document;

import com.speedment.config.Document;
import com.speedment.config.db.trait.HasName;
import com.speedment.internal.util.Cast;
import com.speedment.internal.util.Trees;
import static com.speedment.util.StaticClassUtil.instanceNotAllowed;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import java.util.function.Function;
import static java.util.stream.Collectors.toList;
import java.util.stream.Stream;
import static com.speedment.util.NullUtil.requireNonNulls;
import static java.util.Objects.requireNonNull;

/**
 *
 * @author pemi
 */
public final class DocumentUtil {

    @SuppressWarnings("unchecked")
    public static Stream<? extends Document> traverseOver(Document document) {
        requireNonNull(document);
        return Trees.traverse(
                document,
                d -> (Stream<Document>) d.children(),
                Trees.TraversalOrder.DEPTH_FIRST_PRE
        );
    }
    
    public static <E extends Document> Optional<E> ancestor(Document document, final Class<E> clazz) {
        requireNonNulls(document, clazz);
        return document.ancestors()
                .filter(clazz::isInstance)
                .map(clazz::cast)
//                .map(p -> (E) p)
                .findFirst();
    }

    @SuppressWarnings("unchecked")
    public static <E extends Document> Stream<Document> childrenOf(Document document, BiFunction<Document, Map<String, Object>, E> childConstructor) {
        return document.stream().values()
                .filter(obj -> obj instanceof List<?>)
                .map(list -> (List<Object>) list)
                .flatMap(list -> list.stream())
                .filter(obj -> obj instanceof Map<?, ?>)
                .map(map -> (Map<String, Object>) map)
                .map(map -> childConstructor.apply(document, map));
    }

    public static Map<String, Object> newDocument(Document document, String key) {
        final List<Map<String, Object>> children = document.get(key)
            .map(Document.DOCUMENT_LIST_TYPE::cast)
            .orElseGet(() -> {
                final List<Map<String, Object>> list = new CopyOnWriteArrayList<>();
                document.put(key, list);
                return list;
            });
        
        final Map<String, Object> child = new ConcurrentHashMap<>();
        children.add(child);

        return child;
    }

    /**
     * Returns the relative name for the given Document up to the point given by the
     * parent Class by successively applying the provided nameMapper onto the
     * Node names.
     * <p>
     * For example, {@code relativeName(column, Dbms.class)} would return the
     * String "dbms_name.schema_name.table_name.column_name".
     *
     * @param <T> parent type
     * @param <D> Document type
     * @param document to use
     * @param from class
     * @param nameMapper to apply to all names encountered during traversal
     * @return the relative name for this Node from the point given by the
     * parent Class
     */
    public static <T extends Document & HasName, D extends Document & HasName> String relativeName(D document, final Class<T> from, Function<String, String> nameMapper) {
        requireNonNulls(document, from, nameMapper);
        final StringJoiner sj = new StringJoiner(".").setEmptyValue("");
        final List<Document> ancestors = document.ancestors()/*.map(p -> (Parent<?>) p)*/.collect(toList());
        boolean add = false;
        for (final Document parent : ancestors) {
            if (add || from.isAssignableFrom(parent.getClass())) {
                nameFrom(parent).ifPresent(n -> sj.add(nameMapper.apply(n)));
                add = true;
            }
        }
        nameFrom(document).ifPresent(n -> sj.add(nameMapper.apply(n)));
        return sj.toString();
    }
    
    /**
     * Returns the relative name for the given Document up to the point given by the
     * parent Class.
     * <p>
     * For example, {@code relativeName(column, Dbms.class)} would return the
     * String "dbms_name.schema_name.table_name.column_name".
     *
     * @param <T> parent type
     * @param <D> Document type
     * @param document to use
     * @param from class
     * @return the relative name for this Node from the point given by the
     * parent Class
     */
    public static <T extends Document & HasName, D extends Document & HasName> String relativeName(D document, final Class<T> from) {
        return relativeName(document, from, s -> s);
    }

    private static Optional<String> nameFrom(Document document) {
        return Cast.cast(document, HasName.class).map(HasName::getName);
    }

    /**
     * Utility classes should not be instantiated.
     */
    private DocumentUtil() {
        instanceNotAllowed(getClass());
    }
}

@@ -709,16 +709,63 @@
 cument;%0A
+import com.speedment.config.db.trait.HasAlias;%0A
 import c
@@ -4215,24 +4215,23 @@
 al List%3C
-Document
+HasName
 %3E ancest
@@ -4256,16 +4256,75 @@
 estors()
+.filter(HasName.class::isInstance).map(HasName.class::cast)
 /*.map(p
@@ -4411,24 +4411,23 @@
  (final 
-Document
+HasName
  parent 
@@ -4527,40 +4527,8 @@
     
- nameFrom(parent).ifPresent(n -%3E
  sj.
@@ -4541,32 +4541,46 @@
 meMapper.apply(n
+ameFrom(parent
 )));%0A           
@@ -4631,42 +4631,8 @@
     
- nameFrom(document).ifPresent(n -%3E
  sj.
@@ -4653,16 +4653,32 @@
 .apply(n
+ameFrom(document
 )));%0A   
@@ -4991,70 +4991,8 @@
   *%0A
-     * @param %3CT%3E parent type%0A     * @param %3CD%3E Document type%0A
     
@@ -5166,69 +5166,8 @@
 atic
- %3CT extends Document & HasName, D extends Document & HasName%3E
  Str
@@ -5175,33 +5175,39 @@
 ng relativeName(
-D
+HasName
  document, final
@@ -5209,25 +5209,41 @@
 final Class%3C
-T
+? extends HasName
 %3E from) %7B%0A  
@@ -5323,24 +5323,14 @@
 tic 
-Optional%3C
 String
-%3E
  nam
@@ -5339,102 +5339,194 @@
 rom(
-Document document) %7B%0A        return Cast.cast(document, HasName.class).map(HasName::
+HasName document) %7B%0A        if (document instanceof HasAlias) %7B%0A            return ((HasAlias) document).getJavaName();%0A        %7D else %7B%0A            return document.
 getName
+(
 );%0A
+        %7D%0A
     

Clean up trait usage in DocumentUtil