package cn.com.qmth.stmms.admin.exam;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.com.qmth.stmms.biz.exam.model.Exam;
import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
import cn.com.qmth.stmms.biz.exam.model.Marker;
import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
import cn.com.qmth.stmms.biz.exam.service.ExamService;
import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
import cn.com.qmth.stmms.biz.exam.service.MarkerService;
import cn.com.qmth.stmms.biz.file.service.FileService;
import cn.com.qmth.stmms.biz.lock.LockService;
import cn.com.qmth.stmms.biz.mark.model.ArbitrateHistory;
import cn.com.qmth.stmms.biz.mark.model.ArbitrationDTO;
import cn.com.qmth.stmms.biz.mark.model.MarkLibrary;
import cn.com.qmth.stmms.biz.mark.model.MarkResult;
import cn.com.qmth.stmms.biz.mark.model.Task;
import cn.com.qmth.stmms.biz.mark.query.ArbitrateHistorySearchQuery;
import cn.com.qmth.stmms.biz.mark.service.ArbitrateHistoryService;
import cn.com.qmth.stmms.biz.mark.service.MarkLibraryService;
import cn.com.qmth.stmms.biz.mark.service.MarkService;
import cn.com.qmth.stmms.biz.mark.service.TaskService;
import cn.com.qmth.stmms.biz.user.service.UserService;
import cn.com.qmth.stmms.common.annotation.Logging;
import cn.com.qmth.stmms.common.annotation.RoleRequire;
import cn.com.qmth.stmms.common.domain.WebUser;
import cn.com.qmth.stmms.common.enums.HistoryStatus;
import cn.com.qmth.stmms.common.enums.LibraryStatus;
import cn.com.qmth.stmms.common.enums.LockType;
import cn.com.qmth.stmms.common.enums.LogType;
import cn.com.qmth.stmms.common.enums.MarkMode;
import cn.com.qmth.stmms.common.enums.Role;
import cn.com.qmth.stmms.common.utils.RequestUtils;

@Controller("arbitrateController")
@RequestMapping("/admin/exam/arbitrate")
public class ArbitrateController extends BaseExamController {

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

    public static final int UN_SELECTIVE_SCORE = -1;

    @Autowired
    private UserService userService;

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private MarkGroupService groupService;

    @Autowired
    private ArbitrateHistoryService arbitrateService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private MarkService markService;

    @Autowired
    private LockService lockService;

    @Autowired
    private ExamQuestionService questionService;

    @Autowired
    private ExamService examService;

    @Autowired
    private FileService fileService;

    @Autowired
    private MarkerService markerService;

    @Autowired
    private MarkLibraryService libraryService;

    @Value("${slice.split.config}")
    private String splitConfig;

    @Value("${marker.forceMode}")
    private String forceMarkMode;

    // 并发处理互斥锁
    private Map<Integer, Integer> currentTaskMap = new HashMap<Integer, Integer>();

