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

import java.util.Date;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
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.ExamSubject;
import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
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.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.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.auth.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.LockType;
import cn.com.qmth.stmms.common.enums.Role;
import cn.com.qmth.stmms.common.utils.RequestUtils;
import net.sf.json.JSONObject;

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

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

    @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;

    @Value("${slice.image.server}")
    private String sliceServer;

    @Value("${sheet.image.server}")
    private String sheetServer;

    @Value("${card.server}")
    private String cardServer;

    // 并发处理互斥锁
    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 (query.getSubjectCode() == null && !subjectList.isEmpty()) {
            query.setSubjectCode(subjectList.get(0).getCode());
        }
        subjectFilter(query, wu);
        List<MarkGroup> groupList = groupService.findByExamAndSubjectWithDouble(examId, query.getSubjectCode());
        if (query.getGroupNumber() == null && groupList.size() > 0) {
            query.setGroupNumber(groupList.get(0).getNumber());
        }
        for (MarkGroup group : groupList) {
            group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId,
                    group.getSubjectCode(), false, group.getNumber()));
        }
        query.orderByIdDesc();
        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("/process")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
    public String singleProcess(Model model, HttpServletRequest request, @RequestParam Integer historyId) {
        int examId = getSessionExamId(request);
        WebUser wu = RequestUtils.getWebUser(request);
        ArbitrateHistory history = arbitrateService.findById(historyId);
        if (history == null || !history.getExamId().equals(examId) || !subjectCheck(history.getSubjectCode(), wu)) {
            return "redirect:/admin/exam/arbitrate";
        }
        MarkGroup group = groupService.findOne(examId, history.getSubjectCode(), history.getGroupNumber());
        if (group == null) {
            return "redirect:/admin/exam/arbitrate";
        }
        releaseByUser(wu.getUser().getId());
        model.addAttribute("sheetServer", sheetServer);
        model.addAttribute("sliceServer", sliceServer);
        model.addAttribute("cardServer", cardServer);
        model.addAttribute("subject", subjectService.find(group.getExamId(), group.getSubjectCode()));
        model.addAttribute("group", group);
        model.addAttribute("history", history);
        return "modules/exam/arbitrateSingleProcess";
    }

    @RequestMapping("/batchProcess")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
    public String batchProcess(Model model, HttpServletRequest request, @RequestParam String subjectCode,
            @RequestParam Integer groupNumber) {
        int examId = getSessionExamId(request);
        WebUser wu = RequestUtils.getWebUser(request);
        if (!subjectCheck(subjectCode, wu)) {
            return "redirect:/admin/exam/arbitrate";
        }
        MarkGroup group = groupService.findOne(examId, subjectCode, groupNumber);
        if (group == null) {
            return "redirect:/admin/exam/arbitrate";
        }
        releaseByUser(wu.getUser().getId());
        model.addAttribute("sheetServer", sheetServer);
        model.addAttribute("sliceServer", sliceServer);
        model.addAttribute("cardServer", cardServer);
        model.addAttribute("subject", subjectService.find(group.getExamId(), group.getSubjectCode()));
        model.addAttribute("group", group);
        return "modules/exam/arbitrateBatchProcess";
    }

    @RequestMapping(value = "/back", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
    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 = "/history/{subjectCode}/{groupNumber}", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
    public List<Task> getHistory(HttpServletRequest request, @PathVariable String subjectCode,
            @PathVariable Integer groupNumber, @RequestParam(required = false) Integer pageNumber,
            @RequestParam(required = false) Integer pageSize) {
        int examId = getSessionExamId(request);
        WebUser wu = RequestUtils.getWebUser(request);
        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.setPageNumber(pageNumber);
            query.setPageSize(pageSize);
            query.orderByUpdateTimeDesc();
            query = arbitrateService.findByQuery(query);
            for (ArbitrateHistory history : query.getResult()) {
                list.add(taskService.build(history, group));
            }
        }
        return list;
    }

    @RequestMapping("/singleTask")
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
    public Task getSingleTask(HttpServletRequest request, @RequestParam Integer historyId) {
        int examId = getSessionExamId(request);
        WebUser wu = RequestUtils.getWebUser(request);
        ArbitrateHistory history = arbitrateService.findById(historyId);
        if (history != null && history.getExamId().equals(examId) && subjectCheck(history.getSubjectCode(), wu)) {
            MarkGroup group = groupService.findOne(history.getExamId(), history.getSubjectCode(),
                    history.getGroupNumber());
            return taskService.build(history, group);
        }
        Task task = new Task();
        task.setExist(false);
        return task;
    }

    @RequestMapping("/getTask")
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
    public Task getTask(HttpServletRequest request, @RequestParam String subjectCode,
            @RequestParam Integer groupNumber) {
        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);
            query.setPageNumber(1);
            query.setPageSize(10);
            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;
    }

    @RequestMapping(value = "/saveTask", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
    public JSONObject saveTask(HttpServletRequest request, @RequestBody Task task) {
        WebUser wu = RequestUtils.getWebUser(request);
        JSONObject result = new JSONObject();
        ArbitrateHistory history = arbitrateService.findById(task.getLibraryId());
        if (history != null && subjectCheck(history.getSubjectCode(), wu) && task.getTotalScore() >= 0
                && task.getScoreList() != null) {
            try {
                lockService.watch(LockType.GROUP, history.getExamId(), history.getSubjectCode(),
                        history.getGroupNumber());

                history.setUserId(wu.getUser().getId());
                history.setTotalScore(task.getTotalScore());
                history.setScoreList(task.getScoreList());
                history.setStatus(HistoryStatus.MARKED);
                history.setUpdateTime(new Date());
                markService.processArbitrate(history);
                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);
            } finally {
                lockService.unwatch(LockType.GROUP, history.getExamId(), history.getSubjectCode(),
                        history.getGroupNumber());
            }
        } else {
            result.accumulate("success", false);
        }
        return result;
    }

    @RequestMapping("/status")
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
    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("/clear")
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
    public Object clear(HttpServletRequest request, @RequestParam(required = false) Integer libraryId) {
        WebUser wu = RequestUtils.getWebUser(request);
        if (libraryId != null) {
            releaseTask(libraryId);
        } else {
            releaseByUser(wu.getUser().getId());
        }
        return true;
    }

    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;
        }
    }

}
