package cn.com.qmth.stmms.mark;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import net.sf.json.JSONArray;
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.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 org.springframework.web.servlet.ModelAndView;

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.model.Tag;
import cn.com.qmth.stmms.biz.exam.service.ExamService;
import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
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.MarkerClassService;
import cn.com.qmth.stmms.biz.exam.service.MarkerService;
import cn.com.qmth.stmms.biz.exam.service.TagService;
import cn.com.qmth.stmms.biz.lock.LockService;
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.model.TrialHistory;
import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
import cn.com.qmth.stmms.biz.mark.query.MarkLibrarySearchQuery;
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.mark.service.TrialService;
import cn.com.qmth.stmms.common.controller.BaseController;
import cn.com.qmth.stmms.common.enums.LibraryStatus;
import cn.com.qmth.stmms.common.enums.LockType;
import cn.com.qmth.stmms.common.enums.MarkMode;
import cn.com.qmth.stmms.common.enums.MarkStatus;
import cn.com.qmth.stmms.common.utils.RequestUtils;

@Controller
@RequestMapping("/mark")
public class MarkController extends BaseController {

    private static Logger log = LoggerFactory.getLogger(MarkController.class);

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private TrialService trialService;

    @Autowired
    private MarkerService markerService;

    @Autowired
    private MarkLibraryService libraryService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private TagService tagService;

    @Autowired
    private MarkService markService;

    @Autowired
    private ExamService examService;

    @Autowired
    private MarkGroupService groupService;

    @Autowired
    private LockService lockService;

    @Autowired
    private ExamStudentService studentService;

    @Autowired
    private MarkerClassService classService;

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

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

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

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

    @RequestMapping(value = "/reset", method = RequestMethod.GET)
    public ModelAndView reset(HttpServletRequest request) {
        Marker marker = RequestUtils.getWebUser(request).getMarker();
        ModelAndView modelAndView = new ModelAndView("modules/mark/reset");
        modelAndView.addObject("marker", marker);
        return modelAndView;
    }

    @RequestMapping(value = "/reset", method = RequestMethod.POST)
    public ModelAndView reset(HttpServletRequest request, Marker marker) {
        Marker current = RequestUtils.getWebUser(request).getMarker();
        current.setName(marker.getName());
        current.setPassword(marker.getPassword());
        current.setLastLoginIp(request.getRemoteAddr());
        current.setLastLoginTime(new Date());
        markerService.save(current);
        return new ModelAndView("redirect:/mark/index");
    }

    @RequestMapping("/index")
    public ModelAndView index(HttpServletRequest request, @RequestParam(value = "mode", required = false) String mode) {
        Marker marker = RequestUtils.getWebUser(request).getMarker();
        ModelAndView modelAndView = getMarkModeView(marker, MarkMode.findByName(mode));
        preProcess(marker, modelAndView);
        return modelAndView;
    }

    private ModelAndView getMarkModeView(Marker marker, MarkMode mode) {
        boolean forceMode = false;
        MarkMode sysMode = MarkMode.findByName(forceMarkMode);
        MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
        if (sysMode != null) {
            // 全局配置的强制评卷模式
            mode = sysMode;
            forceMode = true;
        } else {
            // 没有全局配置，优先从大题配置取强制评卷模式
            if (group != null && group.getMarkMode() != null) {
                mode = group.getMarkMode();
                forceMode = true;
            }
            // 否则取评卷员当前记录的评卷模式
            if (mode == null) {
                mode = marker.getMode();
            }
            if (mode == null) {
                mode = MarkMode.COMMON;
            }
            if (marker.getMode() != mode) {
                marker.setMode(mode);
                markerService.save(marker);
            }
        }
        ModelAndView view = new ModelAndView(mode == MarkMode.TRACK ? "modules/mark/markTrack" : "modules/mark/markNew");
        view.addObject("forceMode", forceMode);
        view.addObject("sheetView", group.isSheetView());
        return view;
    }

    @RequestMapping("/logout")
    public ModelAndView logout(HttpServletRequest request) {
        releaseMarker(RequestUtils.getWebUser(request).getMarker());
        return new ModelAndView("redirect:/logout");
    }

    /**
     * 进入评卷界面后的通用预处理
     * 
     * @param marker
     * @param session
     * @param modelAndView
     * @param modelAndView
     */
    private void preProcess(Marker marker, ModelAndView modelAndView) {
        modelAndView.addObject("sliceServer", sliceServer);
        modelAndView.addObject("sheetServer", sheetServer);
        modelAndView.addObject("cardServer", cardServer);
        modelAndView.addObject("marker", marker);
        modelAndView.addObject("subject", subjectService.find(marker.getExamId(), marker.getSubjectCode()));
        Exam exam = examService.findById(marker.getExamId());
        modelAndView.addObject("forceSpecialTag", exam.isForceSpecialTag());
        releaseMarker(marker);
    }

