/*
 * ***************************************************************************
 *
 * @(#) Copyright (C) Siemens AG 2010-2014 All Rights Reserved Confidential
 *
 * ***************************************************************************
 */
package com.smartgwt.sample.client;

import com.smartgwt.client.data.DSCallback;
import com.smartgwt.client.data.DataSource;
import com.smartgwt.client.types.TimeDisplayFormat;
import com.smartgwt.client.types.TimeUnit;
import com.smartgwt.client.util.DateUtil;
import com.smartgwt.client.widgets.Button;
import com.smartgwt.client.widgets.calendar.CalendarEvent;
import com.smartgwt.client.widgets.calendar.CalendarView;
import com.smartgwt.client.widgets.calendar.CellHoverCustomizer;
import com.smartgwt.client.widgets.calendar.EventHoverHTMLCustomizer;
import com.smartgwt.client.widgets.calendar.EventWindow;
import com.smartgwt.client.widgets.calendar.HeaderLevel;
import com.smartgwt.client.widgets.calendar.Lane;
import com.smartgwt.client.widgets.calendar.Timeline;
import com.smartgwt.client.widgets.calendar.events.EventRepositionStop;
import com.smartgwt.client.widgets.calendar.events.EventRepositionStopHandler;
import com.smartgwt.client.widgets.calendar.events.EventResizeStop;
import com.smartgwt.client.widgets.calendar.events.EventResizeStopHandler;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.grid.ListGridField;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.layout.HLayout;
import com.smartgwt.client.widgets.layout.VLayout;
import java.util.ArrayList;
import java.util.Date;
import com.smartgwt.client.data.DSRequest;
import com.smartgwt.client.data.DSResponse;
import com.smartgwt.client.data.OperationBinding;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.data.RestDataSource;
import com.smartgwt.client.data.fields.DataSourceBooleanField;
import com.smartgwt.client.data.fields.DataSourceDateTimeField;
import com.smartgwt.client.data.fields.DataSourceIntegerField;
import com.smartgwt.client.data.fields.DataSourceTextField;
import com.smartgwt.client.types.DSDataFormat;
import com.smartgwt.client.types.DSOperationType;
import com.smartgwt.client.types.DSProtocol;
import com.smartgwt.client.util.SC;
import java.util.HashMap;
import java.util.Map;

/**
 * This class hold the timeline component presenting the orders execution.
 *
 * @author z0033z0j
 */
public class TimelineRestDatasource extends VLayout {

  /**
   * The start date of the timeline.
   */
  private Date minDate = null;

  /**
   * The end date of the timeline.
   */
  private Date maxDate = null;

  /**
   * The datasource with orders.
   */
  private final DataSource dataSource;

  /**
   * The timeline itself.
   */
  private final Timeline timeline;

  /**
   * The lanes of the timeline.
   */
  private final Map<String, Lane> lanes = new HashMap<String, Lane>();

  /**
   * the width of the lane titles<p>
   * use fixed width of the timeline lane title column, to accommodate fully the
   * title text
   */
  private static final int TIMELINE_TITLE_WIDTH = 160;

  /**
   * The number of columns in the timeline.
   * <p>
   * Each column represents an hour.
   */
  private int columnCount;

  /**
   * Well, now.
   */
  private final Date now = new Date();

  /**
   * The number of columns which will be added/removed from the timeline when
   * zooming in/out.<p>
   * Best if this is divisible by 2.
   */
  private static final int ZOOM_RANGE = 6;

  /**
   * Creates an instance of the scheduler pane. Setting up the time line and
   * fill it with data.
   *
   * @param dataSource data source with orders
   */
  public TimelineRestDatasource() {
    this.dataSource = OrderTimelineEventDS.getInstance();
    this.columnCount = 24;
    this.setWidth100();
    this.setHeight100();

    HLayout zoomLayout = createZoomLayout();
    timeline = createTimeline();
    createLanes();

    boolean showBroken = true;
    if (showBroken) {
      broken();
    } else {
      working();
    }

    addMembers(zoomLayout, timeline);
  }

