
class Appointment {

	depend java.text.SimpleDateFormat;
    
	AppStatus {
		Booked {
			noShow(Customer c) / {addNoShow(c);} -> Final;
			
			cancel(String currentDate, String sysDate) [canCancel(currentDate, sysDate)] -> Final;
			cancel(String currentDate, String sysDate) [!canCancel(currentDate, sysDate)] / {rejectCancel();} -> Booked;
			
			update(List<Service> newOptServices, List<TimeSlot> timeSlots, String currentDate, String sysDate, boolean isNewService) [canUpdate(currentDate, sysDate)] / {updateApp(newOptServices, timeSlots, isNewService);} -> Booked;
			update(List<Service> newOptServices, List<TimeSlot> timeSlots, String currentDate, String sysDate, boolean isNewService) [!canUpdate(currentDate, sysDate)] / {rejectUpdate();} -> Booked;
			
			start(Date currentDate, Date appDate, Date currentTime, Date appTime) [canStart(currentDate, appDate, currentTime, appTime)] -> InProgress;
			start(Date currentDate, Date appDate, Date currentTime, Date appTime) [!canStart(currentDate, appDate, currentTime, appTime)] / {rejectStart();} -> Booked;
			
			end / {reject();} -> Booked;
		}
		
		InProgress {
		
			start / {rejectStart();} -> InProgress;
			
			update(List<Service> newOptServices, List<TimeSlot> timeSlots, String currentDate, String sysDate, boolean isNewService) [canUpdateInProgress(isNewService)] / {updateApp(newOptServices, timeSlots, isNewService);} -> InProgress;
			update(List<Service> newOptServices, List<TimeSlot> timeSlots, String currentDate, String sysDate, boolean isNewService) [canUpdateInProgress(isNewService)] / {rejectUpdateInProgress();} -> InProgress;
            
            cancel(String currentDate, String sysDate) / {reject();} -> InProgress;
            
			noShow(Customer c) / {rejectNoShow(c);} -> InProgress;
			
			end -> Final;
		}
	}

	private void addNoShow(Customer c) {
		c.setNoShowCount(c.getNoShowCount() + 1);
	}
	
	private void rejectUpdate() {
		throw new RuntimeException("Cannot update an appointment on the appointment date");
	}
	
	private void rejectUpdateInProgress() {
		throw new RuntimeException("Cannot reschedule an appointment while the appointment is in progress");
	}
  
	private void rejectCancel() {
	    throw new RuntimeException("Cannot cancel an appointment on the appointment date");
	}
	
	private void rejectStart() {
		throw new RuntimeException("Cannot start an appointment early or while it is already in progress.");
	}
	
	private void rejectNoShow(Customer c) {
		throw new RuntimeException("Cannot register a no-show since the appointment has already started");
	}
	
	private void reject() {
		throw new RuntimeException("Action unavailable in the current state");
	}
	
	private boolean canUpdate(String currentDate, String sysDate) {
	  return !currentDate.equals(sysDate);
	}
	
	private boolean canUpdateInProgress(boolean isNewService) {
		return isNewService;
	}
	  
	private boolean canCancel(String currentDate, String sysDate) {
		return !currentDate.equals(sysDate);
	}
	
	private boolean canStart(Date currentDate, Date appDate, Date currentTime, Date appTime) {
		return currentDate.equals(appDate) & !currentTime.before(appTime);
	}
	
	private void updateApp(List<Service> newOptServices, List<TimeSlot> timeSlots, boolean isNewService){
	   List<String> servicescopy = new ArrayList<>();
	   for(int i=0;i<this.getServiceBookings().size();i++){
		   servicescopy.add(this.getServiceBooking(i).getService().getName());
	   }
    if(!isNewService) {
			// updating existing services of the app
			// make sure when check invalid before not failing because overlapping with existing services
			for(int i=0; i< servicescopy.size();i++) {
				// might be dangerous to do this but it should work
				// deletes the time slot and the associated service booking and re-create it after
				this.getServiceBooking(0).getTimeSlot().delete();
				this.getServiceBooking(0).delete();
				
				
			}
			for(int i=0;i<newOptServices.size();i++) {
				new ServiceBooking(newOptServices.get(i),timeSlots.get(i), this);
			}
		} else {
			// adding new services to the app
			for(int i = 0; i< newOptServices.size();i++) {
				new ServiceBooking(newOptServices.get(i), timeSlots.get(i), this);
			}
		}
  }
}