package com.pwt.netty.conponents;

import cn.hutool.core.thread.NamedThreadFactory;
import com.pwt.Globals;
import com.pwt.common.utils.spring.SpringUtils;
import com.pwt.netty.MessageSender;
import com.pwt.netty.message2.Alarm;
import com.pwt.netty.message2.AlarmStatistics;
import com.pwt.web.service.IPwtAlarmRecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 报警中心
 */
@Slf4j
@Component
public class AlarmCenter {

    private volatile int level  = 99;

    private static volatile AlarmCenter INSTANCE;
    private IPwtAlarmRecordService recordService;

    @Autowired
    public void setRecordService(IPwtAlarmRecordService recordService) {
        this.recordService = recordService;
    }

    public static AlarmCenter getInstance() {
        if (null == INSTANCE) {
            synchronized (AlarmCenter.class) {
                if (null == INSTANCE) {
                    INSTANCE = new AlarmCenter();
                }
            }
        }
        return INSTANCE;
    }

    private AlarmCenter() {
        recordService = SpringUtils.getBean(IPwtAlarmRecordService.class);
    }

    /**
     * key: device type
     * value: alarm count
     */
    public static final Map<Integer, AtomicInteger> ALARMS_COUNT = new ConcurrentHashMap<>();

    static {
        ALARMS_COUNT.put(1, new AtomicInteger(0));
        ALARMS_COUNT.put(2, new AtomicInteger(0));
        ALARMS_COUNT.put(3, new AtomicInteger(0));
        ALARMS_COUNT.put(4, new AtomicInteger(0));
        ALARMS_COUNT.put(5, new AtomicInteger(0));
    }

    private static final Set<Alarm> ALARMS = ConcurrentHashMap.newKeySet();

    private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(1, 3, 3L, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(1000), new NamedThreadFactory("alarmCenterWorker", true), new ThreadPoolExecutor.DiscardPolicy() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if(log.isDebugEnabled()) {
                log.debug("discard a task");
            }
        }
    });


    public synchronized void addAlarm(Alarm alarm) {
        if(alarm == null) {
            return;
        }
        if(ALARMS.contains(alarm)) {
            return;
        }
        if(alarm.getLevel() < level) {
            level = alarm.getLevel();
        }
        tryCancelStationTask();
        // add alarm
        ALARMS.add(alarm);
        ALARMS_COUNT.computeIfAbsent(alarm.getSourceDeviceType(), k -> new AtomicInteger(0)).incrementAndGet();
        sendAlarms(Collections.singletonList(alarm), false);
    }

    private void tryCancelStationTask() {
        if(level < 2) {
            return;
        }
        final Future<?> exchangeFuture = Globals.exchangeFuture;
        if(exchangeFuture != null && !exchangeFuture.isDone()) {
            exchangeFuture.cancel(true);
            // 清除换电记录缓存，此时需要手动添加换电记录
            Globals.tempBatteryPlace = null;
        }
    }


    public synchronized void removeAlarm(Alarm alarm) {
        if(alarm == null ) {
            return;
        }

        if(!alarm.getIsDelete()) {
            alarm.setIsDelete(true);
        }

        // delete alarm
        if(ALARMS.remove(alarm)) {
            if(ALARMS_COUNT.get(alarm.getSourceDeviceType()).decrementAndGet() < 0) {
                log.info("alarm count is less than 0, device type: {}", alarm.getSourceDeviceType());
                ALARMS_COUNT.get(alarm.getSourceDeviceType()).set(0);
            }
            calculateLevel();
            sendAlarms(Collections.singletonList(alarm), true);
        }
    }

    public synchronized void addAlarms(List<Alarm> alarmList) {
        if(alarmList == null || alarmList.size() == 0) {
            return;
        }
        List<Alarm> addAlarms = new LinkedList<>();
        for (Alarm alarm:
             alarmList) {
            if(!ALARMS.contains(alarm)) {
                addAlarms.add(alarm);
                // add alarm
                ALARMS.add(alarm);
                ALARMS_COUNT.computeIfAbsent(alarm.getSourceDeviceType(), k -> new AtomicInteger(0)).incrementAndGet();
                if(alarm.getLevel() < level) {
                    level = alarm.getLevel();
                }
            }
        }
        if(addAlarms.size() == 0) {
            return;
        }
        tryCancelStationTask();
        sendAlarms(addAlarms, false);
    }

    public synchronized void removeAlarms(List<Alarm> alarmList) {
        if(alarmList == null || alarmList.size() == 0) {
            return;
        }
        List<Alarm> removeAlarms = new LinkedList<>();
        for (Alarm alarm:
            alarmList) {
            if(ALARMS.contains(alarm)) {
                removeAlarms.add(alarm);
                // delete alarm
                ALARMS.remove(alarm);
                if(ALARMS_COUNT.get(alarm.getSourceDeviceType()).decrementAndGet() <0) {
                    log.info("alarm count is less than 0, device type: {}", alarm.getSourceDeviceType());
                    ALARMS_COUNT.get(alarm.getSourceDeviceType()).set(0);
                }
            }
        }

        if(removeAlarms.size() == 0) {
            return;
        }
        calculateLevel();
        sendAlarms(removeAlarms, true);
    }

    private void calculateLevel() {
        final int oldLevel = level;
        level = 99;
        ALARMS_COUNT.forEach((l, c) -> {
            if(oldLevel > l && c.get() > 0) {
                level = l;
            }
        });
    }

    private void sendAlarms(Collection<Alarm> alarms, final boolean isRemove) {
        if (alarms.size() == 0) {
            return;
        }

        if(!isRemove) {
            EXECUTOR.execute(new Runnable() {
                @Override
                public void run() {
                    recordService.insertPwtAlarmRecords(alarms);
                }
            });
        }
        Globals.uiData.setAlarms(ALARMS);
        MessageSender.send(MessageSender.MSG_ALARM, alarms);

        Set<AlarmStatistics> alarmStats = Globals.uiData.getAlarmStats();
        ALARMS_COUNT.forEach((k, v) -> {
            AlarmStatistics alarmStatistics = new AlarmStatistics();
            alarmStatistics.setSourceDeviceType(k);
            alarmStatistics.setCount(v.get());
            if(alarmStats.contains(alarmStatistics)) {
                alarmStats.remove(alarmStatistics);
                alarmStats.add(alarmStatistics);
            }
        });
        MessageSender.send(MessageSender.MSG_ALARM_STATISTICS, alarmStats);
    }

    public synchronized int getLevel() {
        return level;
    }
}
