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

import java.util.ArrayList;
import java.util.Set;

import org.apache.geode.annotations.internal.MakeNotStatic;
import org.apache.geode.cache.CommitConflictException;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.internal.cache.locks.TXLockId;
import org.apache.geode.internal.cache.locks.TXLockService;
import org.apache.geode.internal.cache.locks.TXRegionLockRequest;

/**
 * TXLockRequest represents all the locks that need to be made for a single transaction.
 *
 *
 * @since GemFire 4.0
 *
 */
public class TXLockRequest {
  private boolean localLockHeld;
  private TXLockId distLockId;
  private IdentityArrayList localLocks; // of TXRegionLockRequest
  private ArrayList<TXRegionLockRequest> distLocks; // of TXRegionLockRequest
  private Set otherMembers;

  public TXLockRequest() {
    localLockHeld = false;
    distLockId = null;
    localLocks = null;
    distLocks = null;
    otherMembers = null;
  }

  void setOtherMembers(Set s) {
    otherMembers = s;
  }

  public void addLocalRequest(TXRegionLockRequest req) {
    if (localLocks == null) {
      localLocks = new IdentityArrayList();
    }
    localLocks.add(req);
  }

  public TXRegionLockRequest getRegionLockRequest(String regionFullPath) {
    if (localLocks == null || regionFullPath == null) {
      return null;
    }
    for (final TXRegionLockRequestImpl rlr : (Iterable<TXRegionLockRequestImpl>) localLocks) {
      if (rlr.getRegionFullPath().equals(regionFullPath)) {
        return rlr;
      }
    }
    return null;
  }

  void addDistributedRequest(TXRegionLockRequest req) {
    if (distLocks == null) {
      distLocks = new ArrayList<>();
    }
    distLocks.add(req);
  }

  public void obtain(InternalDistributedSystem system) throws CommitConflictException {
    if (localLocks != null && !localLocks.isEmpty()) {
      txLocalLock(localLocks);
      localLockHeld = true;
    }
    if (distLocks != null && !distLocks.isEmpty()) {
      distLockId = TXLockService.createDTLS(system).txLock(distLocks, otherMembers);
    }
  }

  /**
   * Release any local locks obtained by this request
   */
  public void releaseLocal() {
    if (localLockHeld) {
      txLocalRelease(localLocks);
      localLockHeld = false;
    }
  }

  /**
   * Release any distributed locks obtained by this request
   */
  public void releaseDistributed(InternalDistributedSystem system) {
    if (distLockId != null) {
      try {
        TXLockService txls = TXLockService.createDTLS(system);
        txls.release(distLockId);
      } catch (IllegalStateException ignore) {
        // IllegalStateException: TXLockService cannot be created
        // until connected to distributed system
        // could be thrown if a jvm is disconnected from the ds,
        // and tries to createDTLS() during clean up
      }
      distLockId = null;
    }
  }

  public TXLockId getDistributedLockId() {
    return distLockId;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append(getClass().getCanonicalName()).append("@").append(System.identityHashCode(this));
    sb.append(" RegionLockRequests:");
    if (localLocks != null) {
      for (final Object localLock : localLocks) {
        TXRegionLockRequest rlr = (TXRegionLockRequest) localLock;
        sb.append(" TXRegionLockRequest:");
        sb.append(rlr.getRegionFullPath()).append(" keys:").append(rlr.getKeys());
      }
    }
    return sb.toString();
  }

  public void cleanup(InternalDistributedSystem system) {
    releaseLocal();
    releaseDistributed(system);
  }

  @MakeNotStatic
  private static final TXReservationMgr resMgr = new TXReservationMgr(true);

  /**
   * @param localLocks is a list of TXRegionLockRequest instances
   */
  private static void txLocalLock(IdentityArrayList localLocks) throws CommitConflictException {
    resMgr.makeReservation(localLocks);
  }

  private static void txLocalRelease(IdentityArrayList localLocks) {
    resMgr.releaseReservation(localLocks);
  }
}