    @RequestMapping("/clear")
    @ResponseBody
    public void clear(HttpServletRequest request) {
        releaseMarker(RequestUtils.getWebUser(request).getMarker());
    }

    @RequestMapping("/status")
    @ResponseBody
    public JSONObject status(HttpServletRequest request) {
        JSONObject status = new JSONObject();
        Marker marker = RequestUtils.getWebUser(request).getMarker();
        ExamSubject subject = subjectService.find(marker.getExamId(), marker.getSubjectCode());
        MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
        if (subject == null || group == null || group.getStatus() == MarkStatus.FINISH) {
            status.accumulate("valid", false);
            return status;
        }
        long totalCount = 0;
        long personCount = 0;
        long markedCount = 0;
        long exceptionCount = 0;
        long topCount = 0;
        if (group.getStatus() == MarkStatus.FORMAL) {
            topCount = marker.getTopCount() != null ? marker.getTopCount() : 0;

            MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
            query.setExamId(marker.getExamId());
            query.setSubjectCode(marker.getSubjectCode());
            query.setGroupNumber(marker.getGroupNumber());
            totalCount = libraryService.countByQuery(query);

            query.setMarkerId(marker.getId());
            personCount = libraryService.countByQuery(query);

            query.setMarkerId(0);
            query.addStatus(LibraryStatus.MARKED);
            query.addStatus(LibraryStatus.ARBITRATED);
            markedCount = libraryService.countByQuery(query);

            query.clearStatus();
            query.addStatus(LibraryStatus.WAIT_ARBITRATE);
            exceptionCount = libraryService.countByQuery(query);
        } else if (group.getStatus() == MarkStatus.TRIAL) {
            totalCount = trialService.countLibrary(group.getExamId(), group.getSubjectCode(), group.getNumber());
            personCount = trialService.countMarkerHistory(marker.getId());
            markedCount = personCount;
        }
        status.accumulate("personCount", personCount);
        status.accumulate("totalCount", totalCount);
        status.accumulate("markedCount", markedCount);
        status.accumulate("exceptionCount", exceptionCount);
        status.accumulate("valid", totalCount > 0);
        status.accumulate("topCount", topCount);
        return status;
    }

