package com.qmth.boot.core.concurrent.configuration;

import ch.qos.logback.classic.Level;
import com.qmth.boot.core.concurrent.annotation.Lockable;
import com.qmth.boot.core.concurrent.annotation.Locks;
import com.qmth.boot.core.concurrent.exception.TryLockFaileException;
import com.qmth.boot.core.concurrent.model.LockType;
import com.qmth.boot.core.concurrent.model.StackLock;
import com.qmth.boot.core.concurrent.service.ConcurrentService;
import com.qmth.boot.core.logger.context.LoggerContextService;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

/**
 * 自定义锁注解的定义执行过程
 */
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class LockAspectConfiguration {

    protected static final Logger log = LoggerFactory.getLogger(LockAspectConfiguration.class);

    private static final String LOCK_KEY_SEPARATOR = "_";

    private ConcurrentService concurrentService;

    public LockAspectConfiguration(ConcurrentService concurrentService, LoggerContextService loggerContextService,
            LockProperties lockProperties) {
        this.concurrentService = concurrentService;
        loggerContextService.setLevel(LockAspectConfiguration.class, Level.toLevel(lockProperties.getLogLevel()));
    }

    /***
     * 定义切入点拦截规则，拦截Lockable注解的业务方法
     */
    @Pointcut("@annotation(com.qmth.boot.core.concurrent.annotation.Lockable)")
    public void lockPointCut() {
    }

    /***
     * 定义切入点拦截规则，拦截Locks注解的业务方法
     */
    @Pointcut("@annotation(com.qmth.boot.core.concurrent.annotation.Locks)")
    public void locksPointCut() {
    }

    /**
     * AOP锁拦截
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    @Around("lockPointCut()")
    public Object lockPointProcess(ProceedingJoinPoint joinPoint) throws Throwable {
        return process(joinPoint);
    }

    @Around("locksPointCut()")
    public Object locksPointProcess(ProceedingJoinPoint joinPoint) throws Throwable {
        return process(joinPoint);
    }

    public Object process(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取切面相关信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();
        List<Lockable> list = new LinkedList<>();
        int count = 1;
        Locks annotationArray = targetMethod.getAnnotation(Locks.class);
        Lockable annotation = targetMethod.getAnnotation(Lockable.class);
        if (annotationArray != null && annotationArray.value().length > 0) {
            list.addAll(Arrays.asList(annotationArray.value()));
            count = annotationArray.value().length;
        } else if (annotation != null) {
            list.add(annotation);
        }
        // 已上锁堆栈
        Deque<StackLock> stack = new ArrayDeque<>(count);
        try {
            // 按顺序循环尝试上锁
            for (Lockable item : list) {
                // 支持SPEL生成key
                String key = StringUtils.trimToNull(item.name());
                Assert.notNull(key, "@Lock.name should not be blank");
                if (StringUtils.isNotEmpty(item.key())) {
                    key = key + LOCK_KEY_SEPARATOR + parseKey(item.key(), targetMethod, joinPoint.getArgs());
                }
                ReadWriteLock readWriteLock = concurrentService.getReadWriteLock(key);
                Lock lock = item.type() == LockType.READ ? readWriteLock.readLock() : readWriteLock.writeLock();
                log.debug("Start lock: key={}, type={}, timeout={}", key, item.type(), item.timeout());
                if (item.timeout() >= 0) {
                    // 定义了超时时间为尝试上锁模式，失败则抛出异常
                    if (!lock.tryLock(item.timeout(), TimeUnit.SECONDS)) {
                        throw new TryLockFaileException("Try lock faile: " + key);
                    }
                } else {
                    lock.lockInterruptibly();
                }
                stack.push(new StackLock(lock, key, item.type()));
            }
            return joinPoint.proceed();
        } catch (TryLockFaileException e) {
            throw e;
        } catch (Exception e) {
            log.error("Lock aspect error", e);
            throw new RuntimeException(e);
        } finally {
            while (!stack.isEmpty()) {
                StackLock lock = stack.pop();
                lock.getLock().unlock();
                log.debug("Finish unlock: key={}, type={}", lock.getKey(), lock.getType());
            }
        }
    }

    /**
     * 获取缓存的key
     * key 定义在注解上，支持SPEL表达式
     *
     * @return
     */
    private String parseKey(String key, Method method, Object[] args) {
        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        //使用SPEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SPEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        if (paraNameArr != null) {
            //把方法参数放入SPEL上下文中
            for (int i = 0; i < paraNameArr.length; i++) {
                context.setVariable(paraNameArr[i], args[i]);
            }
        }
        return parser.parseExpression(key).getValue(context, String.class);
    }

}
