package com.xinxincaravan.caravan.vehicle.biz;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.wxiaoqi.security.common.biz.BaseBiz;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xinxincaravan.caravan.vehicle.common.CustomIllegalParamException;
import com.xinxincaravan.caravan.vehicle.common.RestResponse;
import com.xinxincaravan.caravan.vehicle.constant.*;
import com.xinxincaravan.caravan.vehicle.constant.ResCode.ResCode;
import com.xinxincaravan.caravan.vehicle.entity.Vehicle;
import com.xinxincaravan.caravan.vehicle.entity.VehicleBookInfo;
import com.xinxincaravan.caravan.vehicle.entity.VehicleBookRecord;
import com.xinxincaravan.caravan.vehicle.mapper.BookRecordAccItemMapper;
import com.xinxincaravan.caravan.vehicle.mapper.VehicleBookInfoMapper;
import com.xinxincaravan.caravan.vehicle.mapper.VehicleBookRecordMapper;
import com.xinxincaravan.caravan.vehicle.mapper.VehicleMapper;
import com.xinxincaravan.caravan.vehicle.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class VehicleBiz extends BaseBiz<VehicleMapper, Vehicle> {

    public static final DateTimeFormatter DEFAULT_DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");
    public static final DateTimeFormatter YEARMONTH_DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM");
    /**
     * 允许查询预定信息的最大月份数
     */
    public static final Integer MAX_MONTH_COUNT_BOOKED_INFO_QUERY = 3;

    @Autowired
    private VehicleBookInfoMapper vehicleBookInfoMapper;
    @Autowired
    private VehicleBookRecordMapper vehicleBookRecordMapper;
    @Autowired
    private ConstantBiz constantBiz;
    @Autowired
    private BranchCompanyBiz branchCompanyBiz;
    @Autowired
    private BookRecordAccItemMapper bookRecordAccItemMapper;
    @Autowired
    private RedisTemplate customRedisTemplate;

    @Value("${vehicle.baseUploadPath}")
    private String baseUploadPath ;

    /**
     * 每批次最大更、插入车辆最大条目数
     */
    public static final Integer MAX_BATCH_SIZE_VEHICLE = 1;//使用了悲观锁，不开放批量更新

    /**
     * 写入上传文件，返回相对路径
     * @param file
     * @param vehicleId
     * @return
     */
    public RestResponse<String> uploadDrivingLicense(MultipartFile file) throws Exception{
        //创建本日存放目录
        DateTime now = DateTime.now();
        String dirPathToday =  File.separator + now.toString(DEFAULT_DATE_TIME_FORMATTER);
        String redisNoKey = RedisKey.UPLOAD_FILE_NO_PREFIX + now.toString(DEFAULT_DATE_TIME_FORMATTER);
        Long no = customRedisTemplate.opsForValue().increment(redisNoKey);
        if(no.equals(1l)){
            customRedisTemplate.expire(redisNoKey,1, TimeUnit.DAYS);
        }
        String fileName = file.getOriginalFilename();
        String realFileRelPath = dirPathToday + File.separator + no + fileName.substring(fileName.lastIndexOf("."));
        //文件存放路径
        String filePath = baseUploadPath + realFileRelPath;
        //将文件写入指定位置
        FileUtils.copyInputStreamToFile(file.getInputStream(), new File(filePath));
        return RestResponse.suc(realFileRelPath);
    }

    /**
     * 下载行驶证图片
     * @param realFileRelPath
     * @return
     * @throws Exception
     */
    public ResponseEntity<byte[]> downloadDrivingLicense(String realFileRelPath) throws Exception{
        String filePath = baseUploadPath + realFileRelPath;
        File file = new File(filePath);//新建一个文件
        HttpHeaders headers = new HttpHeaders();//http头信息
        String downloadFileName = new String(file.getName());//设置编码
        headers.setContentDispositionFormData("attachment", downloadFileName);
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),headers, HttpStatus.CREATED);
    }

    public Vehicle get(String id){
        return mapper.selectByPrimaryKey(id);
    }

    /**
     * 获取相关预订记录
     * @param vehicle
     * @param yearMonth
     * @return
     */
    public VehicleBookInfo getByVehicleIdAndYearMonth(String vehicle,String yearMonth){
        Map<String,Object> params = Maps.newHashMap();
        params.put("vehicle",vehicle);
        params.put("yearMonth",yearMonth);
        List<VehicleBookInfo> vehicleBookInfoList = vehicleBookInfoMapper.getByVehicleIdAndYearMonth(params);
        return CollectionUtils.isEmpty(vehicleBookInfoList)?null:vehicleBookInfoList.get(0);
    }

    /**
     * 获取相关预订记录
     * @param vehicle
     * @return
     */
    public List<VehicleBookInfo> getByVehicleIdAndYearMonth(String vehicle){
        Map<String,Object> params = Maps.newHashMap();
        params.put("vehicle",vehicle);
        params.put("yearMonths",
                Lists.newArrayList(
                        DateTime.now().toString(YEARMONTH_DATE_TIME_FORMATTER),
                        DateTime.now().plusMonths(1).toString(YEARMONTH_DATE_TIME_FORMATTER),
                        DateTime.now().plusMonths(2).toString(YEARMONTH_DATE_TIME_FORMATTER)
                )
        );
        List<VehicleBookInfo> vehicleBookInfoList = vehicleBookInfoMapper.getByVehicleIdAndYearMonths(params);
        return vehicleBookInfoList;
    }

    /**
     * 检查常量是否合法
     */
    private void checkIfConstantValid(AddOrUpdateVehicleVo addOrUpdateVehicleVo){
        //检查常量是否合法
        if (addOrUpdateVehicleVo.getUseType() != null && addOrUpdateVehicleVo.getUseType() > 0) {
            constantBiz.checkIfExists(ConstantType.VEHICLE_USE.getCode(), addOrUpdateVehicleVo.getUseType());
        }
        if (addOrUpdateVehicleVo.getBrand() != null && addOrUpdateVehicleVo.getBrand() > 0) {
            constantBiz.checkIfExists(ConstantType.VEHICLE_BRAND.getCode(), addOrUpdateVehicleVo.getBrand());
        }
    }

    /**
     * 增加车辆
     * @param addOrUpdateVehicleVoList
     */
    @Transactional
    public RestResponse add(List<AddOrUpdateVehicleVo> addOrUpdateVehicleVoList) throws Exception{

        if(addOrUpdateVehicleVoList.size()>MAX_BATCH_SIZE_VEHICLE){
            throw new CustomIllegalParamException("exceed max batch size");
        }

        for(AddOrUpdateVehicleVo addOrUpdateVehicleVo:addOrUpdateVehicleVoList) {
            Vehicle vehicle = new Vehicle();
            BeanUtils.copyProperties(vehicle, addOrUpdateVehicleVo);
            vehicle.setId(UUID.randomUUID().toString());
//            检查车牌或者编码是否已存在，已存在则返回失败
            List<Vehicle> exitsVehicles = lockByCode(addOrUpdateVehicleVo);
            if(CollectionUtils.isNotEmpty(exitsVehicles)){
                if(addOrUpdateVehicleVo.getCode()!= null &&
                        addOrUpdateVehicleVo.getCode().equals(exitsVehicles.get(0).getCode())){
                    return RestResponse.code(ResCode.VEHICLE_INFO_CODE_EXIST.getCode());
                }else{
                    return RestResponse.code(ResCode.VEHICLE_INFO_SAME_NUM_PLATE_EXISTS.getCode());
                }
            }
            mapper.insertSelective(vehicle);
        }
        return RestResponse.suc();
    }

    /**
     * 此方法必须置于事务中以使锁生效
     * 符合查询条件记录的修改活且相关记录的锁获取操作将被锁定
     * @param addOrUpdateVehicleVo
     * @return 锁定成功返回
     */
    private List<Vehicle> lockByCode(AddOrUpdateVehicleVo addOrUpdateVehicleVo){
        if(addOrUpdateVehicleVo.getCode() == null){
            return null;
        }
        List<Vehicle> vehicles = mapper.lockByCode(addOrUpdateVehicleVo);
        return vehicles;
    }

    /**
     * 修改汽车信息
     * @param addOrUpdateVehicleVoList
     * @return
     */
    @Transactional
    public RestResponse update(List<AddOrUpdateVehicleVo> addOrUpdateVehicleVoList) throws Exception{
        if(addOrUpdateVehicleVoList.size()>MAX_BATCH_SIZE_VEHICLE){
            throw new CustomIllegalParamException("exceed max batch size");
        }

        for(AddOrUpdateVehicleVo addOrUpdateVehicleVo:addOrUpdateVehicleVoList){
            Vehicle vehicle = new Vehicle();
            BeanUtils.copyProperties(vehicle, addOrUpdateVehicleVo);
            //悲观锁，检查是否已存在车牌或编码
            List<Vehicle> exitsVehicles = lockByCode(addOrUpdateVehicleVo);
            if(CollectionUtils.isNotEmpty(exitsVehicles)){
                for(Vehicle exitsVehicle:exitsVehicles){
                    if(exitsVehicle.getId().equals(addOrUpdateVehicleVo.getId())){
                        continue;
                    }
                    if(addOrUpdateVehicleVo.getCode()!= null &&
                            addOrUpdateVehicleVo.getCode().equals(exitsVehicles.get(0).getCode())){
                        return RestResponse.code(ResCode.VEHICLE_INFO_CODE_EXIST.getCode());
                    }else{
                        return RestResponse.code(ResCode.VEHICLE_INFO_SAME_NUM_PLATE_EXISTS.getCode());
                    }
                }
            }
            mapper.updateByPrimaryKeySelective(vehicle);
        }
        return RestResponse.suc();
    }

    /**
     * 废弃车辆（状态设置为废弃）
     * @param idList
     * @return
     */
    public RestResponse discard(List<String> idList){
        if(idList.size()>MAX_BATCH_SIZE_VEHICLE){
            throw new CustomIllegalParamException("exceed max batch size");
        }
        Map<String,Object> params = Maps.newHashMap();
        params.put("idList",idList);
        params.put("status", VehicleStatus.DISCARD.getCode());
        mapper.updateStatusById(params);
        return RestResponse.suc();
    }

    /**
     * 申请汽车预定（内部）
     * 检查是否可预定，修改相关预定记录状态
     * @param userId
     * @param bookVehicleVo
     * @return
     */
    @Transactional
    public RestResponse applyVehicle4Employee(Integer userId,BookVehicleVo bookVehicleVo,String userName) throws Exception{
        //检查车辆信息是否合法
        checkIfVehicleExists(bookVehicleVo.getVehicle());
        //提取日期和相应的预定目标日期
        Map<String,List<String>> yearMonthAndDate = Maps.newHashMap();
        DateTime startDay =DateTime.parse(bookVehicleVo.getBookStartDate(), DEFAULT_DATE_TIME_FORMATTER);
        DateTime endDay =DateTime.parse(bookVehicleVo.getBookEndDate(), DEFAULT_DATE_TIME_FORMATTER);
        //转换日期范围为列表，并检查是否合法
        fillDateList4DatePeriod(yearMonthAndDate,startDay,endDay);
        if(yearMonthAndDate.size()>3){//连续的日期最多夸3个月
            throw new CustomIllegalParamException(" you can only within 2 month");
        }
        for(Map.Entry<String,List<String>> entry:yearMonthAndDate.entrySet()){
            Boolean rsEach = applyVehicle4EmployeePerMonth(bookVehicleVo.getVehicle(),entry.getValue(),entry.getKey());
            if(Boolean.FALSE.equals(rsEach)){
                return RestResponse.code(ResCode.VEHICLE_BOOKED_INFO_ALREADY_CHANGED.getCode());
            }
        }
        //加入预定申请记录
        VehicleBookRecord vehicleBookRecord = new VehicleBookRecord();
        vehicleBookRecord.setVehicle(bookVehicleVo.getVehicle());
        vehicleBookRecord.setBookType(BookType.EMPLOYEE_APPLY.getCode());
        vehicleBookRecord.setStatus(VehicleBookRecordStatus.APPLY.getCode());
        vehicleBookRecord.setBookUser(userId);
        vehicleBookRecord.setBookUserName(userName);
        vehicleBookRecord.setBookStartDate(DateTime.
                parse(bookVehicleVo.getBookStartDate(),DEFAULT_DATE_TIME_FORMATTER).toDate());
        vehicleBookRecord.setBookEndDate(DateTime.
                parse(bookVehicleVo.getBookEndDate(),DEFAULT_DATE_TIME_FORMATTER).toDate());
        vehicleBookRecord.setLiftAddr(bookVehicleVo.getLiftAddr());
        vehicleBookRecord.setRemark(bookVehicleVo.getRemark());
        vehicleBookRecord.setDestination(bookVehicleVo.getDestination());
        vehicleBookRecord.setLiftCompany(bookVehicleVo.getLiftCompany());
        vehicleBookRecordMapper.insertSelective(vehicleBookRecord);
        List<Map<String,Object>> params = Lists.newArrayList();
        if(MapUtils.isNotEmpty(bookVehicleVo.getSelectedAccItem())){
            for(Map.Entry<Integer,Integer> idAndAmount : bookVehicleVo.getSelectedAccItem().entrySet()){
                Map<String,Object> row = Maps.newHashMap();
                row.put("id",idAndAmount.getKey());
                row.put("amount",idAndAmount.getValue());
                row.put("bookRecordId",vehicleBookRecord.getId());
                params.add(row);
            }
            bookRecordAccItemMapper.batchAdd(params);
        }
        return RestResponse.suc();
    }

    @Transactional
    public Boolean applyVehicle4EmployeePerMonth(String vehicleId, List<String> bookedDates, String yearMonth){
        //检查车辆是否有空档
        //获得当月预定记录
        VehicleBookInfo vehicleBookInfo = getByVehicleIdAndYearMonth(vehicleId,yearMonth);
        //位操作确定目标日期是否可预订
        Map<String,Object> params = Maps.newHashMap();
        Map<String,List<String>> yearMonthAndDate = new HashMap<>();
        yearMonthAndDate.put(yearMonth,bookedDates);
        fillBookedDateSearchParam(params,null,yearMonthAndDate);//转换为查询对应日期未预定的条件
        Map<String,Map<String,Integer>> yearMonthAndParam = (Map<String,Map<String,Integer>>)params.get("yearMonthAndParam");
        Map<String,Integer> andOpratorParam  = yearMonthAndParam.get(yearMonth);
        Integer andOperationFactor = andOpratorParam.get("andOperationFactor");
        Integer andOperationRs = andOpratorParam.get("andOperationRs");
        if(vehicleBookInfo!=null&&vehicleBookInfo.getBookedDate()!=null &&
                ((vehicleBookInfo.getBookedDate() & andOperationFactor) != andOperationRs)){
            return Boolean.FALSE;
        }
        return Boolean.TRUE;
    }


    /**
     * 批准预定车辆预定
     * @param operatorId
     * @param bookRecordId
     * @return
     */
    @Transactional
    public RestResponse<Integer> reviewVehicleBooking(Integer operatorId, Long bookRecordId,Integer rsStatus,String userName) throws Exception{
        //获取相关申请记录
        VehicleBookRecord vehicleBookRecord = vehicleBookRecordMapper.selectByPrimaryKey(bookRecordId);
        //申请记录验证
        if(vehicleBookRecord == null){
            throw new CustomIllegalParamException(" invalid book record");
        }
        if(!VehicleBookRecordStatus.APPLY.getCode().equals(vehicleBookRecord.getStatus())){
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_ALREADY_CHANGED.getCode());
        }
        //转换为相应预定参数
        BookVehicleVo bookVehicleVo = new BookVehicleVo();
        BeanUtils.copyProperties(bookVehicleVo,vehicleBookRecord);
        bookVehicleVo.setBookStartDate(new DateTime(vehicleBookRecord.getBookStartDate()).toString(DEFAULT_DATE_TIME_FORMATTER));
        bookVehicleVo.setBookEndDate(new DateTime(vehicleBookRecord.getBookEndDate()).toString(DEFAULT_DATE_TIME_FORMATTER));
        //审核通过的话，需要修改相关车辆预定记录
        if(VehicleBookRecordStatus.APPROVE.getCode().equals(rsStatus)) {
            Boolean hasSuc = bookedVehicle(bookVehicleVo);
            if(!hasSuc){
                return RestResponse.code(ResCode.VEHICLE_BOOKED_INFO_ALREADY_CHANGED.getCode());
            }
        }
        //成功后修改预定记录状态
        Map<String,Object> updateParam = Maps.newHashMap();
        updateParam.put("id",bookRecordId);
        updateParam.put("status",rsStatus);
        updateParam.put("reviewerApply",operatorId);
        updateParam.put("reviewerNameApply",userName);
        updateParam.put("statusCondition",VehicleBookRecordStatus.APPLY.getCode());
        Integer effected = vehicleBookRecordMapper.changeRecordStatus(updateParam);
        if(effected == 0){//修改失败，手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//手动回滚
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_ALREADY_CHANGED.getCode());
        }
        return RestResponse.suc();
    }

    /**
     * 取消预定
     * @return
     */
    @Transactional
    public RestResponse unbookVehicle4Employee(Integer operatorId,Long bookRecordId,String userName) throws Exception{
        //获取相关申请记录
        VehicleBookRecord vehicleBookRecord = vehicleBookRecordMapper.selectByPrimaryKey(bookRecordId);
        //申请记录验证
        if(vehicleBookRecord == null){
            throw new CustomIllegalParamException(" invalid book record");
        }
        if(!VehicleBookRecordStatus.APPROVE.getCode().equals(vehicleBookRecord.getStatus())){
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_ALREADY_CHANGED.getCode());
        }
        //转换为相应取消预定参数
        BookVehicleVo bookVehicleVo = new BookVehicleVo();
        BeanUtils.copyProperties(bookVehicleVo,vehicleBookRecord);
        bookVehicleVo.setBookStartDate(null);
        bookVehicleVo.setBookEndDate(null);
        bookVehicleVo.setUnbookStartDate(new DateTime(vehicleBookRecord.getBookStartDate()).toString(DEFAULT_DATE_TIME_FORMATTER));
        bookVehicleVo.setUnbookEndDate(new DateTime(vehicleBookRecord.getBookEndDate()).toString(DEFAULT_DATE_TIME_FORMATTER));
        //取消预定
        Boolean hasSuc = unbookVehicle(bookVehicleVo);
        if(!hasSuc){
            throw new IllegalArgumentException(" invalid book record ");
        }
        //修改预定状态，写入取消人
        Map<String,Object> updateParam = Maps.newHashMap();
        updateParam.put("id",bookRecordId);
        updateParam.put("status",VehicleBookRecordStatus.CANCEL_APPLY.getCode());
        updateParam.put("reviewerCancel",operatorId);
        updateParam.put("reviewerNameCancel",userName);
        updateParam.put("statusCondition",VehicleBookRecordStatus.APPROVE.getCode());
        Integer effected = vehicleBookRecordMapper.changeRecordStatus(updateParam);
        if(effected == 0){//修改失败，手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//手动回滚
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_ALREADY_CHANGED.getCode());
        }
        return RestResponse.suc();
    }



    /**
     * 获取预定车辆相关与操作参数
     * @return
     */
    private Integer getBitOpratorFactor4Booked(List<String> dates){
        Integer andOperationFactor = 0;
        for (String dateStr:dates) {//已预定作为条件，该位与1作与运算必定为1
            DateTime dateTime = DateTime.parse(dateStr, DEFAULT_DATE_TIME_FORMATTER);
            //仅对应位为1的整形值
            andOperationFactor |= 1<<(dateTime.dayOfMonth().get()-1);
        }
        return andOperationFactor;
    }

    /**
     * 获取预定车辆相关与操作参数
     * @return
     */
    private Integer getBitOpratorFactor4UnBooked(List<String> dates){
        Integer andOperationFactor = Integer.MAX_VALUE;
        for (String dateStr:dates) {//已预定作为条件，该位与1作与运算必定为1
            DateTime dateTime = DateTime.parse(dateStr, DEFAULT_DATE_TIME_FORMATTER);
            //仅对应位为1的整形值
            andOperationFactor ^= 1<<(dateTime.dayOfMonth().get()-1);
        }
        return andOperationFactor;
    }



    /**
     * 根据预定日期逐条修改预定记录
     * @param bookVehicleVo
     * @return
     */
    @Transactional
    public Boolean bookedVehicle( BookVehicleVo bookVehicleVo) throws Exception{
        //提取日期和相应的预定目标日期
        Map<String,List<String>> yearMonthAndDate = Maps.newHashMap();

        DateTime startDay =DateTime.parse(bookVehicleVo.getBookStartDate(), DEFAULT_DATE_TIME_FORMATTER);
        DateTime endDay =DateTime.parse(bookVehicleVo.getBookEndDate(), DEFAULT_DATE_TIME_FORMATTER);
        //转换日期范围为列表，并检查是否合法
        fillDateList4DatePeriod(yearMonthAndDate,startDay,endDay);
        if(yearMonthAndDate.size()>3){//连续的日期最多夸3个月
            throw new CustomIllegalParamException(" you can only within 2 month");
        }
        Boolean rs = Boolean.TRUE;
        for(Map.Entry<String,List<String>> entry:yearMonthAndDate.entrySet()){
            Boolean rsEach = bookedVehiclePerMonth(bookVehicleVo.getVehicle(),entry.getValue(),entry.getKey());
            if(Boolean.FALSE.equals(rsEach)){
                rs = Boolean.FALSE;
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//手动回滚
            }
        }
        return rs;
    }

    /**
     * 不存在插入、存在更新，利用唯一索引确保相同车辆以及年月序列化提交事务
     * @param vehicleId
     * @param bookedDates
     * @param yearMonth
     * @return 是否更新或插入成功
     */
    @Transactional
    public Boolean bookedVehiclePerMonth(String vehicleId, List<String> bookedDates, String yearMonth) throws Exception{
        VehicleBookInfo vehicleBookInfo = new VehicleBookInfo();
        vehicleBookInfo.setVehicle(vehicleId);
        vehicleBookInfo.setYearMonth(yearMonth);
        //检查是否存在相关车辆
        Vehicle param = new Vehicle();
        param.setId(vehicleId);
        checkIfVehicleExists(vehicleId);
        Integer orRsOperationFactor = getBitOpratorFactor4Booked(bookedDates);//预定的相关或运算因子，当前月份没有预定记录时同时也是结果
        vehicleBookInfo.setBookedDate(orRsOperationFactor);
        Integer effected = vehicleBookInfoMapper.insertIgnore(vehicleBookInfo);
        if(effected == 0){//已存在则需要更新
            Map<String,Object> params = Maps.newHashMap();
            params.put("vehicle",vehicleBookInfo.getVehicle());
            params.put("yearMonth",yearMonth);
            //加入更新条件
            if(CollectionUtils.isEmpty(bookedDates)){
                throw new CustomIllegalParamException(" there are no day to book");
            }
            Map<String,List<String>> yearMonthAndDate = new HashMap<>();
            yearMonthAndDate.put(vehicleBookInfo.getYearMonth(),bookedDates);
            //转换为查询对应日期未预定的条件
            fillBookedDateSearchParam(params,null,yearMonthAndDate);
            //提取相关参数组装更细条件
            Map<String,Map<String,Integer>> yearMonthAndParam = (Map<String,Map<String,Integer>>)params.get("yearMonthAndParam");
            Map<String,Integer> andOpratorParam  = yearMonthAndParam.get(vehicleBookInfo.getYearMonth());
            params.putAll(andOpratorParam);
            params.put("orRsOperationFactor",
                    orRsOperationFactor);
            effected = vehicleBookInfoMapper.updateBookedInfo(params);
        }
        return effected>0?Boolean.TRUE:Boolean.FALSE;
    }

    /**
     * 检查车辆是否存在
     * @param id
     */
    public void checkIfVehicleExists(String id){
        Vehicle param = new Vehicle();
        param.setId(id);
        Integer count = mapper.selectCount(param);
        if(count<=0){
            throw new CustomIllegalParamException(" no such vehicle");
        }
    }

    /**
     * 把日期范围转换为列表，并检查操作范围是否合法
     * @param yearMonthAndDate
     * @param startDay
     * @param endDay
     */
    private void fillDateList4DatePeriod(Map<String,List<String>> yearMonthAndDate,DateTime startDay,DateTime endDay){
        for( DateTime curDate = startDay;curDate.compareTo(endDay)<=0;curDate=curDate.plusDays(1)){
            String curDateStr = curDate.toString(DEFAULT_DATE_TIME_FORMATTER);
            if(curDateStr.compareTo(DateTime.now().toString(DEFAULT_DATE_TIME_FORMATTER))<0){
                throw new CustomIllegalParamException("you can only unbook from today");
            }
            String curYearMonth = curDate.toString(YEARMONTH_DATE_TIME_FORMATTER);
            if(!yearMonthAndDate.containsKey(curYearMonth)){
                yearMonthAndDate.put(curYearMonth,Lists.newArrayList());
            }
            List<String> curBookedDateList = yearMonthAndDate.get(curYearMonth);
            curBookedDateList.add(curDateStr);
        }

        if(yearMonthAndDate.size()>3){//连续的日期最多夸3个月
            throw new CustomIllegalParamException(" you can only within 2 month");
        }
    }

    /**
     * 取消预定
     * @return
     */
    @Transactional
    public Boolean unbookVehicle(BookVehicleVo bookVehicleVo){
        //提取日期参数，改为每月一份
        //提取日期和相应的预定目标日期
        Map<String,List<String>> yearMonthAndDate = Maps.newHashMap();
        DateTime startDay =DateTime.parse(bookVehicleVo.getUnbookStartDate(), DEFAULT_DATE_TIME_FORMATTER);
        DateTime endDay =DateTime.parse(bookVehicleVo.getUnbookEndDate(), DEFAULT_DATE_TIME_FORMATTER);
        //转换日期范围为列表，并检查是否合法
        fillDateList4DatePeriod(yearMonthAndDate,startDay,endDay);

        if(yearMonthAndDate.size()>3){//连续的日期最多夸3个月
            throw new CustomIllegalParamException(" you can only within 2 month");
        }
        Boolean rs = Boolean.TRUE;
        for(Map.Entry<String,List<String>> entry:yearMonthAndDate.entrySet()){
            Boolean rsEach = unbookVehiclePerMonth(bookVehicleVo.getVehicle(),entry.getValue(),entry.getKey());
            if(Boolean.FALSE.equals(rsEach)){
                rs = Boolean.FALSE;
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//手动回滚
            }
        }
        return rs;
    }

    @Transactional
    public Boolean unbookVehiclePerMonth(String vehicleId, List<String> unbookDates, String yearMonth){
        checkIfVehicleExists(vehicleId);
        Map<String,Object> params = Maps.newHashMap();
        params.put("vehicle",vehicleId);
        params.put("yearMonth",yearMonth);
        //加入更新条件
        if(CollectionUtils.isEmpty(unbookDates)){
            throw new CustomIllegalParamException(" there are no day to unbook ");
        }
        Map<String,List<String>> yearMonthAndDate = new HashMap<>();
        yearMonthAndDate.put(yearMonth,unbookDates);
        //转换为查询对应日期未预定的条件
        fillBookedDateSearchParam(params,yearMonthAndDate,null);
        //提取相关参数组装更细条件
        Map<String,Map<String,Integer>> yearMonthAndParam = (Map<String,Map<String,Integer>>)params.get("yearMonthAndParam");
        Map<String,Integer> andOpratorParam  = yearMonthAndParam.get(yearMonth);
        params.putAll(andOpratorParam);
        Integer andRsOperationFactor = getBitOpratorFactor4UnBooked(unbookDates);//预定的相关或运算因子，当前月份没有预定记录时同时也是结果
        params.put("andRsOperationFactor",
                andRsOperationFactor);
        Integer effected = vehicleBookInfoMapper.updateBookedInfo(params);
        return effected>0?Boolean.TRUE:Boolean.FALSE;
    }


    /**
     * 获取某月份相应预定日期查询条件
     * @param yearMonthAndDate 年月 - 预定日期条件字符串（yyyy-MM-dd）
     * @param yearMonthAndDateNotBooked 年月 - 未预定日期条件字符串
     * @return
     */
    public void fillBookedDateSearchParam(Map<String, Object> params,
                                          Map<String,List<String>> yearMonthAndDate,Map<String,List<String>> yearMonthAndDateNotBooked){
        if(MapUtils.isEmpty(yearMonthAndDate)&&MapUtils.isEmpty(yearMonthAndDateNotBooked)){//没有预定信息查询条件
            throw new CustomIllegalParamException(" at least apply one book info condition.");
        }

        Map<String,Map<String,Integer>> yearMonthAndParam = new HashMap<>();
        Set<String> bookedYearMonth = new HashSet<>();//记录未预定查询条件，供检查是否是否存在同一天既是已预定，又是没有预定
        if(MapUtils.isNotEmpty(yearMonthAndDate)) {
            for(Map.Entry<String,List<String>> entry:yearMonthAndDate.entrySet()){
                String curYearMonth = entry.getKey();
                if(CollectionUtils.isEmpty(entry.getValue())){
                    continue;
                }
                Integer andOperationFactor = 0;//查找条件中作与运算的参数
                Integer andOperationRs = 0;//与运算结果
                for (String dateStr:entry.getValue()) {//已预定作为条件，该位与1作与运算必定为1
                    DateTime dateTime = DateTime.parse(dateStr, DEFAULT_DATE_TIME_FORMATTER);
                    bookedYearMonth.add(dateStr);
                    //仅对应位为1的整形值
                    andOperationFactor |= 1<<(dateTime.dayOfMonth().get()-1);
                    andOperationRs |= 1<<(dateTime.dayOfMonth().get()-1);
                }
                Map<String,Integer> andOperationParam = Maps.newHashMap();
                andOperationParam.put("andOperationFactor",andOperationFactor);
                andOperationParam.put("andOperationRs",andOperationRs);
                yearMonthAndParam.put(curYearMonth,andOperationParam);
            }
        }

        if(MapUtils.isNotEmpty(yearMonthAndDateNotBooked)) {
            for(Map.Entry<String,List<String>> entry:yearMonthAndDateNotBooked.entrySet()){
                String curYearMonth = entry.getKey();
                if(CollectionUtils.isEmpty(entry.getValue())){
                    continue;
                }
                Map<String,Integer> andOperationParam = Maps.newHashMap();
                andOperationParam.put("andOperationFactor",0);
                andOperationParam.put("andOperationRs",0);
                Integer andOperationFactor=0;//查找条件中作与运算的参数
                if(yearMonthAndParam.containsKey(curYearMonth)){
                    andOperationParam = yearMonthAndParam.get(curYearMonth);
                    andOperationFactor = andOperationParam.get("andOperationFactor");
                }else{
                    yearMonthAndParam.put(curYearMonth,andOperationParam);
                }
                for (String dateStr:entry.getValue()) {//已预定作为条件，该位与1作与运算必定为1
                    if(bookedYearMonth.contains(dateStr)){
                        throw new CustomIllegalParamException(" 同一天既作为未预定查询条件又作为已预定查询条件");
                    }
                    DateTime dateTime = DateTime.parse(dateStr, DEFAULT_DATE_TIME_FORMATTER);
                    //仅对应位为1的整形值
                    andOperationFactor |= 1<<(dateTime.dayOfMonth().get()-1);
                }
                andOperationParam.put("andOperationFactor",andOperationFactor);
//                if(!bookedYearMonth.contains(curYearMonth)){//仅以未预定日期条件查询时，不存在预定记录即为没有预定
//                    andOperationParam.put("notBookedOnly",1);
//                }
            }

        }
        if(MapUtils.isNotEmpty(yearMonthAndParam)){
            params.put("yearMonthAndParam",yearMonthAndParam);
        }
    }

    /**
     * 根据月份分类到map中
     */
    private void classifyByYearMonth(List<String> target,Map<String,List<String>> yearMonthAndDate,Set<String> allYearMonth){
        if(CollectionUtils.isNotEmpty(target)) {//已预约
            for (String dateStr : target) {
                String curYearMonth = DateTime.parse(dateStr, DEFAULT_DATE_TIME_FORMATTER).toString(YEARMONTH_DATE_TIME_FORMATTER);
                allYearMonth.add(curYearMonth);
                List<String> dateStrList = yearMonthAndDate.get(curYearMonth);
                if(dateStrList == null ){
                    dateStrList = Lists.newArrayList();
                    yearMonthAndDate.put(curYearMonth,dateStrList);
                }
                dateStrList.add(dateStr);
            }
        }
    }

    /**
     * 把日期范围转换成列表
     * @param startDate
     * @param endDate
     * @return
     */
    private List<String> convertDatePeriod2List(String startDate,String endDate){
        DateTime startDay =DateTime.parse(startDate, DEFAULT_DATE_TIME_FORMATTER);
        DateTime endDay =DateTime.parse(endDate, DEFAULT_DATE_TIME_FORMATTER);
        List<String> rs = Lists.newArrayList();
        for( DateTime curDate = startDay;curDate.compareTo(endDay)<=0;curDate=curDate.plusDays(1)){
            String curDateStr = curDate.toString(DEFAULT_DATE_TIME_FORMATTER);
            rs.add(curDateStr);
        }
        return rs;
    }

    /**
     * 根据前端预定日期查询条件转换为实际查询条件
     * @param params
     * @return
     */
    public void adjustBookedInfoParam(Map<String, Object> params,VehiclePageQueryVo vehiclePageQueryVo){
        //加入每月预定信息查询条件
        List<String> bookedDates = Lists.newArrayList();
        if(StringUtils.isNotBlank(vehiclePageQueryVo.getBookedStartDate())&&
                StringUtils.isNotBlank(vehiclePageQueryVo.getBookedEndDate())) {
            bookedDates = convertDatePeriod2List(vehiclePageQueryVo.getBookedStartDate(),
                    vehiclePageQueryVo.getBookedEndDate());
        }
        params.remove("bookStartDate");
        params.remove("bookEndDate");

        List<String> notBookedDates = Lists.newArrayList();
        if(StringUtils.isNotBlank(vehiclePageQueryVo.getNotBookedStartDate())&&
                StringUtils.isNotBlank(vehiclePageQueryVo.getNotBookedEndDate())) {
            notBookedDates = convertDatePeriod2List(vehiclePageQueryVo.getNotBookedStartDate(),
                    vehiclePageQueryVo.getNotBookedEndDate());
        }
        params.remove("notBookedStartDate");
        params.remove("norBookedEndDate");

        //若传入预定信息查询条件，则查询对应月份预定信息查询条件（不超过3个月）
        if(CollectionUtils.isEmpty(bookedDates) && CollectionUtils.isEmpty(notBookedDates)){
            return;
        }
        //筛选出查询条件所在月份
        Set<String> allYearMonth = new HashSet<>();//记录所有年月
        Map<String,List<String>> yearMonthAndDate = new HashMap<>();//预定年月 - 预定日期列表
        classifyByYearMonth(bookedDates,yearMonthAndDate,allYearMonth);
        Map<String,List<String>> yearMonthAndDateNotBooked = new HashMap<>();//未预定年月 - 未预定日期列表
        classifyByYearMonth(notBookedDates,yearMonthAndDateNotBooked,allYearMonth);
        if(allYearMonth.size()>0 && params.get("subordinateBranch") == null){
            throw new CustomIllegalParamException(" <subordinateBranch> must included in params while using <bookedInfo> param. ");
        }
        if(allYearMonth.size()>MAX_MONTH_COUNT_BOOKED_INFO_QUERY){
            throw  new CustomIllegalParamException(" only 3 month can be included <bookedInfo> param.");
        }
        //加入预定信息查询条件
        fillBookedDateSearchParam(params,yearMonthAndDate,yearMonthAndDateNotBooked);
    }

    /**
     * 按页查询
     * @param vehiclePageQueryVo
     * @return
     * @throws Exception
     */
    public PageDataVo<QueryVehicleVo> getByPage(VehiclePageQueryVo vehiclePageQueryVo) throws Exception{
        Map<String, Object> params = PropertyUtils.describe(vehiclePageQueryVo);
        Integer pageSize = (Integer) params.get("limit");
        params.remove("pageSize");
        Integer pageNo = (Integer) params.get("page");
        params.remove("pageNo");
        //处理预定日期相关参数
        adjustBookedInfoParam(params,vehiclePageQueryVo);
        PageHelper.startPage(pageNo,pageSize);
        List<QueryVehicleVo> vehicles = mapper.getByPage(params);
        PageInfo<QueryVehicleVo> vehiclePageInfo = new PageInfo<>(vehicles);
        return PageDataVo.pageInfo(vehiclePageInfo);
    }

}
