package cn.com.qmth.stmms.biz.mark.utils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;

/**
 * 一个评卷任务集合的状态控制工具
 * 
 * @author luoshi
 *
 */
public class TaskCache {

    private volatile ReentrantLock lock;

    private Map<Integer, TaskStatus> libraryMap;

    private SetMultimap<Integer, Integer> studentMap;

    public Map<Integer, TaskStatus> finishLibrary;

    private SetMultimap<Integer, Integer> finishStudent;

    private AtomicInteger applyCount, finishCount;

    private AtomicLong applyTime, finishTime;

    public TaskCache() {
        this.lock = new ReentrantLock();
        this.libraryMap = new ConcurrentHashMap<>();
        this.finishLibrary = new ConcurrentHashMap<>();
        this.studentMap = HashMultimap.create();
        this.finishStudent = HashMultimap.create();

        this.applyCount = new AtomicInteger(0);
        this.finishCount = new AtomicInteger(0);
        this.applyTime = new AtomicLong(0);
        this.finishTime = new AtomicLong(0);
    }

    public boolean apply(Integer libraryId, Integer studentId, Integer markerId) {
        long start = System.currentTimeMillis();
        try {
            lock.lock();

            if (finishLibrary.containsKey(libraryId)) {
                return false;
            }
            if (finishStudent.containsEntry(studentId, markerId)) {
                return false;
            }
            if (studentMap.containsEntry(studentId, markerId)) {
                return false;
            }
            if (libraryMap.get(libraryId) != null) {
                return false;
            }

            libraryMap.put(libraryId, new TaskStatus(libraryId, studentId, markerId));
            studentMap.put(studentId, markerId);
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            lock.unlock();

            applyCount.incrementAndGet();
            applyTime.addAndGet(System.currentTimeMillis() - start);
        }
    }

    public boolean hasApplied(Integer libraryId, Integer markerId) {
        TaskStatus status = libraryMap.get(libraryId);
        return status != null && status.getMarkerId().equals(markerId);
    }

    public void finish(Integer libraryId, Integer studentId, Integer markerId) {
        long start = System.currentTimeMillis();
        try {
            lock.lock();

            libraryMap.remove(libraryId);
            studentMap.remove(studentId, markerId);
            finishLibrary.put(libraryId, new TaskStatus(libraryId, studentId, markerId));
            finishStudent.put(studentId, markerId);
        } catch (Exception e) {
        } finally {
            lock.unlock();

            finishCount.incrementAndGet();
            finishTime.addAndGet(System.currentTimeMillis() - start);
        }
    }

    public int applyCount() {
        return libraryMap.size();
    }

    public int applyCount(Integer markerId) {
        int count = 0;
        for (TaskStatus status : libraryMap.values()) {
            if (status.getMarkerId().equals(markerId)) {
                count++;
            }
        }
        return count;
    }

    public void clearApply() {
        try {
            lock.lock();

            libraryMap.clear();
            studentMap.clear();
        } catch (Exception e) {
        } finally {
            lock.unlock();
        }
    }

    public void clearApply(Integer markerId) {
        try {
            lock.lock();

            Set<Integer> set = new HashSet<>();
            for (TaskStatus status : libraryMap.values()) {
                if (status.getMarkerId().equals(markerId)) {
                    studentMap.remove(status.getStudentId(), status.getMarkerId());
                    set.add(status.getLibraryId());
                }
            }
            for (Integer libraryId : set) {
                libraryMap.remove(libraryId);
            }
        } catch (Exception e) {
        } finally {
            lock.unlock();
        }
    }

    public void reset() {
        try {
            lock.lock();

            libraryMap.clear();
            studentMap.clear();
            finishLibrary.clear();
            finishStudent.clear();
        } catch (Exception e) {
        } finally {
            lock.unlock();
        }
    }

    public void reset(Integer markerId) {
        try {
            lock.lock();

            Set<Integer> set = new HashSet<>();
            for (TaskStatus status : libraryMap.values()) {
                if (status.getMarkerId().equals(markerId)) {
                    studentMap.remove(status.getStudentId(), status.getMarkerId());
                    set.add(status.getLibraryId());
                }
            }
            for (Integer libraryId : set) {
                libraryMap.remove(libraryId);
            }

            set.clear();
            for (TaskStatus status : finishLibrary.values()) {
                if (status.getMarkerId().equals(markerId)) {
                    finishStudent.remove(status.getStudentId(), status.getMarkerId());
                    set.add(status.getLibraryId());
                }
            }
            for (Integer libraryId : set) {
                finishLibrary.remove(libraryId);
            }
        } catch (Exception e) {
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(50);
        TaskCache cache = new TaskCache();
        List<Integer> libraryList = new ArrayList<>(100000);
        for (int i = 1; i <= 100000; i++) {
            libraryList.add(i);
        }

        List<MarkerThread> threads = new ArrayList<>(100);
        for (int i = 1; i <= 100; i++) {
            MarkerThread thread = new MarkerThread(cache, libraryList, i);
            threads.add(thread);
            executor.submit(thread);
        }

        executor.shutdown();
        while (!executor.isTerminated()) {
            Thread.sleep(10000);
            System.out.println("finish count: " + cache.finishLibrary.size());
        }
        int count = 0;
        for (MarkerThread thread : threads) {
            count += thread.librarySet.size();
        }
        if (count != libraryList.size()) {
            System.out.println("library count error: " + count);
        }

        System.out.println("apply avg time: " + (cache.applyTime.get() * 1.0 / cache.applyCount.get()));
        System.out.println("finish avg time: " + (cache.finishTime.get() * 1.0 / cache.finishCount.get()));
    }

    static class MarkerThread implements Runnable {

        private TaskCache cache;

        private Integer id;

        private List<Integer> libraryList;

        private Set<Integer> librarySet;

        private Set<Integer> studentSet;

        public MarkerThread(TaskCache cache, List<Integer> libraryList, Integer id) {
            this.cache = cache;
            this.libraryList = libraryList;
            this.id = id;
            this.librarySet = new HashSet<>();
            this.studentSet = new HashSet<>();
        }

        @Override
        public void run() {
            Integer libraryId = null;
            while ((libraryId = getLibrary()) != null) {
                if (librarySet.contains(libraryId)) {
                    System.out.println("library redo: " + libraryId + "," + id);
                }
                Integer studentId = (libraryId + 1) / 2;
                if (studentSet.contains(studentId)) {
                    System.out.println("student redo: " + libraryId + "," + studentId + "," + id);
                }

                // long time = Math.max((long) (Math.random() * 5000), 500);
                // try {
                // Thread.sleep(time);
                // } catch (InterruptedException e) {
                // e.printStackTrace();
                // }

                if (!cache.hasApplied(libraryId, id)) {
                    System.out.println("applyed error: " + studentId + "," + id);
                }
                cache.finish(libraryId, studentId, id);

                librarySet.add(libraryId);
                studentSet.add(studentId);
            }
        }

        private Integer getLibrary() {
            for (Integer libraryId : libraryList) {
                if (cache.apply(libraryId, (libraryId + 1) / 2, id)) {
                    return libraryId;
                }
            }
            return null;
        }

    }

}
