/**
 * Copyright 2017 IBM Corp. All Rights Reserved.
 *
 * Licensed 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 com.ibm.watson.developer_cloud.util;

import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

/**
 * Date deserializer for different date format across all the Watson APIs.
 */
public class DateDeserializer implements JsonDeserializer<Date> {
  private static final String DATE_FROM_ALCHEMY = "yyyyMMdd'T'HHmmss";
  private static final String DATE_FROM_DIALOG = "yyyy-MM-dd HH:mm:ss";

  /** The Constant DATE_UTC. */
  public static final String DATE_UTC = "yyyy-MM-dd'T'HH:mm:ss.SSS";
  private static final String DATE_UTC_WITH_TS = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
  private static final String DATE_WITHOUT_SECONDS = "yyyy-MM-dd'T'HH:mm:ssZ";
  private static final String DATE_WITH_SECONDS = "yyyy-MM-dd'T'HH:mm:ss";
  private static final String DATE_8601 = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
  private static final String DATE_8601_WITHOUT_MS = "yyyy-MM-dd'T'HH:mm:ssX";

  // SimpleDateFormat is NOT thread safe - they require private visibility and synchronized access
  private final SimpleDateFormat alchemyDateFormatter = new SimpleDateFormat(DATE_FROM_ALCHEMY);
  private final SimpleDateFormat dialogDateFormatter = new SimpleDateFormat(DATE_FROM_DIALOG);
  private final SimpleDateFormat utcDateFormatter = new SimpleDateFormat(DATE_UTC);
  private final SimpleDateFormat utcWithoutSecondsDateFormatter = new SimpleDateFormat(DATE_WITHOUT_SECONDS);
  private final SimpleDateFormat utcWithSecondsDateFormatter = new SimpleDateFormat(DATE_WITH_SECONDS);
  private final SimpleDateFormat iso8601DateFormatter = new SimpleDateFormat(DATE_8601);
  private final SimpleDateFormat iso8601WithoutMsDateFormatter = new SimpleDateFormat(DATE_8601_WITHOUT_MS);
  private final SimpleDateFormat utcWithTsFormatter = new SimpleDateFormat(DATE_UTC_WITH_TS);

  private final List<SimpleDateFormat> dateFormatters =
      Arrays.asList(utcDateFormatter, utcWithoutSecondsDateFormatter, dialogDateFormatter,
              alchemyDateFormatter, utcWithSecondsDateFormatter);

  private final List<SimpleDateFormat> iso8601Formatters =
      Arrays.asList(utcWithTsFormatter, iso8601DateFormatter, iso8601WithoutMsDateFormatter);

  private static final Logger LOG = Logger.getLogger(DateDeserializer.class.getName());

  /*
   * (non-Javadoc)
   *
   * @see com.google.gson.JsonDeserializer#deserialize(com.google.gson.JsonElement, java.lang.reflect.Type,
   * com.google.gson.JsonDeserializationContext)
   */
  @Override
  public synchronized Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException {

    if (json.isJsonNull() || json.getAsString().isEmpty()) {
      return null;
    }

    String dateAsString = json.getAsJsonPrimitive().getAsString();
    ParseException e = null;

    if (dateAsString.endsWith("Z")) {
      for (SimpleDateFormat format : iso8601Formatters) {
        try {
          return format.parse(dateAsString);
        } catch (ParseException e1) {
          e = e1;
        }
      }
    }

    for (SimpleDateFormat format : dateFormatters) {
      try {
        return format.parse(dateAsString);
      } catch (ParseException e1) {
        e = e1;
      }
    }

    Pattern isJustNumber = Pattern.compile("^\\d+$");
    Matcher foundMatch = isJustNumber.matcher(dateAsString);
    if (foundMatch.find()) {
      Long timeAsLong = Long.parseLong(dateAsString);
      Long msCheck = 100000000000L;

      // are we ms or seconds maybe?
      if (timeAsLong < msCheck) {
        // assuming in seconds
        timeAsLong = timeAsLong * 1000;
      }
      return new Date(timeAsLong);
    }

    LOG.log(Level.SEVERE, "Error parsing: " + dateAsString, e);
    return null;
  }

}

@@ -1511,87 +1511,8 @@
 S%22;%0A
-  private static final String DATE_UTC_WITH_TS = %22yyyy-MM-dd'T'HH:mm:ss.SSSZ%22;%0A
   pr
@@ -1697,19 +1697,18 @@
 g DATE_8
-601
+22
  = %22yyyy
@@ -1728,17 +1728,17 @@
 m:ss.SSS
-X
+Z
 %22;%0A  pri
@@ -1764,27 +1764,26 @@
 tring DATE_8
-601
+22
 _WITHOUT_MS 
@@ -1810,9 +1810,9 @@
 m:ss
-X
+Z
 %22;%0A%0A
@@ -2433,23 +2433,22 @@
 eFormat 
-iso8601
+rfc822
 DateForm
@@ -2482,19 +2482,18 @@
 t(DATE_8
-601
+22
 );%0A  pri
@@ -2520,23 +2520,22 @@
 eFormat 
-iso8601
+rfc822
 WithoutM
@@ -2578,19 +2578,18 @@
 t(DATE_8
-601
+22
 _WITHOUT
@@ -2597,102 +2597,8 @@
 MS);
-%0A  private final SimpleDateFormat utcWithTsFormatter = new SimpleDateFormat(DATE_UTC_WITH_TS);
 %0A%0A  
@@ -2848,23 +2848,22 @@
 Format%3E 
-iso8601
+rfc822
 Formatte
@@ -2891,35 +2891,14 @@
 ist(
-utcWithTsFormatter, iso8601
+rfc822
 Date
@@ -2908,23 +2908,22 @@
 matter, 
-iso8601
+rfc822
 WithoutM
@@ -3593,24 +3593,90 @@
 ith(%22Z%22)) %7B%0A
+      String dateWithTz = dateAsString.replaceAll(%22Z$%22, %22+0000%22);%0A
       for (S
@@ -3704,15 +3704,14 @@
 t : 
-iso8601
+rfc822
 Form
@@ -3764,32 +3764,30 @@
 t.parse(date
-AsString
+WithTz
 );%0A        %7D

Fix date serialization for old Android platforms that do not support iso8601 date format