// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC 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.openqa.selenium;

/**
 * Represents the known architectures used in WebDriver. It attempts to smooth over some of Java's
 * rough edges when dealing with microprocessor architectures by, for instance, allowing you to
 * query if a particular architecture is 32- or 64-bit.
 *
 * @see org.openqa.selenium.Platform
 */
// Useful URLs:
// http://hg.openjdk.java.net/jdk7/modules/jdk/file/a37326fa7f95/src/windows/native/java/lang/java_props_md.c
public enum Architecture {

  // Architecture families

  X86(
      "x86",
      "i386",
      "ia32",
      "i686",
      "i486",
      "i86",
      "pentium",
      "pentium_pro",
      "pentium_pro+mmx",
      "pentium+mmx") {
    @Override
    public int getDataModel() {
      return 32;
    }
  },

  X64("amd64", "ia64", "x86_64"),

  ARM("aarch64", "arm"),

  MIPS32("mips32") {
    @Override
    public int getDataModel() {
      return 32;
    }
  },

  MIPS64("mips64"),

  // Meta architecture

  ANY("") {
    @Override
    public boolean is(Architecture compareWith) {
      return true;
    }
  };

  private final String[] archIdentifiers;

  Architecture(String... partOfArch) {
    archIdentifiers = partOfArch;
  }

  /**
   * Heuristic for comparing two architectures. If architectures are found to be in the same
   * "architecture family" (e.g. i386, i686, x86 and ia32 are considered related), they will match.
   *
   * @param compareWith the architecture to compare with
   * @return true if architectures belong to the same architecture family, false otherwise
   */
  public boolean is(Architecture compareWith) {
    return this.equals(compareWith);
  }

  /**
   * Gets the data model of the architecture. The data model tells you how big memory addresses are
   * on the given microprocessor architecture.
   *
   * @return 32- or 64-bit depending on architecture
   */
  public int getDataModel() {
    return 64;
  }

  @Override
  public String toString() {
    return name().toLowerCase();
  }

  /**
   * Gets current architecture.
   *
   * @return current architecture
   */
  public static Architecture getCurrent() {
    return extractFromSysProperty(System.getProperty("os.arch"));
  }

  /**
   * Extracts architectures based on system properties in Java and a heuristic to overcome
   * differences between JDK implementations. If not able to determine the operating system's
   * architecture, it will throw.
   *
   * @param arch the architecture name to determine the architecture of
   * @return the most likely architecture based on the given architecture name
   * @throws UnsupportedOperationException if the architecture given is unknown or unsupported
   */
  public static Architecture extractFromSysProperty(String arch) {
    if (arch != null) {
      arch = arch.toLowerCase();
    }

    // Some architectures are basically the same even though they have different names.  ia32, x86,
    // i386 and i686 are for WebDriver's purposes the same sort of 32-bit x86-esque architecture.
    // So each architecture defined in this enum has an array of strings with the different
    // identifiers it matches.
    for (Architecture architecture : values()) {
      if (architecture == Architecture.ANY) {
        continue;
      }

      for (String matcher : architecture.archIdentifiers) {
        if (matcher.equals(arch)) {
          return architecture;
        }
      }
    }

    throw new UnsupportedOperationException("Unknown architecture: " + arch);
  }
}
