/*
 * 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.modules.session.internal.common;

import java.util.Map;

import jakarta.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.geode.cache.Cache;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionFactory;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.execute.FunctionService;
import org.apache.geode.modules.session.catalina.callback.LocalSessionCacheLoader;
import org.apache.geode.modules.session.catalina.callback.LocalSessionCacheWriter;
import org.apache.geode.modules.util.RegionConfiguration;
import org.apache.geode.modules.util.RegionHelper;
import org.apache.geode.modules.util.TouchPartitionedRegionEntriesFunction;
import org.apache.geode.modules.util.TouchReplicatedRegionEntriesFunction;

/**
 * Class which defines a peer-to-peer cache
 */
public class PeerToPeerSessionCache extends AbstractSessionCache {

  private static final Logger LOG = LoggerFactory.getLogger(PeerToPeerSessionCache.class.getName());

  private final Cache cache;

  private static final String DEFAULT_REGION_ATTRIBUTES_ID = RegionShortcut.REPLICATE.toString();

  private static final Boolean DEFAULT_ENABLE_LOCAL_CACHE = false;

  /**
   * Constructor
   *
   */
  public PeerToPeerSessionCache(Cache cache, Map<CacheProperty, Object> properties) {
    super();
    this.cache = cache;

    /**
     * Set some default properties for this cache if they haven't already been set
     */
    this.properties.put(CacheProperty.REGION_ATTRIBUTES_ID, DEFAULT_REGION_ATTRIBUTES_ID);
    this.properties.put(CacheProperty.ENABLE_LOCAL_CACHE, DEFAULT_ENABLE_LOCAL_CACHE);
    this.properties.putAll(properties);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void initialize() {
    // Register Functions
    registerFunctions();

    // Create or retrieve the region
    createOrRetrieveRegion();

    /**
     * If local cache is enabled, create the local region fronting the session region and set it as
     * the operating region; otherwise, use the session region directly as the operating region.
     */
    boolean enableLocalCache = (Boolean) properties.get(CacheProperty.ENABLE_LOCAL_CACHE);
    operatingRegion = enableLocalCache ? createOrRetrieveLocalRegion() : sessionRegion;

    // Create or retrieve the statistics
    createStatistics();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public GemFireCache getCache() {
    return cache;
  }

  @Override
  public boolean isClientServer() {
    return false;
  }

  private void registerFunctions() {
    // Register the touch partitioned region entries function if it is not already registered
    if (!FunctionService.isRegistered(TouchPartitionedRegionEntriesFunction.ID)) {
      FunctionService.registerFunction(new TouchPartitionedRegionEntriesFunction());
    }

    // Register the touch replicated region entries function if it is not already registered
    if (!FunctionService.isRegistered(TouchReplicatedRegionEntriesFunction.ID)) {
      FunctionService.registerFunction(new TouchReplicatedRegionEntriesFunction());
    }
  }

  private void createOrRetrieveRegion() {
    // Create the RegionConfiguration
    RegionConfiguration configuration = createRegionConfiguration();

    // Attempt to retrieve the region
    // If it already exists, validate it
    // If it doesn't already exist, create it
    Region region = cache.getRegion((String) properties.get(CacheProperty.REGION_NAME));
    if (region == null) {
      // Create the region
      region = RegionHelper.createRegion(cache, configuration);
      LOG.info("Created new session region: {}", region);
    } else {
      // Validate the existing region
      LOG.info("Retrieved existing session region: {}", region);
      RegionHelper.validateRegion(cache, configuration, region);
    }

    // Set the session region
    sessionRegion = region;
  }

  /**
   * Create a local region fronting the main region.
   *
   */
  private Region<String, HttpSession> createOrRetrieveLocalRegion() {
    // Attempt to retrieve the fronting region
    String frontingRegionName = sessionRegion.getName() + "_local";
    Region<String, HttpSession> frontingRegion = cache.getRegion(frontingRegionName);

    if (frontingRegion == null) {
      // Create the region factory
      RegionFactory<String, HttpSession> factory =
          cache.createRegionFactory(RegionShortcut.LOCAL_HEAP_LRU);

      // Add the cache loader and writer
      factory.setCacheLoader(new LocalSessionCacheLoader(sessionRegion));
      factory.setCacheWriter(new LocalSessionCacheWriter(sessionRegion));

      // Create the region
      frontingRegion = factory.create(frontingRegionName);
      LOG.info("Created new local session region: {}", frontingRegion);
    } else {
      LOG.info("Retrieved existing local session region: {}", frontingRegion);
    }

    return frontingRegion;
  }
}
