content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   */
  package org.apache.causeway.extensions.fullcalendar.wkt.integration.fc;
  
  import java.time.ZonedDateTime;
  import java.time.format.DateTimeFormatter;
  import java.util.NoSuchElementException;
  import java.util.Optional;
  import java.util.UUID;
  
  import org.apache.wicket.IRequestListener;
  import org.apache.wicket.markup.head.IHeaderResponse;
  import org.apache.wicket.util.lang.Objects;
  
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.callback.AjaxConcurrency;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.callback.ClickedEvent;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.callback.DateRangeSelectedCallback;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.callback.DroppedEvent;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.callback.EventClickedCallback;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.callback.GetEventsCallback;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.callback.ResizedEvent;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.callback.SelectedRange;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.callback.View;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.res.FullCalendarEventSourceEvents;
  import org.apache.causeway.extensions.fullcalendar.wkt.integration.fc.res.FullCalendarIntegrationJsReference;
  
  public class FullCalendar extends AbstractFullCalendar implements IRequestListener {
  
      private static final long serialVersionUID = 1L;
  
  	private final CalendarConfig calendarConfig;
  	private GetEventsCallback getEvents;
  	private DateRangeSelectedCallback dateRangeSelected;
  	private EventClickedCallback dateClicked;
  //	private EventDroppedCallback eventDropped;
  //  private EventResizedCallback eventResized;
  //	private ViewDisplayCallback viewDisplay;
  
  	public FullCalendar(final String id, final CalendarConfig calendarConfig) {
  		super(id);
  		this.calendarConfig = calendarConfig;
  		setVersioned(false);
  	}
  
  	@Override
  	protected boolean getStatelessHint() {
  		return false;
  	}
  
  	@Override
  	protected void onInitialize() {
  		super.onInitialize();
  		for (EventSource source : calendarConfig.getEventSources()) {
  			if (source.getId() == null) {
  				String uuid = UUID.randomUUID().toString().replaceAll("[^A-Za-z0-9]", "");
  				source.setId(uuid);
  			}
  		}
  	}
  
  	@Override
  	protected void onBeforeRender() {
  		super.onBeforeRender();
  		setupCallbacks();
  	}
  
  	protected void setupCallbacks() {
  
  		if (getEvents == null) {
  			add(getEvents = new GetEventsCallback());
  		}
  
  		FullCalendarEventSourceEvents.setupEventSourceUrls(calendarConfig, getEvents::getUrl);
  
  		if (dateClicked == null) {
  			add(dateClicked = new EventClickedCallback() {
                  private static final long serialVersionUID = 1L;
                  @Override
  				protected void onClicked(final ClickedEvent event, final CalendarResponse response) {
  					onEventClicked(event, response);
  				}
  			});
  		}
  		calendarConfig.setEventClick(dateClicked.getHandlerScript());
  
  		if (dateRangeSelected == null) {
  			add(dateRangeSelected = new DateRangeSelectedCallback() {
                  private static final long serialVersionUID = 1L;
                  @Override
  				protected void onSelect(final SelectedRange range, final CalendarResponse response) {
  					FullCalendar.this.onDateRangeSelected(range, response);
  				}
  			});
  
  		}
  		calendarConfig.setSelect(dateRangeSelected.getHandlerScript());
  
  //		if (eventDropped == null) {
  //			add(eventDropped = new EventDroppedCallback() {
  //                private static final long serialVersionUID = 1L;
  //                @Override
  //				protected boolean onEventDropped(final DroppedEvent event, final CalendarResponse response) {
  //					return FullCalendar.this.onEventDropped(event, response);
  //				}
  //			});
  //		}
  //
  //		if (eventResized == null) {
  //			add(eventResized = new EventResizedCallback() {
  //                private static final long serialVersionUID = 1L;
  //                @Override
  //				protected boolean onEventResized(final ResizedEvent event, final CalendarResponse response) {
  //					return FullCalendar.this.onEventResized(event, response);
  //				}
  //
  //			});
  //		}
  //
  //		if (viewDisplay == null) {
  //			add(viewDisplay = new ViewDisplayCallback() {
  //                private static final long serialVersionUID = 1L;
  //                @Override
  //				protected void onViewDisplayed(final View view, final CalendarResponse response) {
  //					FullCalendar.this.onViewDisplayed(view, response);
  //				}
  //			});
  //		}
  
  		getPage().dirty();
  	}
  
  	@Override
  	public void renderHead(final IHeaderResponse response) {
  		super.renderHead(response);
  		response.render(FullCalendarIntegrationJsReference.domReadyScript(getMarkupId(), calendarConfig));
  	}
  
  	protected boolean onEventDropped(final DroppedEvent event, final CalendarResponse response) {
  		return false;
  	}
  
  	protected boolean onEventResized(final ResizedEvent event, final CalendarResponse response) {
  		return false;
  	}
  
  	protected void onDateRangeSelected(final SelectedRange range, final CalendarResponse response) {
  
  	}
  
  	protected void onEventClicked(final ClickedEvent event, final CalendarResponse response) {
  
  	}
  
  	protected void onViewDisplayed(final View view, final CalendarResponse response) {
  
  	}
  
  	public AjaxConcurrency getAjaxConcurrency() {
  		return AjaxConcurrency.QUEUE;
  	}
  
  	@Override
  	public void onRequest() {
  		getEvents.onRequest();
  
  	}
  
  	// -- EVENT MANAGEMENT
  
      public Optional<EventSource> lookupEventSource(final String id) {
          for (EventSource source : calendarConfig.getEventSources()) {
              if (Objects.equal(id, source.getId())) {
                  return Optional.ofNullable(source);
              }
          }
          return Optional.empty();
      }
  
      public Event getEvent(final String sourceId, final String eventId) throws NoSuchElementException {
          return getEventSource(sourceId).getEventProvider().getEventForId(eventId);
      }
  
      public EventSource getEventSource(final String id) throws NoSuchElementException {
          return lookupEventSource(id)
          .orElseThrow(()->
              new NoSuchElementException("Event source with id: " + id + " not found"));
      }
  
      // -- START/END UTILITY
  
      /**
       * An ISO8601 string representation of the start date.
       * It will have a time zone offset according to the calendar’s timeZone like 2018-09-01T12:30:00-05:00.
       */
      private static final String START_KEY = "startStr";
      /**
       * An ISO8601 string representation of the end date.
       * It will have a time zone offset according to the calendar’s timeZone like 2018-09-01T12:30:00-05:00.
       */
      private static final String END_KEY = "endStr";
      /**
       * @see "https://fullcalendar.io/docs/timeZone"
       */
      private static final String TIMEZONE_KEY = "timeZone";
  
      public ZonedDateTime startInstant() {
          var startStr = getRequest().getRequestParameters().getParameterValue(START_KEY).toOptionalString();
          return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(startStr, ZonedDateTime::from);
      }
  
      public ZonedDateTime endInstant() {
          var endStr = getRequest().getRequestParameters().getParameterValue(END_KEY).toOptionalString();
          return DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(endStr, ZonedDateTime::from);
      }
  
      public String clientTimeZone() {
          var timeZone = getRequest().getRequestParameters()
                  .getParameterValue(TIMEZONE_KEY).toOptionalString();
          return timeZone;
      }
  
  }
  