  private void broken() {
    createLanes();
    timeline.setDataSource(dataSource);
    timeline.invalidateCache();
    timeline.fetchData();
    /**
     NOTE: "records.length" WILL BE ZERO.
     */
//        timeline.fetchData(null, new DSCallback() {
//
//          @Override
//          public void execute(DSResponse dsResponse, Object data, DSRequest dsRequest) {
//            determineMinMaxDate();
//            createIndicator();
//            updateResolution();
//
//            Record[] records = dsResponse.getData();
//            SC.logWarn("records.length == " + records.length);
//          }
//        });        
  }

  private void working() {
    createLanes();
    //works, but not what we want
    dataSource.fetchData(null, new DSCallback() {

      @Override
      public void execute(DSResponse dsResponse, Object data, DSRequest dsRequest) {
        determineMinMaxDate();
        createIndicator();
        updateResolution();

        timeline.setData(dsResponse.getData());
      }
    });
  }

  /**
   * Creates and initializes the timeline component.
   *
   * @return
   */
  private Timeline createTimeline() {
    Timeline t = new Timeline();
    t.setCanReorderLanes(false);
    ListGridField laneField = new ListGridField("title", "HiddenTitle");
    laneField.setShowTitle(false);

    laneField.setWidth(TIMELINE_TITLE_WIDTH);
    t.setLaneFields(laneField);
    t.setTimeFormatter(TimeDisplayFormat.TO24HOURTIME);
    t.setShowSnapGrid(false);
    t.setChildrenSnapToGrid(false);
    t.setChildrenSnapResizeToGrid(false);
    t.setWidth100();
    t.setHeight100();
    t.setAutoFetchData(false);
    t.setShowControlsBar(true);
    t.setHideUnusedLanes(false);
    t.setAllowDurationEvents(false);
    t.setUseSublanes(true);
    t.setCanEditLane(false);
    t.setCanEditSublane(false);
    t.setCanCreateEvents(false);
    /**
     * Editing must be enabled for dragging and resizing to work.
     */
    t.setCanEditEvents(true);
    t.setCanRemoveEvents(false);
    t.setDisableWeekends(false);
    t.setShowEventDescriptions(false);
    t.setColumnsPerPage(2);
    t.setLaneEventPadding(0);
    t.setShowZones(false);
    t.setShowIndicators(true);
    t.setEventAutoArrange(false);
    t.setEventHoverHTMLCustomizer(new EventHoverHTMLCustomizer() {
      @Override
      public String getEventHoverHTML(CalendarEvent calendarEvent, EventWindow eventWindow) {
        timeline.getEventStartDate(calendarEvent);
        return buildEventDescription(calendarEvent); //calendarEvent.getDescription();
      }
    });
    t.setShowViewHovers(true);
    t.setShowLaneFieldHovers(true);
    /**
     * Show hovers above lane titles.
     */
    t.setCellHoverCustomizer(new CellHoverCustomizer() {
      @Override
      public String getHoverHTML(ListGridRecord record, int rowNum,
              int colNum, Date date, String defaultValue, CalendarView view) {
        /**
         * According to the javadoc of Timeline.setShowLaneFieldHovers(), the
         * date will be null when hovering above the lane titles.
         */
        if (date != null) {
          return null;
        }

        return buildLaneDescription(record);
      }
    });
    t.addEventResizeStopHandler(new EventResizeStopHandler() {

      @Override
      public void onEventResizeStop(EventResizeStop event) {
//        updateOrder(event.getNewEvent());
        //cancel the event - update logic will be handled server-side
        event.cancel();
      }
    });
    t.addEventRepositionStopHandler(new EventRepositionStopHandler() {
      @Override
      public void onEventRepositionStop(EventRepositionStop event) {
//        updateOrder(event.getNewEvent());
        //cancel the event - update logic will be handled server-side
        event.cancel();
      }
    });

    // set initial resolution
    HeaderLevel[] headers = new HeaderLevel[]{
      new HeaderLevel(TimeUnit.DAY),
      new HeaderLevel(TimeUnit.HOUR)
    };
    int headerWidth = 60;
    headers[1].setHeaderWidth(headerWidth);
    t.setEventSnapGap(2);
    t.setResolution(headers, TimeUnit.HOUR, columnCount);
    t.setWidth100();
    t.setHeight100();

    return t;
  }

