/*
 * 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.geode.cache.configuration;

import static org.apache.geode.cache.Region.SEPARATOR;
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.apache.geode.internal.config.JAXBService;
import org.apache.geode.management.configuration.RegionType;


public class CacheConfigTest {

  private CacheConfig cacheConfig;
  private JAXBService service;
  private RegionConfig regionConfig;
  private String regionXml;
  private DeclarableType declarableWithString;
  private String declarableWithStringXml;
  private String classNameTypeXml;
  private DeclarableType declarableWithParam;
  private String declarableWithParamXml;
  private String cacheXml;

  @Before
  public void setUp() throws Exception {
    cacheConfig = new CacheConfig("1.0");
    cacheXml =
        "<cache version=\"1.0\" xsi:schemaLocation=\"http://geode.apache.org/schema/cache http://geode.apache.org/schema/cache/cache-1.0.xsd\" xmlns=\"http://geode.apache.org/schema/cache\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">";
    service = new JAXBService(CacheConfig.class);
    service.validateWithLocalCacheXSD();
    regionConfig = new RegionConfig();
    regionConfig.setName("regionA");
    regionConfig.setType(RegionType.REPLICATE);
    regionXml = "<region name=\"regionA\" refid=\"REPLICATE\">";

    classNameTypeXml = "<class-name>my.className</class-name>";
    declarableWithString = new DeclarableType("my.className", "{'key':'value'}");
    declarableWithStringXml =
        classNameTypeXml + "<parameter name=\"key\"><string>value</string></parameter>";

    declarableWithParam = new DeclarableType("my.className");
    ParameterType param = new ParameterType("key");
    param.setDeclarable(declarableWithString);
    declarableWithParam.getParameters().add(param);
    declarableWithParamXml = classNameTypeXml + "<parameter name=\"key\"><declarable>"
        + declarableWithStringXml + "</declarable></parameter>";
  }

  @After
  public void tearDown() throws Exception {
    // make sure the marshalled xml passed validation
    System.out.println(service.marshall(cacheConfig));
  }

  @Test
  public void indexType() {
    String xml = cacheXml + regionXml
        + "<index name=\"indexName\" expression=\"expression\" key-index=\"true\"/>"
        + "</region></cache>";

    cacheConfig = service.unMarshall(xml);
    RegionConfig.Index index = cacheConfig.getRegions().get(0).getIndexes().get(0);
    assertThat(index.isKeyIndex()).isTrue();
    assertThat(index.getName()).isEqualTo("indexName");
    assertThat(index.getExpression()).isEqualTo("expression");
    assertThat(index.getType()).isEqualTo("key");
  }


  @Test
  public void regionEntry() {
    String xml = cacheXml + regionXml + "<entry>" + "<key><string>key1</string></key>"
        + "<value><declarable>" + declarableWithStringXml + "</declarable></value>" + "</entry>"
        + "<entry>" + "<key><string>key2</string></key>" + "<value><declarable>"
        + declarableWithParamXml + "</declarable></value>" + "</entry>" + "</region></cache>";

    cacheConfig = service.unMarshall(xml);
    RegionConfig.Entry entry = cacheConfig.getRegions().get(0).getEntries().get(0);
    assertThat(entry.getKey().toString()).isEqualTo("key1");
    assertThat(entry.getValue().getDeclarable()).isEqualTo(declarableWithString);

    entry = cacheConfig.getRegions().get(0).getEntries().get(1);
    assertThat(entry.getKey().toString()).isEqualTo("key2");
    assertThat(entry.getValue().getDeclarable()).isEqualTo(declarableWithParam);
  }

  @Test
  public void cacheTransactionManager() {
    String xml = cacheXml + "<cache-transaction-manager>" + "<transaction-listener>"
        + declarableWithStringXml + "</transaction-listener>" + "<transaction-writer>"
        + declarableWithStringXml + "</transaction-writer>"
        + "</cache-transaction-manager></cache>";

    cacheConfig = service.unMarshall(xml);
    assertThat(cacheConfig.getCacheTransactionManager().getTransactionWriter())
        .isEqualTo(declarableWithString);
    assertThat(cacheConfig.getCacheTransactionManager().getTransactionListeners().get(0))
        .isEqualTo(declarableWithString);
  }

  @Test
  public void declarables() {
    String xml = cacheXml + "<region-attributes>" + "<cache-loader>" + declarableWithStringXml
        + "</cache-loader>" + "<cache-listener>" + declarableWithStringXml + "</cache-listener>"
        + "<cache-writer>" + declarableWithStringXml + "</cache-writer>" + "<compressor>"
        + classNameTypeXml + "</compressor>"
        + "<region-time-to-live><expiration-attributes timeout=\"0\"><custom-expiry>"
        + declarableWithStringXml + "</custom-expiry></expiration-attributes></region-time-to-live>"
        + "</region-attributes>" + "<function-service><function>" + declarableWithStringXml
        + "</function></function-service>" + "<initializer>" + declarableWithStringXml
        + "</initializer>" + "<pdx><pdx-serializer>" + declarableWithStringXml
        + "</pdx-serializer></pdx>" + "<cache-server><custom-load-probe>" + declarableWithStringXml
        + "</custom-load-probe></cache-server>" + "<gateway-conflict-resolver>"
        + declarableWithStringXml + "</gateway-conflict-resolver>"
        + "<gateway-receiver><gateway-transport-filter>" + declarableWithStringXml
        + "</gateway-transport-filter></gateway-receiver>" + "<async-event-queue id=\"queue\">"
        + "<gateway-event-substitution-filter>" + declarableWithStringXml
        + "</gateway-event-substitution-filter>" + "<gateway-event-filter>"
        + declarableWithStringXml + "</gateway-event-filter>" + "<async-event-listener>"
        + declarableWithStringXml + "</async-event-listener>" + "</async-event-queue>"
        + "<gateway-hub id=\"hub\"><gateway id=\"gateway\"><gateway-listener>"
        + declarableWithStringXml + "</gateway-listener></gateway></gateway-hub>" + "</cache>";

    cacheConfig = service.unMarshall(xml);

    assertThat(cacheConfig.getInitializer()).isEqualTo(declarableWithString);
    assertThat(cacheConfig.getFunctionService().getFunctions().get(0))
        .isEqualTo(declarableWithString);
    assertThat(cacheConfig.getPdx().getPdxSerializer()).isEqualTo(declarableWithString);
    assertThat(cacheConfig.getCacheServers().get(0).getCustomLoadProbe())
        .isEqualTo(declarableWithString);
    assertThat(cacheConfig.getGatewayConflictResolver()).isEqualTo(declarableWithString);
    assertThat(cacheConfig.getGatewayReceiver().getGatewayTransportFilters().get(0))
        .isEqualTo(declarableWithString);
    assertThat(cacheConfig.getGatewayHubs().get(0).getGateway().get(0).getGatewayListeners().get(0))
        .isEqualTo(declarableWithString);

    CacheConfig.AsyncEventQueue asyncEventQueue = cacheConfig.getAsyncEventQueues().get(0);
    assertThat(asyncEventQueue.getAsyncEventListener()).isEqualTo(declarableWithString);
    assertThat(asyncEventQueue.getGatewayEventFilters().get(0)).isEqualTo(declarableWithString);
    assertThat(asyncEventQueue.getGatewayEventSubstitutionFilter()).isEqualTo(declarableWithString);

    RegionAttributesType regionAttributes = cacheConfig.getRegionAttributes().get(0);
    assertThat(regionAttributes.getCacheListeners().get(0)).isEqualTo(declarableWithString);
    assertThat(regionAttributes.getCompressor().toString()).isEqualTo("my.className");
    assertThat(regionAttributes.getCacheLoader()).isEqualTo(declarableWithString);
    assertThat(regionAttributes.getCacheWriter()).isEqualTo(declarableWithString);
    assertThat(regionAttributes.getRegionTimeToLive().getCustomExpiry())
        .isEqualTo(declarableWithString);
  }

  @Test
  public void regionConfig() {
    cacheConfig = new CacheConfig("1.0");
    RegionConfig regionConfig = new RegionConfig();
    regionConfig.setName("test");
    regionConfig.setType(RegionType.REPLICATE);
    RegionAttributesType attributes = new RegionAttributesType();
    attributes.setCacheLoader(new DeclarableType("abc.Foo"));
    regionConfig.setRegionAttributes(attributes);
    cacheConfig.getRegions().add(regionConfig);

    // make sure the xml marshed by this config can be validated with xsd
    String xml = service.marshall(cacheConfig);

    CacheConfig newCache = service.unMarshall(xml);
    assertThat(cacheConfig).isEqualToComparingFieldByFieldRecursively(newCache);
  }

  @Test
  public void regionAttributeType() throws Exception {
    String xml = "<region name=\"test\">\n"
        + "        <region-attributes>\n"
        + "            <region-time-to-live>\n"
        + "                <expiration-attributes action=\"invalidate\" timeout=\"20\">\n"
        + "                    <custom-expiry>\n"
        + "                        <class-name>bar</class-name>\n"
        + "                    </custom-expiry>\n"
        + "                </expiration-attributes>\n"
        + "            </region-time-to-live>\n"
        + "            <entry-time-to-live>\n"
        + "                <expiration-attributes action=\"destroy\" timeout=\"10\">\n"
        + "                    <custom-expiry>\n"
        + "                        <class-name>foo</class-name>\n"
        + "                    </custom-expiry>\n"
        + "                </expiration-attributes>\n"
        + "            </entry-time-to-live>\n"
        + "        </region-attributes>\n"
        + "    </region>";

    RegionConfig regionConfig = service.unMarshall(xml, RegionConfig.class);
    RegionAttributesType.ExpirationAttributesType entryTimeToLive =
        regionConfig.getRegionAttributes().getEntryTimeToLive();
    assertThat(entryTimeToLive.getTimeout()).isEqualTo("10");
    assertThat(entryTimeToLive.getAction()).isEqualTo("destroy");
    assertThat(entryTimeToLive.getCustomExpiry().getClassName()).isEqualTo("foo");
    RegionAttributesType.ExpirationAttributesType regionTimeToLive =
        regionConfig.getRegionAttributes().getRegionTimeToLive();
    assertThat(regionTimeToLive.getTimeout()).isEqualTo("20");
    assertThat(regionTimeToLive.getAction()).isEqualTo("invalidate");
    assertThat(regionTimeToLive.getCustomExpiry().getClassName()).isEqualTo("bar");

    cacheConfig.getRegions().add(regionConfig);
  }

  @Test
  public void findRegionConfiguration() throws Exception {
    CacheConfig config = new CacheConfig();
    assertThat(config.findRegionConfiguration(SEPARATOR + "test")).isNull();
    assertThat(config.findRegionConfiguration("test")).isNull();
    assertThat(config.findRegionConfiguration("test" + SEPARATOR + "test1")).isNull();

    RegionConfig testRegion = new RegionConfig();
    testRegion.setName("test");
    config.getRegions().add(testRegion);

    assertThat(config.findRegionConfiguration(SEPARATOR + "test")).isNotNull();
    assertThat(config.findRegionConfiguration("test")).isNotNull();
    assertThat(config.findRegionConfiguration("test" + SEPARATOR + "test1")).isNull();

    RegionConfig test1Region = new RegionConfig();
    test1Region.setName("test1");
    testRegion.getRegions().add(test1Region);

    assertThat(config.findRegionConfiguration(SEPARATOR + "test")).isNotNull()
        .extracting(RegionConfig::getName).isEqualTo("test");
    assertThat(config.findRegionConfiguration("test")).isNotNull()
        .extracting(RegionConfig::getName).isEqualTo("test");
    assertThat(config.findRegionConfiguration("test" + SEPARATOR + "test1")).isNotNull()
        .extracting(RegionConfig::getName).isEqualTo("test1");
  }
}
