/*******************************************************************************
 * 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.olingo.odata2.core.exception;

import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingFormatArgumentException;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.apache.olingo.odata2.api.exception.MessageReference;

/**
 *  
 */
public class MessageService {
  private static final String BUNDLE_NAME = "i18n";

  private final ResourceBundle resourceBundle;
  private final Locale requestedLocale;

  private static final Map<Locale, MessageService> LOCALE_2_MESSAGE_SERVICE = new HashMap<Locale, MessageService>();

  private MessageService(final Locale locale) {
    requestedLocale = locale;
    resourceBundle = createResourceBundle(locale);
  }

  /**
   * Create a {@link ResourceBundle} based on given locale and name ({@value #BUNDLE_NAME}).
   * If during creation an exception occurs it is catched and an special bundle is created with error type and message
   * of
   * this exception.
   * 
   * @param locale for which locale the {@link ResourceBundle} is created
   * @return a {@link ResourceBundle}
   */
  private ResourceBundle createResourceBundle(final Locale locale) {
    ResourceBundle bundle;
    try {
      if (locale == null) {
        throw new IllegalArgumentException("Parameter locale MUST NOT be NULL.");
      }
      bundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);
    } catch (final Exception e) {
      bundle = new ResourceBundle() {
        @Override
        protected Object handleGetObject(final String key) {
          return "MessageService could not be created because of exception '" +
              e.getClass().getSimpleName() + " with message '" + e.getMessage() + "'.";
        }

        @Override
        public Locale getLocale() {
          return Locale.ENGLISH;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Enumeration<String> getKeys() {
          return (Enumeration<String>) Collections.emptySet();
        }
      };
    }
    return bundle;
  }

  public static class Message {
    private final String localizedText;
    private final Locale locale;

    public Message(final Locale locale, final String localizedMessage) {
      super();
      localizedText = localizedMessage;
      this.locale = locale;
    }

    public String getText() {
      return localizedText;
    }

    public Locale getLocale() {
      return locale;
    }

    /**
     * Get language as defined in RFC 4646 based on {@link Locale}.
     * 
     * @return the language label
     */
    public String getLang() {
      Locale locale = getLocale();
      if (locale.getCountry().isEmpty()) {
        return locale.getLanguage();
      }
      return locale.getLanguage() + "-" + locale.getCountry();
    }
  }

  public static MessageService getInstance(final Locale locale) {
    MessageService messagesInstance = LOCALE_2_MESSAGE_SERVICE.get(locale);
    if (messagesInstance == null) {
      messagesInstance = new MessageService(locale);
      LOCALE_2_MESSAGE_SERVICE.put(locale, messagesInstance);
    }
    return messagesInstance;
  }

  public static boolean isLocaleSupported(final Locale locale) {
    if (locale == null) {
      return false;
    }
    MessageService service = getInstance(locale);
    return service.getLocale().equals(locale);
  }

  /**
   * Return first found supported {@link Locale} (iterating over list starting with first element).
   * If no {@link Locale} is supported <code>NULL</code> is returned.
   * 
   * @param locales
   * @return first supported {@link Locale} or <code>NULL</code>.
   */
  public static Locale getSupportedLocale(final List<Locale> locales) {
    return getSupportedLocale(locales, null);
  }

  /**
   * Return first found supported {@link Locale} (iterating over list starting with first element).
   * If no {@link Locale} is supported given <code>defaultLocale</code> is returned.
   * 
   * @param locales to be checked locales
   * @param defaultLocale local which is returned if no supported local is in given <code>locales</code> list
   * @return first supported {@link Locale} or given <code>defaultLocale</code>.
   */
  public static Locale getSupportedLocale(final List<Locale> locales, final Locale defaultLocale) {
    for (Locale locale : locales) {
      if (isLocaleSupported(locale)) {
        return locale;
      }
    }
    return defaultLocale;
  }

  public static MessageService getInstance(final List<Locale> locales) {
    MessageService service = null;

    for (Locale locale : locales) {
      service = getInstance(locale);
      if (service.getLocale().equals(locale)) {
        break;
      }
    }

    return service;
  }

  public static Message getMessage(final Locale language, final MessageReference context) {
    Object[] contentAsArray = context.getContent().toArray(new Object[0]);
    return getMessage(language, context.getKey(), contentAsArray);
  }

  public static Message getMessage(final Locale locale, final String key, final Object... replacements) {
    MessageService messages = MessageService.getInstance(locale);
    return messages.getMessage(key, replacements);
  }

  /**
   * 
   * @param key
   * @param replacements
   * @return the message
   */
  private Message getMessage(final String key, final Object... replacements) {
    String message = null;

    try {
      message = resourceBundle.getString(key);
      StringBuilder builder = new StringBuilder();
      Formatter f = new Formatter(builder, requestedLocale);
      f.format(message, replacements);
      f.close();

      return new Message(getLocale(), builder.toString());

    } catch (MissingResourceException e) {
      return new Message(Locale.ENGLISH, "Missing message for key '" + key + "'!");
    } catch (MissingFormatArgumentException e) {
      return new Message(Locale.ENGLISH, "Missing replacement for place holder in message '" + message +
          "' for following arguments '" + Arrays.toString(replacements) + "'!");
    }
  }

  public Locale getLocale() {
    return resourceBundle.getLocale();
  }

  public Enumeration<String> getKeys() {
    return resourceBundle.getKeys();
  }
}