    @RequestMapping("/gettask")
    @ResponseBody
    public Task getTask(HttpServletRequest request) {
        Marker marker = RequestUtils.getWebUser(request).getMarker();
        Task task = null;
        Set<String> classes = null;
        if (marker.getClassCount() != 0) {
            classes = classService.findClassNameByMarkerId(marker.getId());
        }
        try {
            lockService.watch(LockType.GROUP, marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
            lockService.watch(LockType.MARKER, marker.getId());

            MarkGroup group = groupService
                    .findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
            if (group == null) {
                task = new Task();
                task.setExist(false);
                task.setMessage("评卷大题不存在");
            } else if (group.getStatus() == MarkStatus.FINISH) {
                task = new Task();
                task.setExist(false);
                task.setMessage("评卷已结束");
            } else if (group.getStatus() == MarkStatus.TRIAL) {
                task = getTrialTask(marker);
            } else if (group.getStatus() == MarkStatus.FORMAL) {
                task = getFormalTask(marker, classes);
            }
            if (task == null) {
                task = new Task();
                task.setExist(false);
                task.setMessage("当前无评卷任务");
            }
        } catch (Exception e) {
            log.error("get task error", e);
        } finally {
            lockService.unwatch(LockType.MARKER, marker.getId());
            lockService.unwatch(LockType.GROUP, marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
        }
        return task;
    }

    private Task getFormalTask(Marker marker, Set<String> classes) {
        int retry = 1;
        Task task = null;
        while (task == null) {
            List<MarkLibrary> list = new ArrayList<MarkLibrary>();
            // 绑定了班级的评卷员
            if (classes != null) {
                list = libraryService.findUnMarked(marker.getExamId(), marker.getSubjectCode(),
                        marker.getGroupNumber(), classes, marker.getId(), retry, 20);
            } else {
                list = libraryService.findUnMarked(marker.getExamId(), marker.getSubjectCode(),
                        marker.getGroupNumber(), marker.getId(), retry, 20);
            }
            if (list.isEmpty()) {
                break;
            }
            for (MarkLibrary library : list) {
                if (markService.applyLibrary(library, marker)) {
                    task = taskService.build(library);
                    break;
                }
            }
            if (task == null) {
                retry++;
            }
        }
        return task;
    }

    private Task getTrialTask(Marker marker) {
        int retry = 1;
        Task task = null;
        while (task == null) {
            List<TrialLibrary> list = trialService.findUnMarkedLibrary(marker.getExamId(), marker.getSubjectCode(),
                    marker.getGroupNumber(), marker.getId(), retry, 10);
            if (list.isEmpty()) {
                break;
            }
            for (TrialLibrary library : list) {
                if (markService.applyLibrary(library, marker)) {
                    task = taskService.build(library, null);
                    break;
                }
            }
            retry++;
        }
        return task;
    }

    @RequestMapping(value = "/savetask", method = RequestMethod.POST)
    @ResponseBody
    public JSONObject saveTask(HttpServletRequest request, @RequestBody MarkResult markResult) {
        JSONObject result = new JSONObject();
        Marker marker = RequestUtils.getWebUser(request).getMarker();
        boolean success = false;
        try {
            lockService.watch(LockType.GROUP, marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
            lockService.watch(LockType.STUDENT, markResult.getStudentId());
            lockService.watch(LockType.MARKER, marker.getId());

            success = markService.submitTask(markResult, marker);
        } catch (Exception e) {
            success = false;
            log.error("save task error", e);
        } finally {
            lockService.unwatch(LockType.MARKER, marker.getId());
            lockService.unwatch(LockType.STUDENT, markResult.getStudentId());
            lockService.unwatch(LockType.GROUP, marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
        }
        result.accumulate("success", success);
        result.accumulate("status", status(request));
        if (!success) {
            result.accumulate("message", "评卷任务提交失败，请刷新重试");
        }
        return result;
    }

    @RequestMapping("/gethistory")
    @ResponseBody
    public Object history(HttpServletRequest request, @RequestParam int pageNumber, @RequestParam int pageSize,
            @RequestParam String order, @RequestParam String sort,
            @RequestParam(required = false, defaultValue = "false") Boolean isTag,
            @RequestParam(required = false) Integer studentId) throws Exception {
        Marker marker = RequestUtils.getWebUser(request).getMarker();
        List<Task> list = new ArrayList<>();
        MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
        if (group != null && group.getStatus() == MarkStatus.FORMAL) {
            // 正评查找已给分的评卷任务
            MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
            query.setExamId(marker.getExamId());
            query.setSubjectCode(marker.getSubjectCode());
            query.setMarkerId(marker.getId());
            query.addStatus(LibraryStatus.MARKED);
            query.setGroupNumber(marker.getGroupNumber());
            query.setTagNotNull(isTag);
            query.setPageNumber(pageNumber);
            query.setPageSize(pageSize);
            Direction d = Direction.DESC;
            if (sort.equals("asc")) {
                d = Direction.ASC;
            }
            if (order.equals("time")) {
                query.setSort(new Sort(d, "markerTime"));
            } else if (order.equals("studentId")) {
                query.setSort(new Sort(d, "studentId"));
            } else if (order.equals("score")) {
                query.setSort(new Sort(d, "markerScore"));
            } else {
                query.orderByMarkerTimeDesc();
            }
            if (studentId != null) {
                query.setStudentId(studentId);
            }
            list = taskService.findByQuery(query);
            for (Task task : list) {
                task.setPrevious(true);
            }
        } else if (group != null && group.getStatus() == MarkStatus.TRIAL) {
            // 试评查找给分历史记录
            List<TrialHistory> historyList = trialService.findHistory(marker.getExamId(), marker.getSubjectCode(),
                    marker.getGroupNumber(), marker.getId(), pageNumber, pageSize);
            for (TrialHistory history : historyList) {
                TrialLibrary library = trialService.findLibrary(history.getLibraryId());
                if (library != null) {
                    Task task = taskService.build(library, history);
                    task.setPrevious(true);
                    list.add(task);
                }
            }
        }
        return list;
    }

    @RequestMapping("/change-name")
    @ResponseBody
    public JSONObject changeName(HttpServletRequest request, @RequestParam String name,
            @RequestParam(required = false) String password) {
        Marker marker = RequestUtils.getWebUser(request).getMarker();
        JSONObject result = new JSONObject();
        marker.setName(name);
        if (StringUtils.isNotEmpty(password)) {
            marker.setPassword(password);
        }
        marker = markerService.save(marker);
        result.accumulate("success", marker != null);
        if (marker != null) {
            result.accumulate("name", marker.getName());
        }
        return result;
    }

    @RequestMapping("/tags")
    @ResponseBody
    public JSONArray getTags(HttpServletRequest request) {
        JSONArray array = new JSONArray();
        List<Tag> list = tagService.findAll();
        for (Tag tag : list) {
            JSONObject obj = new JSONObject();
            obj.accumulate("value", tag.getId());
            obj.accumulate("name", tag.getName());
            array.add(obj);
        }
        return array;
    }

    private void releaseMarker(Marker marker) {
        if (marker == null) {
            return;
        }
        try {
            lockService.waitlock(LockType.MARKER, marker.getId());
            markService.releaseByMarker(marker);
        } catch (Exception e) {
            log.error("release marker error", e);
        } finally {
            lockService.unlock(LockType.MARKER, marker.getId());
        }
    }

}