    @RequestMapping
    public String list(Model model, HttpServletRequest request, ArbitrateHistorySearchQuery query) {
        int examId = getSessionExamId(request);
        WebUser wu = RequestUtils.getWebUser(request);
        List<ExamSubject> subjectList = getExamSubject(examId, wu);
        if (subjectList.isEmpty()) {
            return "redirect:/admin/exam/mark";
        }
        query.setExamId(examId);
        if (StringUtils.isBlank(query.getSubjectCode()) && !subjectList.isEmpty()) {
            query.setSubjectCode(subjectList.get(0).getCode());
        }
        List<MarkGroup> groupList = groupService.findByExamAndSubjectWithDouble(query.getExamId(),
                query.getSubjectCode());
        if (!groupList.isEmpty()) {
            for (MarkGroup group : groupList) {
                group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(query.getExamId(),
                        group.getSubjectCode(), false, group.getNumber()));
            }
            query.addSort("examNumber", Direction.ASC);
            query = arbitrateService.findByQuery(query);
            for (ArbitrateHistory history : query.getResult()) {
                if (history.getUserId() != null) {
                    history.setUser(userService.findById(history.getUserId()));
                }
            }
        }
        model.addAttribute("query", query);
        model.addAttribute("subjectList", subjectList);
        model.addAttribute("groupList", groupList);
        model.addAttribute("statusList", HistoryStatus.getOptionList());
        return "modules/exam/arbitrateList";
    }

    @RequestMapping("/getSetting")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    @ResponseBody
    public JSONObject getSetting(Model model, HttpServletRequest request,
            @RequestParam(required = false) String subjectCode, @RequestParam(required = false) Integer groupNumber,
            @RequestParam(required = false) Integer historyId) {
        int examId = getSessionExamId(request);
        WebUser wu = RequestUtils.getWebUser(request);
        releaseByUser(wu.getUser().getId());
        JSONObject setting = new JSONObject();
        MarkGroup group = null;
        if (historyId != null) {
            ArbitrateHistory history = arbitrateService.findById(historyId);
            if (history == null) {
                return setting;
            }
            subjectCode = history.getSubjectCode();
            group = groupService.findOne(examId, subjectCode, history.getGroupNumber());
            setting.accumulate("selective", group.isSelective());
        }
        if (subjectCode != null && groupNumber != null) {
            group = groupService.findOne(examId, subjectCode, groupNumber);
            setting.accumulate("selective", group.isSelective());
        }
        ExamSubject examSubject = subjectService.find(examId, subjectCode);
        setting.accumulate("fileServer", fileService.getFileServer());
        Exam exam = examService.findById(examId);
        setting.accumulate("examType", exam.getType());
        setting.accumulate("userName", wu.getName());
        JSONObject subject = new JSONObject();
        subject.accumulate("name", examSubject.getName());
        subject.accumulate("code", examSubject.getCode());
        examSubject.setPaperAnswerUrl(fileService);
        subject.accumulate("paperUrl", examSubject.getPaperUrl() == null ? "" : examSubject.getPaperUrl());
        subject.accumulate("answerUrl", examSubject.getAnswerUrl() == null ? "" : examSubject.getAnswerUrl());
        setting.accumulate("subject", subject);
        setting.accumulate("splitConfig", getSplitConfig());
        setting.accumulate("enableSplit",
                examSubject.getEnableSplit() == null ? exam.isEnableSplit() : examSubject.getEnableSplit());
        setMode(setting, group, exam);
        return setting;
    }

    private void setMode(JSONObject setting, MarkGroup group, Exam exam) {
        MarkMode mode = null;
        boolean forceMode = false;
        if (MarkMode.findByName(forceMarkMode) != null) {
            // 全局配置的强制评卷模式
            mode = MarkMode.findByName(forceMarkMode);
            forceMode = true;
        } else {
            // 没有全局配置，优先从考试取强制评卷模式
            if (exam != null && exam.getMarkMode() != null) {
                mode = exam.getMarkMode();
                forceMode = true;
            } else if (group != null && group.getMarkMode() != null) {
                // 没有全局配置，优先从大题配置取强制评卷模式
                mode = group.getMarkMode();
                forceMode = true;
            }
            if (mode == null) {
                mode = MarkMode.COMMON;
            }
        }
        setting.accumulate("mode", mode);
        setting.accumulate("forceMode", forceMode);
    }

    private double[] getSplitConfig() {
        String strs[] = splitConfig.split(",");
        double[] config = new double[strs.length];
        for (int i = 0; i < strs.length; i++) {
            config[i] = Double.parseDouble(strs[i]);
        }
        return config;
    }

    @Logging(menu = "打回仲裁任务", type = LogType.UPDATE)
    @RequestMapping(value = "/back", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONObject back(HttpServletRequest request, @RequestParam Integer historyId) {
        JSONObject obj = new JSONObject();
        ArbitrateHistory history = arbitrateService.findById(historyId);
        if (history != null) {
            if (history.getStatus() != HistoryStatus.WAITING) {
                obj.accumulate("success", false);
                obj.accumulate("message", "该仲裁卷已被处理");
            } else {
                if (subjectCheck(history.getSubjectCode(), RequestUtils.getWebUser(request))) {
                    // TODO - 需要区分全体打回和单个任务打回，下个版本实现
                    obj.accumulate("success", true);
                } else {
                    obj.accumulate("success", false);
                    obj.accumulate("message", "没有操作该仲裁卷的权限");
                }
            }
        } else {
            obj.accumulate("success", false);
            obj.accumulate("message", "该仲裁记录不存在");
        }
        return obj;
    }

    @RequestMapping(value = "/getHistory", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public List<Task> getHistory(HttpServletRequest request, @RequestParam String subjectCode,
            @RequestParam Integer groupNumber, @RequestParam(required = false) Integer pageNumber,
            @RequestParam(required = false) Integer pageSize, @RequestParam String order, @RequestParam String sort,
            @RequestParam(required = false, defaultValue = "false") Boolean isTag,
            @RequestParam(required = false) String secretNumber) {
        int examId = getSessionExamId(request);
        WebUser wu = RequestUtils.getWebUser(request);
        Direction d = Direction.DESC;
        Sort querySort = null;
        if (sort.equalsIgnoreCase("ASC")) {
            d = Direction.ASC;
        }
        if (order.equals("markerTime")) {
            querySort = new Sort(d, "updateTime");
        } else if (order.equals("markerScore")) {
            querySort = new Sort(d, "totalScore").and(new Sort(Direction.DESC, "updateTime"));
        } else {
            querySort = new Sort(Direction.DESC, "updateTime");
        }
        MarkGroup group = groupService.findOne(examId, subjectCode, groupNumber);
        List<Task> list = new LinkedList<Task>();
        if (subjectCheck(subjectCode, wu) && group != null) {
            ArbitrateHistorySearchQuery query = new ArbitrateHistorySearchQuery();
            query.setExamId(examId);
            query.setSubjectCode(subjectCode);
            query.setGroupNumber(groupNumber);
            query.setStatus(HistoryStatus.MARKED);
            query.setUserId(wu.getUser().getId());
            query.setSecretNumber(secretNumber);
            query.setStartScroe(0.0);
            query.setPageNumber(pageNumber);
            query.setPageSize(pageSize);
            query.setSort(querySort);
            query = arbitrateService.findByQuery(query);
            for (ArbitrateHistory history : query.getResult()) {
                Task task = taskService.build(history, group);
                task.setPrevious(true);
                list.add(task);
            }
        }
        return list;
    }

    @RequestMapping("/getTask")
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public Task getTask(HttpServletRequest request, @RequestParam(required = false) String subjectCode,
            @RequestParam(required = false) Integer groupNumber, @RequestParam(required = false) Integer historyId) {
        int examId = getSessionExamId(request);
        WebUser wu = RequestUtils.getWebUser(request);
        if (historyId != null) {
            ArbitrateHistory history = arbitrateService.findById(historyId);
            if (history == null) {
                return new Task();
            }
            MarkGroup group = groupService.findOne(examId, history.getSubjectCode(), history.getGroupNumber());
            return taskService.build(history, group);
        }
        MarkGroup group = groupService.findOne(examId, subjectCode, groupNumber);
        if (subjectCheck(subjectCode, wu) && group != null) {
            ArbitrateHistorySearchQuery query = new ArbitrateHistorySearchQuery();
            query.setExamId(examId);
            query.setSubjectCode(subjectCode);
            query.setGroupNumber(groupNumber);
            query.setStatus(HistoryStatus.WAITING);
            query.setPageNumber(1);
            query.setPageSize(10);
            query.addSort("examNumber", Direction.ASC);
            while (true) {
                query = arbitrateService.findByQuery(query);
                if (query.getCurrentCount() == 0) {
                    break;
                }
                for (ArbitrateHistory history : query.getResult()) {
                    // 尝试领取该任务并上锁
                    if (setCurrent(history.getId(), wu.getUser().getId())) {
                        return taskService.build(history, group);
                    } else {
                        continue;
                    }
                }
                // 查询下一页
                query.setPageNumber(query.getPageNumber() + 1);
                continue;
            }
        }
        Task task = new Task();
        // task.setExist(false);
        return task;
    }

    @Logging(menu = "处理仲裁", type = LogType.UPDATE)
    @RequestMapping(value = "/saveTask", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONObject saveTask(HttpServletRequest request, @RequestBody MarkResult markResult) {
        WebUser wu = RequestUtils.getWebUser(request);
        JSONObject result = new JSONObject();
        ArbitrateHistory history = arbitrateService.findById(markResult.getLibraryId());
        if (history != null && subjectCheck(history.getSubjectCode(), wu)) {
            try {
                lockService.watch(LockType.EXAM_SUBJECT, history.getExamId(), history.getSubjectCode());
                lockService.watch(LockType.GROUP, history.getExamId(), history.getSubjectCode(),
                        history.getGroupNumber());
                lockService.waitlock(LockType.STUDENT, markResult.getStudentId());
                markService.processArbitrate(markResult, wu.getUser().getId());
                releaseTask(history.getId());
                result.accumulate("success", true);
                result.accumulate("status", status(request, history.getSubjectCode(), history.getGroupNumber()));
            } catch (Exception e) {
                log.error("ArbitrateController-处理仲裁卷出错", e);
                result.accumulate("success", false);
                result.accumulate("message", "评卷任务提交失败");
            } finally {
                lockService.unlock(LockType.STUDENT, markResult.getStudentId());
                lockService.unwatch(LockType.GROUP, history.getExamId(), history.getSubjectCode(),
                        history.getGroupNumber());
                lockService.unwatch(LockType.EXAM_SUBJECT, history.getExamId(), history.getSubjectCode());
            }
        } else {
            result.accumulate("message", "任务不存在或没有权限");
            result.accumulate("success", false);
        }
        return result;
    }

    @RequestMapping("/getStatus")
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONObject status(HttpServletRequest request, @RequestParam String subjectCode,
            @RequestParam Integer groupNumber) {
        JSONObject status = new JSONObject();
        int examId = getSessionExamId(request);
        WebUser wu = RequestUtils.getWebUser(request);
        MarkGroup group = groupService.findOne(examId, subjectCode, groupNumber);
        if (subjectCheck(subjectCode, wu) && group != null) {
            ArbitrateHistorySearchQuery query = new ArbitrateHistorySearchQuery();
            query.setExamId(examId);
            query.setSubjectCode(subjectCode);
            query.setGroupNumber(groupNumber);
            query.setStatus(HistoryStatus.WAITING);
            long totalCount = arbitrateService.countByQuery(query);

            query.setUserId(wu.getUser().getId());
            query.setStatus(HistoryStatus.MARKED);
            long markedCount = arbitrateService.countByQuery(query);

            status.accumulate("totalCount", totalCount);
            status.accumulate("markedCount", markedCount);
            status.accumulate("valid", (totalCount + markedCount) > 0);
        } else {
            status.accumulate("valid", false);
        }
        return status;
    }

    @RequestMapping("/getArbitrationList")
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public Object getList(HttpServletRequest request, @RequestParam Integer historyId) {
        ArbitrateHistory history = arbitrateService.findById(historyId);
        List<ArbitrationDTO> list = new ArrayList<ArbitrationDTO>();
        List<MarkLibrary> libraryList = libraryService.findByStudentAndGroup(history.getStudentId(),
                history.getGroupNumber());
        for (MarkLibrary library : libraryList) {
            if (library.getStatus() != LibraryStatus.WAITING) {
                Marker marker = markerService.findById(library.getMarkerId());
                marker.setUser(userService.findById(marker.getUserId()));
                list.add(new ArbitrationDTO(library, marker));
            }
        }
        return list;
    }

    @RequestMapping("/clear")
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public Object clear(HttpServletRequest request, @RequestParam(required = false) Integer libraryId) {
        WebUser wu = RequestUtils.getWebUser(request);
        JSONObject obj = new JSONObject();
        if (libraryId != null) {
            releaseTask(libraryId);
            obj.accumulate("success", true);
        } else {
            releaseByUser(wu.getUser().getId());
            obj.accumulate("success", true);
        }
        return obj;
    }

    private void releaseByUser(Integer userId) {
        Set<Integer> taskIds = new HashSet<>();
        taskIds.addAll(currentTaskMap.keySet());
        synchronized (currentTaskMap) {
            for (Integer taskId : taskIds) {
                Integer value = currentTaskMap.get(taskId);
                if (value != null && value.equals(userId)) {
                    currentTaskMap.remove(taskId);
                }
            }
        }
    }

    private void releaseTask(Integer taskId) {
        synchronized (currentTaskMap) {
            currentTaskMap.remove(taskId);
        }
    }

    private boolean setCurrent(Integer taskId, Integer userId) {
        Integer value = currentTaskMap.get(taskId);
        if (value == null) {
            synchronized (currentTaskMap) {
                value = currentTaskMap.get(taskId);
                if (value == null) {
                    currentTaskMap.put(taskId, userId);
                    return true;
                } else {
                    return false;
                }
            }
        } else {
            return false;
        }
    }

}