  /**
   * This method re-creates the lanes on an update of the data.
   *
   * @param recordList order records which make up the lanes.
   */
  private void createLanes() {

    minDate = null;
    maxDate = null;

    // clear old content first
    removeAllLanes();

    //Hardcoded data for the test
    int size = 9;
    Lane[] laneArray = new Lane[size];
    Integer[] ids = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
    String[] names = new String[]{"ProdOrder.01", "ProdOrder.02", "ProdOrder.03", "ProdOrder.04", "ProdOrder.05", "ProdOrder.06", "ProdOrder.07", "ProdOrder.08", "ProdOrder.09"};
    String[] states = new String[]{"TERMINATED", "TERMINATED", "STARTED", "PLANNED", "PLANNED", "PLANNED", "PLANNED", "PLANNED", "PLANNED"};
    Date[] plannedStartDates = new Date[]{
      new Date(2014 - 1900, 8, 15, 7, 0, 0),
      new Date(2014 - 1900, 8, 15, 9, 0, 0),
      new Date(2014 - 1900, 8, 15, 13, 0, 0),
      new Date(2014 - 1900, 8, 16, 6, 0, 0),
      new Date(2014 - 1900, 8, 16, 13, 30, 0),
      new Date(2014 - 1900, 8, 16, 13, 50, 0),
      new Date(2014 - 1900, 8, 16, 16, 50, 0),
      new Date(2014 - 1900, 8, 17, 4, 50, 0),
      new Date(2014 - 1900, 8, 17, 8, 0, 0),};
    Date[] plannedEndDates = new Date[]{
      new Date(2014 - 1900, 8, 15, 9, 30, 0),
      new Date(2014 - 1900, 8, 15, 10, 30, 0),
      new Date(2014 - 1900, 8, 15, 14, 40, 0),
      new Date(2014 - 1900, 8, 16, 8, 30, 0),
      new Date(2014 - 1900, 8, 16, 16, 30, 0),
      new Date(2014 - 1900, 8, 16, 17, 0, 0),
      new Date(2014 - 1900, 8, 17, 1, 0, 0),
      new Date(2014 - 1900, 8, 17, 9, 0, 0),
      new Date(2014 - 1900, 8, 17, 10, 0, 0),};
    Date[] actualStartDates = new Date[]{
      new Date(2014 - 1900, 8, 15, 7, 5, 0),
      new Date(2014 - 1900, 8, 15, 9, 5, 0),
      new Date(2014 - 1900, 8, 15, 13, 15, 0),
      new Date(2014 - 1900, 8, 16, 6, 5, 0),
      null,
      null,
      null,
      null,
      null,};
    Date[] actualEndDates = new Date[]{
      new Date(2014 - 1900, 8, 15, 7, 5, 0),
      new Date(2014 - 1900, 8, 15, 9, 5, 0),
      new Date(2014 - 1900, 8, 15, 13, 15, 0),
      new Date(2014 - 1900, 8, 16, 6, 5, 0),
      null,
      null,
      null,
      null,
      null,};
//    Lane[] laneArray = new Lane[recordList.getLength()];
    // create lanes
    for (int i = 0; i < size; i++) {
//      Record record = recordList.get(i);
//      Lane lane = getOrCreateLane(record.getAttributeAsInt("id"), record.getAttribute("name"), record.getAttribute("state"));
      Lane lane = getOrCreateLane(ids[i], names[i], states[i]);
      laneArray[i] = lane;

      // we also do an update on min and max date here to avoid looping a second time.
      updateMinAndMaxDate(plannedStartDates[i]);
      updateMinAndMaxDate(plannedEndDates[i]);
      updateMinAndMaxDate(actualStartDates[i]);
      updateMinAndMaxDate(actualEndDates[i]);
    }
    timeline.setLanes(laneArray);

  }

