/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.ambari.server.stack;

import org.apache.ambari.server.state.AutoDeployInfo;
import org.apache.ambari.server.state.ClientConfigFileDefinition;
import org.apache.ambari.server.state.CommandScriptDefinition;
import org.apache.ambari.server.state.ComponentInfo;
import org.apache.ambari.server.state.CustomCommandDefinition;
import org.apache.ambari.server.state.DependencyInfo;
import org.junit.Test;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static org.easymock.EasyMock.createNiceMock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

/**
 * ComponentModule unit test case.
 */
public class ComponentModuleTest {

  @Test
  public void testResolve_CommandScript() {
    CommandScriptDefinition commandScript = new CommandScriptDefinition();
    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setCommandScript(commandScript);
    assertSame(commandScript, resolveComponent(info, parentInfo).getModuleInfo().getCommandScript());

    // child has value set, parent value is null
    info.setCommandScript(commandScript);
    parentInfo.setCommandScript(null);
    assertSame(commandScript, resolveComponent(info, parentInfo).getModuleInfo().getCommandScript());

    // value set in both parent and child; child overwrites
    CommandScriptDefinition commandScript2 = createNiceMock(CommandScriptDefinition.class);
    info.setCommandScript(commandScript);
    parentInfo.setCommandScript(commandScript2);
    assertSame(commandScript, resolveComponent(info, parentInfo).getModuleInfo().getCommandScript());
  }

  @Test
  public void testResolve_DisplayName() {
    String displayName = "foo";

    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setDisplayName(displayName);
    assertEquals(displayName, resolveComponent(info, parentInfo).getModuleInfo().getDisplayName());

    // child has value set, parent value is null
    info.setDisplayName(displayName);
    parentInfo.setDisplayName(null);
    assertEquals(displayName, resolveComponent(info, parentInfo).getModuleInfo().getDisplayName());

    // value set in both parent and child; child overwrites
    String displayName2 = "foo2";
    info.setDisplayName(displayName2);
    parentInfo.setDisplayName(displayName);
    assertEquals(displayName2, resolveComponent(info, parentInfo).getModuleInfo().getDisplayName());
  }

  @Test
  public void testResolve_ClientConfigFiles() {
    List<ClientConfigFileDefinition> clientConfigs = new ArrayList<ClientConfigFileDefinition>();
    ClientConfigFileDefinition clientConfig1 = new ClientConfigFileDefinition();
    clientConfig1.setType("type1");
    clientConfig1.setDictionaryName("dictName1");
    clientConfig1.setFileName("filename1");
    ClientConfigFileDefinition clientConfig2 = new ClientConfigFileDefinition();
    clientConfig1.setType("type1");
    clientConfig1.setDictionaryName("dictName1");
    clientConfig1.setFileName("filename1");
    clientConfigs.add(clientConfig1);
    clientConfigs.add(clientConfig2);

    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setClientConfigFiles(clientConfigs);
    assertEquals(clientConfigs, resolveComponent(info, parentInfo).getModuleInfo().getClientConfigFiles());

    // child has value set, parent value is null
    info.setClientConfigFiles(clientConfigs);
    parentInfo.setClientConfigFiles(null);
    assertEquals(clientConfigs, resolveComponent(info, parentInfo).getModuleInfo().getClientConfigFiles());

    // value set in both parent and child; child overwrites with no merge
    List<ClientConfigFileDefinition> clientConfigs2 = new ArrayList<ClientConfigFileDefinition>();
    ClientConfigFileDefinition clientConfig3 = new ClientConfigFileDefinition();
    clientConfig3.setType("type1");
    clientConfig3.setDictionaryName("dictName1");
    clientConfig3.setFileName("DIFFERENT filename");
    clientConfigs2.add(clientConfig3);

    info.setClientConfigFiles(clientConfigs2);
    parentInfo.setClientConfigFiles(clientConfigs);
    assertEquals(clientConfigs2, resolveComponent(info, parentInfo).getModuleInfo().getClientConfigFiles());
  }

  @Test
  public void testResolve_Category() {
    String category = "foo";

    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setCategory(category);
    assertEquals(category, resolveComponent(info, parentInfo).getModuleInfo().getCategory());

    // child has value set, parent value is null
    info.setCategory(category);
    parentInfo.setCategory(null);
    assertEquals(category, resolveComponent(info, parentInfo).getModuleInfo().getCategory());

    // value set in both parent and child; child overwrites
    String category2 = "foo2";
    info.setCategory(category2);
    parentInfo.setCategory(category);
    assertEquals(category2, resolveComponent(info, parentInfo).getModuleInfo().getCategory());
  }

