/**
 * Copyright (C) 2006-2018 Talend Inc. - www.talend.com
 *
 * 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 org.talend.sdk.component.studio.metadata;

import java.util.HashMap;
import java.util.Map;

import org.talend.core.model.components.IComponent;
import org.talend.sdk.component.server.front.model.ConfigTypeNode;
import org.talend.sdk.component.server.front.model.ConfigTypeNodes;
import org.talend.sdk.component.studio.Lookups;

/**
 * DOC cmeng class global comment. Detailled comment
 */
public class TaCoKitCache {

    private IComponent tacokitGuessSchemaComponent;

    private ConfigTypeNodes configTypeNodesCache;

    private Map<String, ConfigTypeNode> configTypeNodeMapCache;

    /**
     * Stores family ConfigTypeNode. A key represents family name. Value - family ConfigTypeNode
     * It is initialized and filled up lazily
     */
    private Map<String, ConfigTypeNode> familyConfigTypes;

    public TaCoKitCache() {
        // nothing to do
    }

    public ConfigTypeNode getFamilyNode(final ConfigTypeNode configNode) {
        if (configNode == null) {
            return null;
        }
        String parentId = configNode.getParentId();
        if (parentId == null) {
            return configNode;
        }
        return getFamilyNode(getConfigTypeNodeMap().get(parentId));
    }

    /**
     * Retrieves ConfigTypeNode from cache for specified parameters
     * 
     * @param familyName ConfigTypeNode family name
     * @param nodeName ConfigTypeNode name
     * @param configurationType ConfigTypeNode type, either dataset or datastore
     * @return ConfigTypeNode for specified parameters
     */
    public ConfigTypeNode getConfigTypeNode(final String familyName, final String nodeName,
            final String configurationType) {
        if (familyConfigTypes == null) {
            fillFamilyConfig();
        }
        ConfigTypeNode familyConfig = familyConfigTypes.get(familyName);
        if (familyConfig == null) {
            return null;
        }
        return findConfigTypeNode(familyConfigTypes.get(familyName), nodeName, configurationType);
    }

    /**
     * Finds only family ConfigTypeNode and puts them to {@link #familyConfigTypes}
     */
    private void fillFamilyConfig() {
        Map<String, ConfigTypeNode> allNodes = getConfigTypeNodeMap();
        familyConfigTypes = new HashMap<>();
        allNodes.values().stream().filter(c -> c.getParentId() == null).forEach(
                c -> familyConfigTypes.put(c.getName(), c));
    }

    /**
     * Finds required ConfigTypeNode by its name and configuration types (dataset or datastore) in ConfigTypeNode tree.
     * 
     * @param current a node which is visited
     * @param nodeName a node name of required ConfigTypeNode
     * @param configurationType configuration type of required ConfigTypeNode. It makes sense,
     * when datastore and dataset may have the same name within one component family
     * @return required ConfigTypeNode
     */
    private ConfigTypeNode findConfigTypeNode(final ConfigTypeNode current, final String nodeName,
            final String configurationType) {
        // node is found
        if (current.getName().equals(nodeName) && current.getConfigurationType().equals(configurationType)) {
            return current;
        }
        // it is leaf, but node wasn't found in this branch
        if (current.getEdges().isEmpty()) {
            return null;
        }
        for (String edge : current.getEdges()) {
            ConfigTypeNode node = findConfigTypeNode(configTypeNodeMapCache.get(edge), nodeName, configurationType);
            if (node != null) {
                return node;
            }
        }
        // node wasn't found at all
        return null;
    }

    public ConfigTypeNodes getConfigTypeNodes() {
        if (configTypeNodesCache == null) {
            configTypeNodesCache = Lookups.client().v1().configurationType().getRepositoryModel();
        }
        return configTypeNodesCache;
    }

    public Map<String, ConfigTypeNode> getConfigTypeNodeMap() {
        if (configTypeNodeMapCache == null || configTypeNodeMapCache.isEmpty()) {
            configTypeNodeMapCache = getConfigTypeNodes().getNodes();
        }
        return configTypeNodeMapCache;
    }

    public IComponent getTaCoKitGuessSchemaComponent() {
        return this.tacokitGuessSchemaComponent;
    }

    public void setTaCoKitGuessSchemaComponent(final IComponent guessSchemaComponent) {
        this.tacokitGuessSchemaComponent = guessSchemaComponent;
    }

    public void clearCache() {
        configTypeNodesCache = null;
        if (configTypeNodeMapCache != null) {
            configTypeNodeMapCache.clear();
        }
    }

}

@@ -1628,16 +1628,22 @@
         
+final 
 String p
@@ -1674,24 +1674,24 @@
 ParentId();%0A
-
         if (
@@ -2279,32 +2279,171 @@
 gurationType) %7B%0A
+        if (configurationType == null) %7B%0A            throw new IllegalArgumentException(%22configurationType should not be null%22);%0A        %7D%0A
         if (fami
@@ -2509,32 +2509,38 @@
       %7D%0A        
+final 
 ConfigTypeNode f
@@ -2911,16 +2911,22 @@
         
+final 
 Map%3CStri
@@ -3865,16 +3865,41 @@
 ame) && 
+configurationType.equals(
 current.
@@ -3924,33 +3924,8 @@
 pe()
-.equals(configurationType
 )) %7B
@@ -4115,16 +4115,22 @@
    for (
+final 
 String e
@@ -4169,16 +4169,22 @@
         
+final 
 ConfigTy
@@ -4196,16 +4196,36 @@
 e node =
+%0A                   
  findCon

fix potential NPE