/*
 * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.yangtools.binding.data.codec.impl;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.opendaylight.yangtools.util.ClassLoaderUtils;
import org.opendaylight.yangtools.yang.binding.ChildOf;
import org.opendaylight.yangtools.yang.binding.DataRoot;
import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.model.api.ChoiceNode;
import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;

class SchemaRootCodecContext extends DataContainerCodecContext<SchemaContext> {

    private final LoadingCache<Class<?>, DataContainerCodecContext<?>> children = CacheBuilder.newBuilder().build(
            new CacheLoader<Class<?>, DataContainerCodecContext<?>>() {
                @Override
                public DataContainerCodecContext<?> load(final Class<?> key) {
                    Class<Object> parent = ClassLoaderUtils.findFirstGenericArgument(key, ChildOf.class);
                    Preconditions.checkArgument(DataRoot.class.isAssignableFrom(parent));
                    QName qname = BindingReflections.findQName(key);
                    DataSchemaNode childSchema = schema().getDataChildByName(qname);
                    return DataContainerCodecPrototype.from(key, childSchema, factory()).get();
                }
            });

    private SchemaRootCodecContext(final DataContainerCodecPrototype<SchemaContext> dataPrototype) {
        super(dataPrototype);
    }

    /**
     * Creates RootNode from supplied CodecContextFactory.
     *
     * @param factory
     *            CodecContextFactory
     * @return
     */
    static SchemaRootCodecContext create(final CodecContextFactory factory) {
        DataContainerCodecPrototype<SchemaContext> prototype = DataContainerCodecPrototype.rootPrototype(factory);
        return new SchemaRootCodecContext(prototype);
    }

    @Override
    protected DataContainerCodecContext<?> getStreamChild(final Class<?> childClass) {
        return children.getUnchecked(childClass);
    }

    @Override
    protected Optional<DataContainerCodecContext<?>> getPossibleStreamChild(final Class<?> childClass) {
        throw new UnsupportedOperationException("Not supported");
    }

    @Override
    protected YangInstanceIdentifier.PathArgument getDomPathArgument() {
        throw new UnsupportedOperationException();
    }

    @Override
    protected NodeCodecContext getYangIdentifierChild(final YangInstanceIdentifier.PathArgument arg) {
        // FIXME: Optimize this
        QName childQName = arg.getNodeType();
        DataSchemaNode childSchema = schema().getDataChildByName(childQName);
        Preconditions.checkArgument(childSchema != null, "Argument %s is not valid child of %s", arg, schema());
        if (childSchema instanceof DataNodeContainer || childSchema instanceof ChoiceNode) {
            Class<?> childCls = factory().getRuntimeContext().getClassForSchema(childSchema);
            DataContainerCodecContext<?> childNode = getStreamChild(childCls);
            return childNode;
        } else {
            throw new UnsupportedOperationException();
        }
    }

    @Override
    protected Object dataFromNormalizedNode(final NormalizedNode<?, ?> normalizedNode) {
        throw new UnsupportedOperationException("Could not create Binding data representation for root");
    }
}
@@ -946,16 +946,29 @@
 entifier
+.PathArgument
 ;%0Aimport
@@ -1451,16 +1451,23 @@
 children
+ByClass
  = Cache
@@ -2152,16 +2152,952 @@
    %7D);%0A%0A
+    private final LoadingCache%3CQName, DataContainerCodecContext%3C?%3E%3E childrenByQName = CacheBuilder.newBuilder().build(%0A        new CacheLoader%3CQName, DataContainerCodecContext%3C?%3E%3E() %7B%0A            @Override%0A            public DataContainerCodecContext%3C?%3E load(final QName qname) %7B%0A                final DataSchemaNode childSchema = schema().getDataChildByName(qname);%0A                Preconditions.checkArgument(childSchema != null, %22Argument %25s is not valid child of %25s%22, qname, schema());%0A%0A                if (childSchema instanceof DataNodeContainer %7C%7C childSchema instanceof ChoiceNode) %7B%0A                    final Class%3C?%3E childCls = factory().getRuntimeContext().getClassForSchema(childSchema);%0A                    return getStreamChild(childCls);%0A                %7D else %7B%0A                    throw new UnsupportedOperationException(%22Unsupported child type %22 + childSchema.getClass());%0A                %7D%0A            %7D%0A        %7D);%0A%0A
     priv
@@ -3761,16 +3761,23 @@
 children
+ByClass
 .getUnch
@@ -4022,39 +4022,16 @@
 otected 
-YangInstanceIdentifier.
 PathArgu
@@ -4190,39 +4190,16 @@
 d(final 
-YangInstanceIdentifier.
 PathArgu
@@ -4222,646 +4222,63 @@
     
-// FIXME: Optimize this%0A        QName childQName = arg.getNodeType();%0A        DataSchemaNode childSchema = schema().getDataChildByName(childQName);%0A        Preconditions.checkArgument(childSchema != null, %22Argument %25s is not valid child of %25s%22, arg, schema());%0A        if (childSchema instanceof DataNodeContainer %7C%7C childSchema instanceof ChoiceNode) %7B%0A            Class%3C?%3E childCls = factory().getRuntimeContext().getClassForSchema(childSchema);%0A            DataContainerCodecContext%3C?%3E childNode = getStreamChild(childCls);%0A            return childNode;%0A        %7D else %7B%0A            throw new UnsupportedOperationException();%0A        %7D
+return childrenByQName.getUnchecked(arg.getNodeType());
 %0A   

optimize SchemaRootCodecContext.getYangIdentifierChild()