/*
 * Copyright 2017 Adaptris Ltd.
 * 
 * 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.adaptris.core.services.metadata;

import static org.apache.commons.lang.StringUtils.isBlank;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import org.hibernate.validator.constraints.NotBlank;

import com.adaptris.annotation.AdvancedConfig;
import com.adaptris.annotation.AutoPopulated;
import com.adaptris.annotation.DisplayOrder;
import com.adaptris.annotation.InputFieldDefault;
import com.adaptris.annotation.InputFieldHint;
import com.adaptris.core.AdaptrisMessage;
import com.adaptris.core.util.Args;
import com.adaptris.util.text.DateFormatUtil.CustomDateFormat;
import com.thoughtworks.xstream.annotations.XStreamAlias;

/**
 * Builds a DateFormat instance for use with {@link ReformatDateService} and {@link AddTimestampMetadataService}.
 * 
 * @config date-format-builder
 */
@XStreamAlias("date-format-builder")
@DisplayOrder(order = {"format", "locale", "timezone"})
public class DateFormatBuilder {

  public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";

  @InputFieldHint(expression = true)
  @NotBlank
  @AutoPopulated
  @InputFieldDefault(value = DEFAULT_DATE_FORMAT)
  private String format;
  @AdvancedConfig
  @InputFieldHint(expression = true, style = "java.util.Locale")
  private String languageTag;
  @InputFieldHint(expression = true, style = "java.util.TimeZone#getAvailableIDs")
  @AdvancedConfig
  private String timezone;

  private enum CustomFormatParser {
    SECONDS_SINCE_EPOCH() {

      @Override
      DateFormatter create() {
        return new SecondsSinceEpoch();
      }
    },
    MILLISECONDS_SINCE_EPOCH() {
      @Override
      DateFormatter create() {
        return new MillisecondsSinceEpoch();
      }
    };

    abstract DateFormatter create();

  }

  public DateFormatBuilder() {
    setFormat(DEFAULT_DATE_FORMAT);
  }

  public DateFormatBuilder(String format) {
    this();
    setFormat(format);
  }

  public DateFormatter build(AdaptrisMessage msg) {
    try {
      return CustomFormatParser.valueOf(CustomDateFormat.valueOf(msg.resolve(getFormat())).name()).create();
    }
    catch (IllegalArgumentException e) {
      return withTimeZone(createWithLocale(msg), msg.resolve(getTimezone()));
    }
  }

  private SimpleDateFormat createWithLocale(AdaptrisMessage msg) {
    String language = msg.resolve(getLanguageTag());
    String format = msg.resolve(getFormat());
    return (!isBlank(language)) ? new SimpleDateFormat(format, Locale.forLanguageTag(language)) : new SimpleDateFormat(format);
  }

  private DateFormatter withTimeZone(SimpleDateFormat format, String id) {
    format.setTimeZone((!isBlank(id)) ? TimeZone.getTimeZone(id) : TimeZone.getDefault());
    return new SimpleDateFormatter(format);
  }

  public String getFormat() {
    return format;
  }

  /**
   * Set the format.
   * 
   * @param format the dateformat, default is {@value #DEFAULT_DATE_FORMAT} if not specified.
   */
  public void setFormat(String format) {
    this.format = Args.notBlank(format, "format");
  }

  public DateFormatBuilder withFormat(String f) {
    setFormat(f);
    return this;
  }

  public String getLanguageTag() {
    return languageTag;
  }

  /**
   * Set the language tag for the {@link java.util.Locale} which is resolved via {@link Locale#forLanguageTag(String)}.
   * 
   * @param locale the locale e.g. {@code fr_FR} or {@code en_GB}.
   * @see Locale#forLanguageTag(String)
   */
  public void setLanguageTag(String locale) {
    this.languageTag = locale;
  }

  public DateFormatBuilder withLanguageTag(String tag) {
    setLanguageTag(tag);
    return this;
  }

  public String getTimezone() {
    return timezone;
  }

  /**
   * Set the timezone
   * 
   * @param tz the timezone e.g. {@code UTC} or {@code GMT}.
   * @see java.util.TimeZone#getTimeZone(String)
   */
  public void setTimezone(String tz) {
    this.timezone = tz;
  }

  public DateFormatBuilder withTimezone(String tz) {
    setTimezone(tz);
    return this;
  }

  public interface DateFormatter {

    Date toDate(String dateString) throws ParseException;

    String toString(Date d);
  }

  private static class SimpleDateFormatter implements DateFormatter {

    private SimpleDateFormat formatter;

    private SimpleDateFormatter(SimpleDateFormat f) {
      formatter = f;
    }
    @Override
    public Date toDate(String dateString) throws ParseException {
      return formatter.parse(dateString);
    }

    @Override
    public String toString(Date d) {
      return formatter.format(d);
    }

  }

  private static class SecondsSinceEpoch implements DateFormatter {

    private SecondsSinceEpoch() {}

    public Date toDate(String stringRep) {
      return new Date(new BigDecimal(stringRep).longValue() * 1000);
    }

    public String toString(Date date) {
      return String.valueOf(new BigDecimal(date.getTime()).divide(new BigDecimal(1000), RoundingMode.HALF_UP).longValue());
    }

  }

  private static class MillisecondsSinceEpoch implements DateFormatter {

    private MillisecondsSinceEpoch() {
    }

    public Date toDate(String stringRep) {
      return new Date(new BigDecimal(stringRep).longValue());
    }

    public String toString(Date date) {
      return String.valueOf(date.getTime());
    }

  }
}

@@ -4064,16 +4064,58 @@
  locale 
+using the IETF BCP 47 language tag string 
 e.g. %7B@c
@@ -4120,17 +4120,17 @@
 @code fr
-_
+-
 FR%7D or %7B
@@ -4137,17 +4137,17 @@
 @code en
-_
+-
 GB%7D.%0A   

Update DateFormatBuilder language tag javadoc.