  @Test
  public void testResolve_Cardinality() {
    String cardinality = "foo";

    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setCardinality(cardinality);
    assertEquals(cardinality, resolveComponent(info, parentInfo).getModuleInfo().getCardinality());

    // child has value set, parent value is null
    info.setCardinality(cardinality);
    parentInfo.setCardinality(null);
    assertEquals(cardinality, resolveComponent(info, parentInfo).getModuleInfo().getCardinality());

    // value set in both parent and child; child overwrites
    String cardinality2 = "foo2";
    info.setCardinality(cardinality2);
    parentInfo.setCardinality(cardinality);
    assertEquals(cardinality2, resolveComponent(info, parentInfo).getModuleInfo().getCardinality());
  }

  @Test
  public void testResolve_AutoDeploy() {
    AutoDeployInfo autoDeployInfo = new AutoDeployInfo();
    autoDeployInfo.setEnabled(true);
    autoDeployInfo.setCoLocate("foo/bar");
    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setAutoDeploy(autoDeployInfo);
    assertEquals(autoDeployInfo, resolveComponent(info, parentInfo).getModuleInfo().getAutoDeploy());

    // child has value set, parent value is null
    info.setAutoDeploy(autoDeployInfo);
    parentInfo.setAutoDeploy(null);
    assertEquals(autoDeployInfo, resolveComponent(info, parentInfo).getModuleInfo().getAutoDeploy());

    // value set in both parent and child; child overwrites
    AutoDeployInfo autoDeployInfo2 = new AutoDeployInfo();
    info.setAutoDeploy(autoDeployInfo);
    parentInfo.setAutoDeploy(autoDeployInfo2);
    assertEquals(autoDeployInfo, resolveComponent(info, parentInfo).getModuleInfo().getAutoDeploy());
  }


  @Test
  public void testResolve_Dependencies() {
    List<DependencyInfo> dependencies = new ArrayList<DependencyInfo>();
    DependencyInfo dependency1 = new DependencyInfo();
    dependency1.setName("service/one");
    DependencyInfo dependency2 = new DependencyInfo();
    dependency2.setName("service/two");
    dependencies.add(dependency1);
    dependencies.add(dependency2);

    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setDependencies(dependencies);
    assertEquals(dependencies, resolveComponent(info, parentInfo).getModuleInfo().getDependencies());

    // child has value set, parent value is null
    info.setDependencies(dependencies);
    parentInfo.setDependencies(null);
    assertEquals(dependencies, resolveComponent(info, parentInfo).getModuleInfo().getDependencies());

    // value set in both parent and child; merge parent and child
    //todo: currently there is no way to remove an inherited dependency
    List<DependencyInfo> dependencies2 = new ArrayList<DependencyInfo>();
    DependencyInfo dependency3 = new DependencyInfo();
    dependency3.setName("service/two");
    DependencyInfo dependency4 = new DependencyInfo();
    dependency4.setName("service/four");
    dependencies2.add(dependency3);
    dependencies2.add(dependency4);

    info.setDependencies(dependencies2);
    parentInfo.setDependencies(dependencies);

    List<DependencyInfo> resolvedDependencies = resolveComponent(info, parentInfo).getModuleInfo().getDependencies();
    assertEquals(3, resolvedDependencies.size());
    assertTrue(resolvedDependencies.contains(dependency1));
    assertTrue(resolvedDependencies.contains(dependency3));
    assertTrue(resolvedDependencies.contains(dependency4));
  }

  @Test
  public void testResolve_CustomCommands() throws Exception {
    List<CustomCommandDefinition> commands = new ArrayList<CustomCommandDefinition>();
    CustomCommandDefinition command1 = new CustomCommandDefinition();
    setPrivateField(command1, "name", "one");
    CustomCommandDefinition command2 = new CustomCommandDefinition();
    setPrivateField(command2, "name", "two");
    commands.add(command1);
    commands.add(command2);

    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setCustomCommands(commands);
    assertEquals(commands, resolveComponent(info, parentInfo).getModuleInfo().getCustomCommands());

    // child has value set, parent value is null
    info.setCustomCommands(commands);
    parentInfo.setCustomCommands(null);
    assertEquals(commands, resolveComponent(info, parentInfo).getModuleInfo().getCustomCommands());

    // value set in both parent and child; merge parent and child
    //todo: currently there is no way to remove an inherited command
    List<CustomCommandDefinition> commands2 = new ArrayList<CustomCommandDefinition>();
    CustomCommandDefinition command3 = new CustomCommandDefinition();
    // override command 2
    setPrivateField(command3, "name", "two");
    CustomCommandDefinition command4 = new CustomCommandDefinition();
    setPrivateField(command4, "name", "four");
    commands2.add(command3);
    commands2.add(command4);

    info.setCustomCommands(commands2);
    parentInfo.setCustomCommands(commands);

    List<CustomCommandDefinition> resolvedCommands = resolveComponent(info, parentInfo).getModuleInfo().getCustomCommands();
    assertEquals(3, resolvedCommands.size());
    assertTrue(resolvedCommands.contains(command1));
    assertTrue(resolvedCommands.contains(command3));
    assertTrue(resolvedCommands.contains(command4));
  }