  /**
   * Set up the buttons for resolution of the displayed time range.
   *
   * @return layout panel.
   */
  private HLayout createZoomLayout() {
    HLayout layout = new HLayout(5);

    Button zoomIn = new Button("");
    zoomIn.setWidth(20);
    zoomIn.setHeight(20);
    zoomIn.setIcon("icons/16/zoom_in.png");
    zoomIn.setIconSize(16);
    zoomIn.setBaseStyle("transparentButton");

    zoomIn.addClickHandler(new ClickHandler() {
      @Override
      public void onClick(ClickEvent event) {
        timelineZoom(-ZOOM_RANGE);
      }
    });

    Button zoomOut = new Button("");
    zoomOut.setWidth(20);
    zoomOut.setHeight(20);
    zoomOut.setIcon("icons/16/zoom_out.png");
    zoomOut.setIconSize(16);
    zoomOut.setBaseStyle("transparentButton");
    zoomOut.addClickHandler(new ClickHandler() {
      @Override
      public void onClick(ClickEvent event) {
        timelineZoom(+ZOOM_RANGE);
      }
    });

    layout.addMembers(zoomIn, zoomOut);

    return layout;
  }

  /**
   * Set the indicator for the current point in time.
   */
  private void createIndicator() {
    CalendarEvent indicator = new CalendarEvent(-1, DateUtil.TOSHORT24HOURTIME.format(now), "", now, null);
    indicator.setStyleName("indicatorRed");
    //The canDrag is enabled just because otherwise the gripper is not shown.
    indicator.setCanDrag(true);
    timeline.setIndicators(indicator);
  }

  /**
   * Updates the resoultion of the timeline.
   */
  private void updateResolution() {
    columnCount = (int) ((maxDate.getTime() - minDate.getTime()) / 1000 / 60 / 60);
    int fourDays = 4 * 24;
    if (columnCount > fourDays) {
      //Do not make the timeline bigger than 4 days
      columnCount = fourDays;
    }
    timeline.setResolution(timeline.getHeaderLevels(), TimeUnit.HOUR, columnCount);
  }

  /**
   * Set the timeline zoom level.
   *
   * @param offset zooming offset.
   */
  private void timelineZoom(int offset) {
    int tmpColCount = columnCount + offset;
    if (tmpColCount > 24 * 4 || tmpColCount < 24 / 2) {
      return;
    }
    this.columnCount = tmpColCount;
    timeline.setResolution(timeline.getHeaderLevels(), TimeUnit.HOUR, columnCount);
    long halfRange = (offset / 2) * 60L * 60L * 1000L;
    Date lastChosenDate = timeline.getChosenDate();
    Date chosenDate = new Date(lastChosenDate.getTime() - halfRange);
    timeline.setChosenDate(chosenDate);
    timeline.redraw();
  }

  /**
   * determine min and max date.
   */
  private void determineMinMaxDate() {
    minDate = minDate == null ? new Date() : minDate;
    long fourteenDays = 14 * 24 * 60 * 60 * 1000L;
    maxDate = maxDate == null ? new Date(minDate.getTime() + fourteenDays) : maxDate;
    //TODO: Cannot change configuration property 'startDate' to Mon Sep 15 07:00:00 GMT+200 2014 now that component isc_Timeline_0 has been created.
    timeline.setStartDate(minDate);
    timeline.setEndDate(maxDate);
    timeline.setChosenDate(minDate);

  }

  /**
   * Updates (or not) the min and max date by comparing it to provided date.
   *
   * @param date date to compare with.
   */
  private void updateMinAndMaxDate(Date date) {

    // Don't update on a null date
    if (date == null) {
      return;
    }

    if (minDate == null) {
      minDate = date;
    } else {
      minDate = (date.before(minDate)) ? date : minDate;
    }

    if (maxDate == null) {
      maxDate = date;
    } else {
      maxDate = (date.after(maxDate)) ? date : maxDate;
    }
  }

