/*
 *  Copyright 1999-2019 Seata.io Group.
 *
 *  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 io.seata.server.lock;

import io.seata.common.XID;
import io.seata.common.util.CollectionUtils;
import io.seata.common.util.StringUtils;
import io.seata.core.exception.TransactionException;
import io.seata.core.lock.Locker;
import io.seata.core.lock.RowLock;
import io.seata.core.model.LockStatus;
import io.seata.server.session.BranchSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * The type Abstract lock manager.
 *
 * @author zhangsen
 */
public abstract class AbstractLockManager implements LockManager {

	/**
	 * The constant LOGGER.
	 */
	protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractLockManager.class);

	@Override
	public boolean acquireLock(BranchSession branchSession) throws TransactionException {
		return acquireLock(branchSession, true, false);
	}

	@Override
	public boolean acquireLock(BranchSession branchSession, boolean autoCommit, boolean skipCheckLock)
			throws TransactionException {
		if (branchSession == null) {
			throw new IllegalArgumentException("branchSession can't be null for memory/file locker.");
		}
		String lockKey = branchSession.getLockKey();
		if (StringUtils.isNullOrEmpty(lockKey)) {
			// no lock
			return true;
		}
		// get locks of branch
		List<RowLock> locks = collectRowLocks(branchSession);
		if (CollectionUtils.isEmpty(locks)) {
			// no lock
			return true;
		}
		return getLocker(branchSession).acquireLock(locks, autoCommit, skipCheckLock);
	}

	@Override
	public boolean releaseLock(BranchSession branchSession) throws TransactionException {
		if (branchSession == null) {
			throw new IllegalArgumentException("branchSession can't be null for memory/file locker.");
		}
		List<RowLock> locks = collectRowLocks(branchSession);
		try {
			return getLocker(branchSession).releaseLock(locks);
		}
		catch (Exception t) {
			LOGGER.error("unLock error, branchSession:{}", branchSession, t);
			return false;
		}
	}

	@Override
	public boolean isLockable(String xid, String resourceId, String lockKey) throws TransactionException {
		if (StringUtils.isBlank(lockKey)) {
			// no lock
			return true;
		}
		List<RowLock> locks = collectRowLocks(lockKey, resourceId, xid);
		try {
			return getLocker().isLockable(locks);
		}
		catch (Exception t) {
			LOGGER.error("isLockable error, xid:{} resourceId:{}, lockKey:{}", xid, resourceId, lockKey, t);
			return false;
		}
	}

	@Override
	public void cleanAllLocks() throws TransactionException {
		getLocker().cleanAllLocks();
	}

	/**
	 * Gets locker.
	 * @return the locker
	 */
	protected Locker getLocker() {
		return getLocker(null);
	}

	/**
	 * Gets locker.
	 * @param branchSession the branch session
	 * @return the locker
	 */
	protected abstract Locker getLocker(BranchSession branchSession);

	@Override
	public List<RowLock> collectRowLocks(BranchSession branchSession) {
		if (branchSession == null || StringUtils.isBlank(branchSession.getLockKey())) {
			return Collections.emptyList();
		}

		String lockKey = branchSession.getLockKey();
		String resourceId = branchSession.getResourceId();
		String xid = branchSession.getXid();
		long transactionId = branchSession.getTransactionId();
		long branchId = branchSession.getBranchId();

		return collectRowLocks(lockKey, resourceId, xid, transactionId, branchId);
	}

	/**
	 * Collect row locks list.
	 * @param lockKey the lock key
	 * @param resourceId the resource id
	 * @param xid the xid
	 * @return the list
	 */
	protected List<RowLock> collectRowLocks(String lockKey, String resourceId, String xid) {
		return collectRowLocks(lockKey, resourceId, xid, XID.getTransactionId(xid), null);
	}

	/**
	 * Collect row locks list.
	 * @param lockKey the lock key
	 * @param resourceId the resource id
	 * @param xid the xid
	 * @param transactionId the transaction id
	 * @param branchID the branch id
	 * @return the list
	 */
	protected List<RowLock> collectRowLocks(String lockKey, String resourceId, String xid, Long transactionId,
			Long branchID) {
		List<RowLock> locks = new ArrayList<>();

		String[] tableGroupedLockKeys = lockKey.split(";");
		for (String tableGroupedLockKey : tableGroupedLockKeys) {
			int idx = tableGroupedLockKey.indexOf(":");
			if (idx < 0) {
				return locks;
			}
			String tableName = tableGroupedLockKey.substring(0, idx);
			String mergedPKs = tableGroupedLockKey.substring(idx + 1);
			if (StringUtils.isBlank(mergedPKs)) {
				return locks;
			}
			String[] pks = mergedPKs.split(",");
			if (pks == null || pks.length == 0) {
				return locks;
			}
			for (String pk : pks) {
				if (StringUtils.isNotBlank(pk)) {
					RowLock rowLock = new RowLock();
					rowLock.setXid(xid);
					rowLock.setTransactionId(transactionId);
					rowLock.setBranchId(branchID);
					rowLock.setTableName(tableName);
					rowLock.setPk(pk);
					rowLock.setResourceId(resourceId);
					locks.add(rowLock);
				}
			}
		}
		return locks;
	}

	@Override
	public void updateLockStatus(String xid, LockStatus lockStatus) {
		this.getLocker().updateLockStatus(xid, lockStatus);
	}

}
