/** @return the number of days displayed (0 or greater) */
 public int getNumberDaysDisplayed() {
   if (isCanonical()) {
     return new Long(CommonDateOperations.approximateDifference(getEndDate(), getStartDate()))
         .intValue();
   }
   return pathData.getEndDateIndex() - pathData.getStartDateIndex();
 }
  /**
   * @param ownerIdentifier
   * @param weekStart
   * @param visitorUsername
   * @param model
   * @return
   * @throws NotAVisitorException
   * @throws CalendarUserNotFoundException
   */
  @RequestMapping(
      value = "/admin/schedule-debug/{ownerIdentifier}/visitor-conflicts.json",
      method = RequestMethod.GET)
  public View visitorConflicts(
      @PathVariable("ownerIdentifier") long ownerIdentifier,
      @RequestParam(value = "weekStart", required = false, defaultValue = "1") int weekStart,
      @RequestParam(value = "visitorUsername", required = true) String visitorUsername,
      final ModelMap model)
      throws NotAVisitorException, CalendarAccountNotFoundException {

    ICalendarAccount visitorAccount = this.calendarAccountDao.getCalendarAccount(visitorUsername);
    if (visitorAccount == null) {
      throw new NotAVisitorException(visitorUsername + " not found");
    }
    IScheduleVisitor visitor = this.visitorDao.toVisitor(visitorAccount);

    IScheduleOwner owner = ownerDao.locateOwnerByAvailableId(ownerIdentifier);
    if (owner == null) {
      throw new CalendarAccountNotFoundException("no owner found for id " + ownerIdentifier);
    }
    VisibleScheduleRequestConstraints requestConstraints =
        VisibleScheduleRequestConstraints.newInstance(owner, weekStart);

    List<AvailableBlock> visitorConflicts =
        this.schedulingAssistantService.calculateVisitorConflicts(
            visitor,
            owner,
            requestConstraints.getTargetStartDate(),
            requestConstraints.getTargetEndDate());
    List<String> conflictBlocks = new ArrayList<String>();
    SimpleDateFormat df = CommonDateOperations.getDateTimeFormat();
    for (AvailableBlock b : visitorConflicts) {
      conflictBlocks.add(df.format(b.getStartTime()));
    }
    model.addAttribute("conflicts", conflictBlocks);

    Calendar visitorCalendar =
        this.calendarDataDao.getCalendar(
            visitorAccount,
            requestConstraints.getTargetStartDate(),
            requestConstraints.getTargetEndDate());
    model.addAttribute("visitorCalendarData", visitorCalendar.toString());
    return new MappingJacksonJsonView();
  }
  /** @param request */
  public ShareRequestDetails(final HttpServletRequest request) {

    String requestPath = new UrlPathHelper().getPathWithinServletMapping(request);
    if (requestPath.startsWith(URL_PATH_SEPARATOR)) {
      requestPath = requestPath.substring(1, requestPath.length());
    }
    requestPath = requestPath.trim();

    // sharekey[, eventId, daterange]
    this.pathData = extractPathData(requestPath);
    // if dr == 0,0, check for start/end query parameters
    if (DEFAULT_DATE_PHRASE.equals(getDatePhrase())) {
      String startValue = request.getParameter(START);
      DateFormat possibleFormat = getDateFormat(startValue);
      if (possibleFormat != null) {
        Date startDate = safeParse(possibleFormat.getSimpleDateFormat(), startValue);
        if (startDate != null) {
          this.pathData.setStartDate(CommonDateOperations.beginningOfDay(startDate));
          String endValue = request.getParameter(END);
          possibleFormat = getDateFormat(endValue);
          if (possibleFormat == null) {
            this.pathData.setEndDate(CommonDateOperations.endOfDay(startDate));
          } else {
            Date endDate = safeParse(possibleFormat.getSimpleDateFormat(), endValue);
            if (endDate != null && endDate.after(startDate)) {
              this.pathData.setEndDate(CommonDateOperations.endOfDay(endDate));
            } else {
              this.pathData.setEndDate(CommonDateOperations.endOfDay(startDate));
            }
          }

          canonical = true;
        }
      }
    }
    // identify displayFormat
    this.displayFormat = determineDisplayFormat(request);

    // only care about user-agent and request parameters if iCalendar is the output format
    if (this.displayFormat.isIcalendar()) {
      // sniff user-agent to predict client
      String userAgent = request.getHeader(USER_AGENT);
      this.client = predictClientFromUserAgent(userAgent);

      // examine request parameters to override
      String[] compatValues = request.getParameterValues(COMPATIBILITY_PARAM);
      if (null != compatValues) {
        for (String compatValue : compatValues) {
          if (BREAK_RECURRENCE.equals(compatValue)) {
            overrideBreakRecurrence = true;
          } else if (CONVERT_CLASS.equals(compatValue)) {
            overrideConvertClass = true;
          } else if (KEEP_RECURRENCE.equals(compatValue)) {
            keepRecurrence = true;
          }
        }
      }
      String clientValue = request.getParameter(CLIENT_PARAM);
      if (StringUtils.isNotBlank(clientValue)) {
        if (GOOGLE_CLIENT.equals(clientValue)) {
          this.client = Client.GOOGLE;
        } else if (APPLE_CLIENT.equals(clientValue)) {
          this.client = Client.APPLE;
        } else if (MOZILLA_CLIENT.equals(clientValue)) {
          this.client = Client.MOZILLA;
        }
      }
    }

    if (request.getParameter(ORGANIZING) != null) {
      this.organizerOnly = true;
    } else if (request.getParameter(ATTENDING) != null) {
      this.attendeeOnly = true;
    } else if (request.getParameter(PERSONAL) != null) {
      this.personalOnly = true;
    }

    if (request.getParameter(UW_SUPPORT_RDATE) != null) {
      this.requiresProblemRecurringPreference = true;
    }

    this.ipAddress = extractIpAddress(request);

    if (LOG.isDebugEnabled()) {
      LOG.debug(this);
    }
  }
  /**
   * @param model
   * @param startTimePhrase
   * @param ownerId
   * @return
   * @throws InputFormatException
   * @throws SchedulingException
   * @throws OwnerNotFoundException
   * @throws NotAVisitorException
   */
  @RequestMapping(method = RequestMethod.GET)
  protected String setupForm(
      final ModelMap model,
      @RequestParam(value = "startTime", required = true) final String startTimePhrase,
      @PathVariable("ownerIdentifier") final String ownerIdentifier)
      throws InputFormatException, SchedulingException, OwnerNotFoundException,
          NotAVisitorException {
    CalendarAccountUserDetailsImpl currentUser =
        (CalendarAccountUserDetailsImpl)
            SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    IScheduleVisitor visitor = currentUser.getScheduleVisitor();

    IScheduleOwner selectedOwner = null;
    if (StringUtils.isNumeric(ownerIdentifier)) {
      Long ownerId = Long.parseLong(ownerIdentifier);
      selectedOwner = findOwnerForVisitor(visitor, ownerId);
    } else {
      PublicProfile profile = publicProfileDao.locatePublicProfileByKey(ownerIdentifier);
      if (null != profile) {
        selectedOwner = ownerDao.locateOwnerByAvailableId(profile.getOwnerId());
      }
    }

    if (null == selectedOwner) {
      throw new OwnerNotFoundException("no owner found for " + ownerIdentifier);
    }
    model.put("owner", selectedOwner);
    Date startTime = CommonDateOperations.parseDateTimePhrase(startTimePhrase);
    validateChosenStartTime(selectedOwner.getPreferredVisibleWindow(), startTime);

    AvailableBlock targetBlock = availableScheduleDao.retrieveTargetBlock(selectedOwner, startTime);
    if (null == targetBlock) {
      throw new SchedulingException("requested time is not available");
    }

    if (selectedOwner.hasMeetingLimit()) {
      VisibleSchedule sched = schedulingAssistantService.getVisibleSchedule(visitor, selectedOwner);
      int attendingCount = sched.getAttendingCount();
      if (selectedOwner.isExceedingMeetingLimit(attendingCount)) {
        // visitor has already matched owner's appointment limit
        LOG.warn(
            "blocked attempt to use create form by visitor: "
                + visitor
                + ", target owner: "
                + selectedOwner);
        return "redirect:view.html";
      }
    }

    VEvent event = schedulingAssistantService.getExistingAppointment(targetBlock, selectedOwner);
    if (event != null) {
      model.put("event", event);
      if (this.eventUtils.isAttendingAsVisitor(event, visitor.getCalendarAccount())) {
        // redirect the visitor to the cancel form
        StringBuilder redirect = new StringBuilder("redirect:cancel.html?r=true&startTime=");
        SimpleDateFormat dateFormat = CommonDateOperations.getDateTimeFormat();
        redirect.append(startTimePhrase);
        redirect.append("&endTime=");
        redirect.append(dateFormat.format(targetBlock.getEndTime()));
        return redirect.toString();
      }

      Integer visitorLimit = this.eventUtils.getEventVisitorLimit(event);
      model.put("visitorLimit", visitorLimit);
      if (this.eventUtils.getScheduleVisitorCount(event) >= visitorLimit) {
        return "visitor/appointment-full";
      }
    }
    CreateAppointmentFormBackingObject fbo =
        new CreateAppointmentFormBackingObject(
            targetBlock, selectedOwner.getPreferredMeetingDurations());
    model.addAttribute(COMMAND_ATTR_NAME, fbo);
    return "visitor/create-appointment-form";
  }