  /**
   * Build the event description string by calling it from hover listener.
   *
   * @param rec event record to get the description for.
   * @return event description.
   */
  private String buildEventDescription(Record rec) {
    if (rec == null) {
      return "";
    }
    String name = rec.getAttributeAsString("orderName");
    String type = rec.getAttributeAsString("type");
    String desc = rec.getAttributeAsString("description");
    String prio = rec.getAttributeAsString("priority");
    String state = rec.getAttributeAsString("state");
    Date startDate = rec.getAttributeAsDate("startDate");
    Date endDate = rec.getAttributeAsDate("endDate");
    String discriminator = rec.getAttributeAsString("discriminator");

    StringBuilder sb = new StringBuilder();
    sb.append("<div>")
            .append("<b><u>").append(name).append("</u></b>");

    if (discriminator.equals("PLANNED")) {
      sb.append("<br><b>").append("Planned interval").append(":</b>");
    } else if (discriminator.equals("EARLIEST_START")) {
      sb.append("<br><b>").append("Buffered planned start").append(":</b> ");
    } else if (discriminator.equals("LATEST_END")) {
      sb.append("<br><b>").append("Buffered planned end").append(":</b> ");
    } else if (discriminator.equals("ACTUAL")) {
      sb.append("<br><b>").append("Actual execution").append(":</b> ");
    } else if (discriminator.equals("ESTIMATED")) {
      sb.append("<br><b>").append("Estimated execution").append(":</b> ");
    }

    sb.append("<br>");
    sb.append("<table class=\"calendarItemTableContent\">");
    if (startDate != null) {
      sb.append("<tr><td class=\"calendarItemColContent\"><b>")
              .append("Date From")
              .append(":</b></td><td class=\"calendarItemColContent\">")
              .append(DateUtil.formatAsShortDatetime(startDate)).append("</td></tr>");
    }
    if (endDate != null) {
      sb.append("<tr><td class=\"calendarItemColContent\"><b>")
              .append("Date To")
              .append(":</b></td><td class=\"calendarItemColContent\">")
              .append(DateUtil.formatAsShortDatetime(endDate)).append("</td></tr>");
    }
    sb.append("</table><br>");

    sb.append("<u>Info:</u><br>")
            .append("<img src=\"images/icons/16/")
            .append(getStateIconName(state))
            .append("\"></img> ")
            .append("<br><b>")
            .append("Type")
            .append(":</b> ")
            .append(((type != null) ? type : "-"))
            .append("<br><b>").append("Priority").append(":</b> ")
            .append(((prio != null) ? prio : "-"))
            .append("<br><b>").append("State").append(":</b> ")
            .append(((state != null) ? state : "-"));

    sb.append("</div>");
    return sb.toString();
  }

  /**
   * Build the description when hovering over the first column with lane names.
   *
   * @param rec record of current lane.
   * @return lane description
   */
  private String buildLaneDescription(Record rec) {
    if (rec == null) {
      return "";
    }
    String type = rec.getAttributeAsString("type");
    String desc = rec.getAttributeAsString("description");
    String prio = rec.getAttributeAsString("priority");
    String state = rec.getAttributeAsString("state");
    Date plannedStartDate = rec.getAttributeAsDate("plannedStart");
    Date plannedEndDate = rec.getAttributeAsDate("plannedEnd");
    Date actualStartDate = rec.getAttributeAsDate("actualStart");
    Date actualEndDate = rec.getAttributeAsDate("actualEnd");
    Date earliestStartDate = rec.getAttributeAsDate("earliestStart");
    Date latestEndDate = rec.getAttributeAsDate("latestEnd");

    StringBuilder sb = new StringBuilder();
    sb.append("<div>")
            .append((desc != null) ? desc : "")
            .append("<img src=\"images/icons/16/")
            .append(getStateIconName(state))
            .append("\"></img> ")
            .append("<br><b>")
            .append("Type")
            .append(":</b> ")
            .append(((type != null) ? type : "-"))
            .append("<br><b>").append("Priority").append(":</b> ")
            .append(((prio != null) ? prio : "-"))
            .append("<br><b>").append("State").append(":</b> ")
            .append(((state != null) ? state : "-"));

    sb.append("<table class=\"calendarItemTableContent\">");
    if (plannedStartDate != null) {
      sb.append("<tr><td class=\"calendarItemColContent\"><b>")
              .append("Planned Start")
              .append(":</b></td><td class=\"calendarItemColContent\">")
              .append(DateUtil.formatAsShortDatetime(plannedStartDate)).append("</td></tr>");
    }
    if (plannedEndDate != null) {
      sb.append("<tr><td class=\"calendarItemColContent\"><b>")
              .append("Planned End")
              .append(":</b></td><td class=\"calendarItemColContent\">")
              .append(DateUtil.formatAsShortDatetime(plannedEndDate)).append("</td></tr>");
    }
    if (earliestStartDate != null) {
      sb.append("<tr><td class=\"calendarItemColContent\"><b>")
              .append("Earliest Start")
              .append(":</b></td><td class=\"calendarItemColContent\">")
              .append(DateUtil.formatAsShortDatetime(earliestStartDate)).append("</td></tr>");
    }
    if (latestEndDate != null) {
      sb.append("<tr><td class=\"calendarItemColContent\"><b>")
              .append("Latest End")
              .append(":</b></td><td class=\"calendarItemColContent\">")
              .append(DateUtil.formatAsShortDatetime(latestEndDate)).append("</td></tr>");
    }
    if (actualStartDate != null) {
      sb.append("<tr><td class=\"calendarItemColContent\"><b>")
              .append("Started at")
              .append(":</b></td><td class=\"calendarItemColContent\">")
              .append(DateUtil.formatAsShortDatetime(actualStartDate)).append("</td></tr>");
    }
    if (actualEndDate != null) {
      sb.append("<tr><td class=\"calendarItemColContent\"><b>")
              .append("Finished at")
              .append(":</b></td><td class=\"calendarItemColContent\">")
              .append(DateUtil.formatAsShortDatetime(actualEndDate)).append("</td></tr>");
    }
    sb.append("</table></div>");
    return sb.toString();
  }

