/*-
 * ~~~~~~licensing~~~~~~
 * core-support-runtime
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.core.support.runtime.cluster;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.apache.camel.CamelContext;
import org.apache.camel.cluster.CamelClusterMember;
import org.apache.camel.support.cluster.AbstractCamelClusterView;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;

@Deprecated(since = "1.10")
public class HazelcastValueLockClusterView extends AbstractCamelClusterView {
    
    private static final transient Logger LOG = LoggerFactory.getLogger(HazelcastValueLockClusterView.class);
    
    private static final String LOCK_VALUE_DELIMITER = "_";
    
    private HazelcastInstance hazelcastInstance;
    private IMap<String,Object> cache;
    private long healthInterval;
    private TimeUnit healthIntervalUnit;
    private String lockName;
    
    private final ClusterMember localMember;
    private ScheduledExecutorService executor;
    private ScheduledFuture<?> task;
    
    private long currentId;
    private volatile boolean leader;
    
    protected HazelcastValueLockClusterView(HazelcastLockClusterService cluster, String namespace) {
        super(cluster, namespace);
        
        this.hazelcastInstance = cluster.getHazelcastInstance();
        this.cache = cluster.getCache();
        this.healthInterval = cluster.getHealthInterval();
        this.healthIntervalUnit = cluster.getHealthIntervalUnit();
        
        this.currentId = hazelcastInstance.getAtomicLong(cluster.getCacheName()).getAndIncrement();
        
        this.localMember = new ClusterMember();
        this.lockName = namespace;
    }

    @Override
    public Optional<CamelClusterMember> getLeader() {
        return this.localMember.isLeader() ? Optional.of(this.localMember) : Optional.empty();
    }

    @Override
    public CamelClusterMember getLocalMember() {
        return this.localMember;
    }

    @Override
    public List<CamelClusterMember> getMembers() {
        return new ArrayList<CamelClusterMember>() {{ add(localMember); }};
    }
    
    @Override
    protected void doStart() throws Exception {
        HazelcastLockClusterService service = getClusterService().unwrap(HazelcastLockClusterService.class);
        executor = getExecutor();
        
        task = executor.scheduleAtFixedRate(this::tryLock,
                TimeUnit.MILLISECONDS.convert(service.getAcquireLockDelay(), service.getAcquireLockDelayUnit()),
                TimeUnit.MILLISECONDS.convert(service.getAcquireLockInterval(), service.getAcquireLockIntervalUnit()),
                TimeUnit.MILLISECONDS);
    }
    
    protected ScheduledExecutorService getExecutor() {
        if (executor == null) {
            HazelcastLockClusterService service = getClusterService().unwrap(HazelcastLockClusterService.class);
            // Camel context should be set at this stage.
            final CamelContext context = ObjectHelper.notNull(getCamelContext(), "CamelContext");

            executor = context.getExecutorServiceManager().newSingleThreadScheduledExecutor(this, "HazelcastLockClusterService-" + service.getId());
        }

        return executor;
    }
    
    protected void tryLock() {
        if (isStarting() || isStarted()) {
            if (localMember.isLeader()) {
                leaderHealth();
            } else if (!isLeaderHealthy()) {
                tryLockProc();
            } else {
                LOG.trace("The leader is healthy, but the jackals are on alert! Key {}", lockName);
            }
        }
    }
    
    protected void leaderHealthWrite() {
        cache.put(lockName, getLockValueNext());
        LOG.trace("Leader health refresh on lock {}", lockName);
    }
    
    protected void leaderHealth() {
        if (getLockOwnerId() == currentId) {
            leaderHealthWrite();
            LOG.trace("Holding the lock on key {}", lockName);
        } else {
            LOG.error("Inconsistent lock on key {}. Leadership is reset!", lockName);
            leader = false;
            fireLeadershipChangedEvent(Optional.empty());
        }
    }
    
    protected boolean isLeaderHealthy() {
        boolean health = false;
        if (cache.containsKey(lockName)) {
            long leaderHealthCheck = getLockMilliseconds();
            long difference = healthIntervalUnit.convert(System.currentTimeMillis() - leaderHealthCheck, TimeUnit.MILLISECONDS);
            health = difference < healthInterval;
        } else {
            LOG.warn("Empty leader's health key {}", lockName);
        }
        return health;
    }
    
    protected void tryLockProc() {
        LOG.debug("Try to acquire a lock on key {}", lockName);
        
        if (cache.tryLock(lockName)) {
            leaderHealthWrite();
            if (getLockOwnerId() == currentId) {
                leader = true;
                fireLeadershipChangedEvent(Optional.ofNullable(localMember));
                LOG.info("Lock on key {} acquired", lockName);
            }
            cache.unlock(lockName);
        }
        
        if (!localMember.isLeader()) {
            LOG.debug("Lock on key {} NOT acquired", lockName);
            leader = false;
            fireLeadershipChangedEvent(Optional.empty());
        }
    }
    
    protected String getLockValueNext() {
        return String.valueOf(currentId) + LOCK_VALUE_DELIMITER + System.currentTimeMillis();
    }
    
    protected long getLockOwnerId() {
        return getLockPart(cache.get(lockName), 0);
    }
    
    protected long getLockMilliseconds() {
        return getLockPart(cache.get(lockName), 1);
    }

    protected long getLockPart(Object lockValue, int i) {
        if (lockValue != null && lockValue instanceof String) {
            String[] values =  ((String)lockValue).split(LOCK_VALUE_DELIMITER);
            if (values.length == 2) {
                return Long.parseLong(values[i]);
            } else {
                throw new IllegalArgumentException("Lock value = " + lockValue + " in unknown format");
            }
        }
        return 0L;
    }
    
    @Override
    protected void doStop() throws Exception {
        leader = false;
        fireLeadershipChangedEvent(Optional.empty());
        closeInternal();
        shutdownExecutors();
    }

    protected void closeInternal() {
        if (task != null) {
            task.cancel(true);
        }
        tryUnlock();
    }
    
    protected void shutdownExecutors() {
        CamelContext context = getCamelContext();
        if (executor != null) {
            if (context != null) {
                context.getExecutorServiceManager().shutdown(executor);
            } else {
                executor.shutdown();
            }
            executor = null;
        }
    }
   
    protected void tryUnlock() {
        LOG.debug("Unlock key {}", lockName);
        try {
            if (getLockOwnerId() == currentId && cache.tryLock(lockName)) {
                try {
                    cache.delete(lockName);
                    LOG.trace("Leader lock {} is cleared", lockName);
                } finally {
                    cache.unlock(lockName);
                }
                LOG.info("Unlocked key {}", lockName);
            } else {
                LOG.debug("Current thread is not owner of lock {}", lockName);
            }
        } catch (IllegalMonitorStateException e) {
            LOG.trace("Unlock error", e);
        }
    }

    public boolean isLeader() {
        return leader;
    }

    public void setLeader(boolean leader) {
        this.leader = leader;
    }
    
    private final class ClusterMember implements CamelClusterMember {
        @Override
        public boolean isLeader() {
            return leader;
        }

        @Override
        public boolean isLocal() {
            return true;
        }

        @Override
        public String getId() {
            return getClusterService().getId();
        }
    }
}
