/**
 * Copyright 2010 Google Inc.
 *
 * 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 org.waveprotocol.wave.client.common.util;

import com.google.common.annotations.VisibleForTesting;
import org.waveprotocol.wave.model.util.Box;

import com.google.gwt.core.client.GWT;

/**
 * Class to contain run-time checks of a user-agent's capabilities.
 *
 *
 */
@VisibleForTesting
public class UserAgentRuntimeProperties {
  private static final UserAgentRuntimeProperties INSTANCE = createInstance();

  private static UserAgentRuntimeProperties createInstance() {
    return GWT.isScript() ? new UserAgentRuntimeProperties(getNativeUserAgent())
                          : new UserAgentRuntimeProperties("");
  }

  @VisibleForTesting
  public static UserAgentRuntimeProperties get() {
    return INSTANCE;
  }

  private final String userAgent;
  private final int version;
  private final boolean isMac;
  private final boolean isWin;
  private final boolean isLinux;
  private final boolean isIe7;
  private final boolean isIe8;
  private final boolean isChrome;

  @VisibleForTesting
  public UserAgentRuntimeProperties(String userAgent) {
    this.userAgent = userAgent;
    this.version = calculateVersion(userAgent);
    this.isMac = calculateIsMac(userAgent);
    this.isWin = calculateIsWin(userAgent);
    this.isLinux = calculateIsLinux(userAgent);
    this.isIe7 = calculateIe7(userAgent);
    this.isIe8 = calculateIe8(userAgent);
    this.isChrome = calculateIsChrome(userAgent);
  }

  @VisibleForTesting
  public String getUserAgent() {
    return userAgent;
  }

  @VisibleForTesting
  public boolean isMac() {
    return isMac;
  }

  @VisibleForTesting
  public boolean isWin() {
    return isWin;
  }

  @VisibleForTesting
  public boolean isLinux() {
    return isLinux;
  }

  @VisibleForTesting
  public boolean isIe7() {
    return isIe7;
  }

  @VisibleForTesting
  public boolean isIe8(){
    return isIe8;
  }

  @VisibleForTesting
  public boolean isChrome() {
    return isChrome;
  }

  /**
   * @return whether the current user agent version is at least the one given by
   *         the method parameters.
   */
  @VisibleForTesting
  public boolean isAtLeastVersion(int major, int minor) {
    return version >= (major * 1000 + minor);
  }

  /**
   * Do not use this for program logic - for debugging only. For program logic,
   * instead use {@link #isAtLeastVersion(int, int)}
   */
  @VisibleForTesting
  public int getMajorVer() {
    return version / 1000;
  }

  /**
   * Do not use this for program logic - for debugging only. For program logic,
   * instead use {@link #isAtLeastVersion(int, int)}
   */
  @VisibleForTesting
  public int getMinorVer() {
    return version % 1000;
  }

  private static native String getNativeUserAgent() /*-{
    return navigator.userAgent;
  }-*/;

  private static boolean calculateIe7(String userAgent) {
    return userAgent.indexOf(" MSIE 7.") != -1;
  }

  private static boolean calculateIe8(String userAgent) {
    return userAgent.indexOf(" MSIE 8.") != -1;
  }

  private static boolean calculateIsMac(String userAgent) {
    return userAgent.indexOf("Mac") != -1;
  }

  private static boolean calculateIsWin(String userAgent) {
    return userAgent.indexOf("Windows") != -1;
  }

  private static boolean calculateIsLinux(String userAgent) {
    return userAgent.indexOf("Linux") != -1;
  }

  private static boolean calculateIsChrome(String userAgent) {
    return userAgent.indexOf("Chrome") != -1;
  }

  private static int calculateVersion(String userAgent) {
    if (userAgent == null || userAgent.isEmpty()) {
      return -1;
    }

//  TODO(user): Make this work after regex deps are fixed and don't break static rendering
//
//    String regexps[] = {"firefox.([0-9]+).([0-9]+)",
//                        "webkit.([0-9]+).([0-9]+)",
//                        "msie.([0-9]+).([0-9]+)",
//                        "minefield.([0-9]+).([0-9]+)"};


//  TODO(user): Don't use "firefox" and "minefield", check Gecko rv.
    String names[] = {"firefox", "webkit", "msie", "minefield"};

    for (String name : names) {
      int v = calculateVersion(name, userAgent);
      if (v >= 0) {
        return v;
      }
    }
    return -1;
  }

//  /**
//   * Matches a browser-specific regular expression against the user agent to
//   * obtain a version number.
//   *
//   * @param regexp The browser-specific regular expression to use
//   * @param userAgent The user agent string to check
//   * @return A version number or -1 if unknown
//   */
  /**
   * Matches a browser-specific name against the user agent to obtain a version
   * number.
   *
   * @param name The browser-specific name to use
   * @param userAgent The user agent string to check
   * @return A version number or -1 if unknown
   */
  private static int calculateVersion(String name, String userAgent) {
    int index = userAgent.toLowerCase().indexOf(name);
    if (index == -1) {
      return -1;
    }

    Box<Integer> output = Box.create();

    index += name.length() + 1;

    if ((index = consumeDigit(index, userAgent, output)) == -1) {
      return -1;
    }
    int major = output.boxed;

    index++;

    if ((index = consumeDigit(index, userAgent, output)) == -1) {
      return -1;
    }
    int minor = output.boxed;

    return major * 1000 + minor;

//  TODO(user): Make this work after regex deps are fixed and don't break static rendering
//
//    RegExp pattern = RegExp.compile(regexp);
//    MatchResult result = pattern.exec(userAgent.toLowerCase());
//    if (result != null && result.getGroupCount() == 3) {
//      int major = Integer.parseInt(result.getGroup(1));
//      int minor = Integer.parseInt(result.getGroup(2));
//      return major * 1000 + minor;
//    }
//    return -1;
  }

  private static int consumeDigit(int index, String str, Box<Integer> output) {
    StringBuilder nb = new StringBuilder();

    char c;
    while (index < str.length() && Character.isDigit( (c = str.charAt(index)) )) {
      nb.append(c);
      index++;
    }

    if (nb.length() == 0) {
      return -1;
    }

    try {
      output.boxed = Integer.parseInt(nb.toString());
      return index;
    } catch (NumberFormatException e) {
      return -1;
    }
  }

}