  private Lane getOrCreateLane(int orderID, String orderName, String orderState) {
    String laneName = orderID + " - " + orderName + " - " + orderState;
    Lane lane = lanes.get(laneName);
    if (lane == null) {
      lane = createLane(orderName, laneName, 50);
      lanes.put(laneName, lane);
    }
    return lane;
  }

  private Lane createLane(String name, String title, Integer height) {
    ArrayList<Lane> sublanes = new ArrayList<Lane>();

    sublanes.add(createSubLane("1", 6));
    sublanes.add(createSubLane("planned", 16));
    sublanes.add(createSubLane("2", 6));
    sublanes.add(createSubLane("actual", 16));
    sublanes.add(createSubLane("3", 6));

    Lane lane = new Lane(name, title);
    lane.setHeight(height);
    lane.setSublanes(sublanes.toArray(new Lane[sublanes.size()]));

    return lane;
  }

  private Lane createSubLane(String name, int height) {
    final Lane lane = new Lane(name, name);
    lane.setHeight(height);
    return lane;
  }

  private void removeAllLanes() {
//    for (Lane lane : timeline.getLanes()) {
//      timeline.removeLane(lane.getName());
//    }
    lanes.clear();
  }

  private String getStateIconName(String state) {
    if (state.equalsIgnoreCase("PLANNED")) {
      return "STATE_PLANNED.png";
    } else if (state.equalsIgnoreCase("STARTED")) {
      return "STATE_RUNNING.gif";
    } else if (state.equalsIgnoreCase("TERMINATED")) {
      return "STATE_TERMINATED.png";
    } else {
      return "order_templates.png";
    }
  }

  static class OrderTimelineEventDS extends RestDataSource {

    private static OrderTimelineEventDS instance = null;

    /**
     * Get an instance of the datasource.
     *
     * @return
     */
    public static OrderTimelineEventDS getInstance() {
      if (instance == null) {
        instance = new OrderTimelineEventDS("OrderTimelineEventsDS");
      }
      return instance;
    }