  @Test
  // merging of config dependencies is different than other non-module merges in that the collections aren't
  // merged if any config dependency is specified in the child.  So, the merged result is either the child
  // dependencies or if null, the parent dependencies.
  public void testResolve_ConfigDependencies() {
    List<String> dependencies = new ArrayList<String>();
    String dependency1 = "one";
    String dependency2 = "two";
    dependencies.add(dependency1);
    dependencies.add(dependency2);

    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setConfigDependencies(dependencies);
    assertEquals(dependencies, resolveComponent(info, parentInfo).getModuleInfo().getConfigDependencies());

    // child has value set, parent value is null
    info.setConfigDependencies(dependencies);
    parentInfo.setConfigDependencies(null);
    assertEquals(dependencies, resolveComponent(info, parentInfo).getModuleInfo().getConfigDependencies());

    // value set in both parent and child; merge parent and child
    List<String> dependencies2 = new ArrayList<String>();
    String dependency3 = "two";
    String dependency4 = "four";
    dependencies2.add(dependency3);
    dependencies2.add(dependency4);

    info.setConfigDependencies(dependencies2);
    parentInfo.setConfigDependencies(dependencies);

    List<String> resolvedDependencies = resolveComponent(info, parentInfo).getModuleInfo().getConfigDependencies();
    assertEquals(2, resolvedDependencies.size());
    assertTrue(resolvedDependencies.contains(dependency3));
    assertTrue(resolvedDependencies.contains(dependency4));
  }

  @Test
  // merging of "client to update configs", whatever that means, is different than most other non-module merges
  // in that the collections aren't merged if any "client to update configs" is specified in the child.
  // So, the merged result is either the child collection or if null, the parent collection.
  public void testResolve_ClientToUpdateConfigs() {
    List<String> clientsToUpdate = new ArrayList<String>();
    String client1 = "one";
    String client2 = "two";
    clientsToUpdate.add(client1);
    clientsToUpdate.add(client2);

    ComponentInfo info = new ComponentInfo();
    ComponentInfo parentInfo = new ComponentInfo();

    // parent has value set, child value is null
    parentInfo.setClientsToUpdateConfigs(clientsToUpdate);
    assertEquals(clientsToUpdate, resolveComponent(info, parentInfo).getModuleInfo().getClientsToUpdateConfigs());

    // child has value set, parent value is null
    info.setClientsToUpdateConfigs(clientsToUpdate);
    parentInfo.setClientsToUpdateConfigs(null);
    assertEquals(clientsToUpdate, resolveComponent(info, parentInfo).getModuleInfo().getClientsToUpdateConfigs());

    // value set in both parent and child; merge parent and child
    List<String> clientsToUpdate2 = new ArrayList<String>();
    String client3 = "two";
    String client4 = "four";
    clientsToUpdate2.add(client3);
    clientsToUpdate2.add(client4);

    info.setClientsToUpdateConfigs(clientsToUpdate2);
    parentInfo.setClientsToUpdateConfigs(clientsToUpdate);

    List<String> resolvedClientsToUpdate = resolveComponent(info, parentInfo).getModuleInfo().getClientsToUpdateConfigs();
    assertEquals(2, resolvedClientsToUpdate.size());
    assertTrue(resolvedClientsToUpdate.contains(client3));
    assertTrue(resolvedClientsToUpdate.contains(client4));
  }

  @Test
  public void testGetId() {
    ComponentInfo info = new ComponentInfo();
    info.setName("foo");

    ComponentModule component = new ComponentModule(info);
    assertEquals("foo", component.getId());
  }

  @Test
  public void testIsDeleted() {
    // default value
    ComponentInfo info = new ComponentInfo();
    info.setName("foo");

    ComponentModule component = new ComponentModule(info);
    assertFalse(component.isDeleted());

    // explicit value
    info = new ComponentInfo();
    info.setName("foo");
    info.setDeleted(true);

    component = new ComponentModule(info);
    assertTrue(component.isDeleted());
  }

  private ComponentModule resolveComponent(ComponentInfo info, ComponentInfo parentInfo) {
    info.setName("FOO");
    parentInfo.setName("FOO");

    ComponentModule component = new ComponentModule(info);
    ComponentModule parentComponent = new ComponentModule(parentInfo);

    component.resolve(parentComponent, Collections.<String, StackModule>emptyMap(), Collections.<String, ServiceModule>emptyMap());

    return component;
  }

  private void setPrivateField(Object o, String field, Object value) throws Exception{
    Class<?> c = o.getClass();
    Field f = c.getDeclaredField(field);
    f.setAccessible(true);
    f.set(o, value);
  }
}