    /**
     * Creates a instance of the data source.
     *
     * @param id id of data source.
     */
    private OrderTimelineEventDS(String id) {

      setID(id);
      setDataFormat(DSDataFormat.XML);
      setRecordXPath("data/orderCalendarEvent");
      setUseHttpProxy(false);

      // define data source fields cause it is specific.
      DataSourceTextField idField = new DataSourceTextField("eventId", "eventId", 20, false);
      idField.setPrimaryKey(true);
      DataSourceTextField nameField = new DataSourceTextField("name", "name", 100, false);
      DataSourceTextField descField = new DataSourceTextField("description", "description", 100, false);
      DataSourceDateTimeField startField = new DataSourceDateTimeField("startDate", "startDate", 100, false);
      DataSourceDateTimeField endField = new DataSourceDateTimeField("endDate", "endDate", 100, false);
      DataSourceTextField styleField = new DataSourceTextField("styleName");
      DataSourceTextField laneField = new DataSourceTextField("lane");
      DataSourceTextField subLaneField = new DataSourceTextField("sublane");

      DataSourceDateTimeField plannedStartField = new DataSourceDateTimeField("plannedStart", "plannedStart", 100, false);
      DataSourceDateTimeField plannedEndField = new DataSourceDateTimeField("plannedEnd", "plannedEnd", 100, false);
      DataSourceDateTimeField actualStartField = new DataSourceDateTimeField("actualStart", "actualStart", 100, false);
      DataSourceDateTimeField actualEndField = new DataSourceDateTimeField("actualEnd", "actualEnd", 100, false);
      DataSourceDateTimeField earliestStartField = new DataSourceDateTimeField("earliestStart", "earliestStart", 100,
              false);
      DataSourceDateTimeField latestEndField = new DataSourceDateTimeField("latestEnd", "latestEnd", 100, false);
      DataSourceBooleanField canDragField = new DataSourceBooleanField("canDrag");
      DataSourceBooleanField canResizeField = new DataSourceBooleanField("canResize");
      DataSourceBooleanField canEditField = new DataSourceBooleanField("canEdit");
      DataSourceIntegerField orderIdField = new DataSourceIntegerField("orderId");

      setFields(idField, nameField, descField, startField, endField, styleField,
              laneField, subLaneField,
              plannedStartField, plannedEndField, actualStartField, actualEndField, earliestStartField, latestEndField,
              canDragField, canResizeField, canEditField, orderIdField);

      OperationBinding fetch = new OperationBinding();
      fetch.setOperationType(DSOperationType.FETCH);
      fetch.setDataProtocol(DSProtocol.GETPARAMS);
      OperationBinding add = new OperationBinding();
      add.setOperationType(DSOperationType.ADD);
      add.setDataProtocol(DSProtocol.POSTMESSAGE);
      OperationBinding update = new OperationBinding();
      update.setOperationType(DSOperationType.UPDATE);
      DSRequest updateProps = new DSRequest();
      updateProps.setHttpMethod("PUT");
      update.setRequestProperties(updateProps);
      update.setDataProtocol(DSProtocol.POSTMESSAGE);
      OperationBinding remove = new OperationBinding();
      remove.setOperationType(DSOperationType.REMOVE);
      remove.setDataProtocol(DSProtocol.POSTMESSAGE);

      setOperationBindings(fetch, add, update, remove);

      String url = "http://localhost:7001/opal-control-backend/api/order-timeline-events";
      setFetchDataURL(url);
      setAddDataURL(url);
      setUpdateDataURL(url);
      setRemoveDataURL(url);

    }

    /**
     * Overriden tranformation of the request. This method checks for a plan id.
     * If it exists, then it will be added as a query parameter.
     *
     * @param request REST request
     * @return modified REST request.
     */
    @Override
    protected Object transformRequest(DSRequest request) {
      return super.transformRequest(request);
    }

    /**
     * Overriding transformation of response.
     *
     * @param dsResponse response after fetching or updating the data.
     * @param dsRequest request data
     * @param data
     */
    @Override
    protected void transformResponse(DSResponse dsResponse, DSRequest dsRequest, Object data) {
      Record[] records = dsResponse.getData();
      for (Record record : records) {
//      String[] attributes = record.getAttributes();
//      for (String attr : attributes) {
//        SC.logWarn(attr + " == " + record.getAttribute(attr));
//      }
        SC.logWarn("startDate == " + record.getAttribute("startDate"));
        SC.logWarn("startDate as String == " + record.getAttributeAsString("startDate"));
        SC.logWarn("startDate as Date == " + record.getAttributeAsDate("startDate"));
        SC.logWarn("endDate == " + record.getAttribute("endDate"));
        SC.logWarn("endDate as String == " + record.getAttributeAsString("endDate"));
        SC.logWarn("endDate as Date == " + record.getAttributeAsDate("endDate"));
      }

      super.transformResponse(dsResponse, dsRequest, data);
    }

  }

}
