/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.security.authorization;

import alluxio.exception.ExceptionMessage;
import alluxio.security.authorization.Mode.Bits;

import org.apache.commons.lang3.StringUtils;

import java.text.MessageFormat;
import javax.annotation.concurrent.ThreadSafe;

/**
 * Parser for {@code Mode} instances.
 * @author rvesse
 *
 */
@ThreadSafe
public final class ModeParser {

  private static final char[] VALID_TARGETS = { 'u', 'g', 'o', 'a' };
  private static final char[] VALID_PERMISSIONS = { 'r', 'w', 'x' };

  private ModeParser() {} // to prevent initialization

  /**
   * Parses the given value as a mode.
   * @param value Value
   * @return Mode
   */
  public static Mode parse(String value) {
    if (StringUtils.isBlank(value)) {
      throw new IllegalArgumentException(ExceptionMessage.INVALID_MODE.getMessage(value));
    }

    try {
      return parseNumeric(value);
    } catch (NumberFormatException e) {
      // Treat as symbolic
      return parseSymbolic(value);
    }
  }

  private static Mode parseNumeric(String value) {
    short s = Short.parseShort(value, 8);
    return new Mode(s);
  }

  private static Mode parseSymbolic(String value) {
    Mode.Bits ownerBits = Bits.NONE;
    Mode.Bits groupBits = Bits.NONE;
    Mode.Bits otherBits = Bits.NONE;

    String[] specs = value.contains(",") ? value.split(",") : new String[] { value };
    for (String spec : specs) {
      String[] specParts = spec.split("=");

      // Validate that the spec is usable
      // Must have targets=perm i.e. 2 parts
      // Targets must be in u, g, o and a
      // Permissions must be in r, w and x
      if (specParts.length != 2) {
        throw new IllegalArgumentException(ExceptionMessage.INVALID_MODE_SEGMENT
            .getMessage(value, spec));
      }
      if (!StringUtils.containsOnly(specParts[0], VALID_TARGETS)) {
        throw new IllegalArgumentException(MessageFormat.format(
            "Invalid mode {0} - contains invalid segment {1} which has invalid targets {2}", value,
            spec, specParts[0]));
      }
      if (!StringUtils.containsOnly(specParts[1], VALID_PERMISSIONS)) {
        throw new IllegalArgumentException(MessageFormat.format(
            "Invalid mode {0} - contains invalid segment {1} which has invalid permissions {2}",
            value, spec, specParts[1]));
      }

      // Build the permissions being specified
      Mode.Bits specBits = Bits.NONE;
      for (char permChar : specParts[1].toCharArray()) {
        switch (permChar) {
          case 'r':
            specBits = specBits.or(Bits.READ);
            break;
          case 'w':
            specBits = specBits.or(Bits.WRITE);
            break;
          case 'x':
            specBits = specBits.or(Bits.EXECUTE);
            break;
          default:
            // Should never get here as already checked for invalid targets
            throw new IllegalStateException("Unknown permission character: " + permChar);
        }
      }

      // Apply them to the targets
      for (char targetChar : specParts[0].toCharArray()) {
        switch (targetChar) {
          case 'u':
            ownerBits = ownerBits.or(specBits);
            break;
          case 'g':
            groupBits = groupBits.or(specBits);
            break;
          case 'o':
            otherBits = otherBits.or(specBits);
            break;
          case 'a':
            ownerBits = ownerBits.or(specBits);
            groupBits = groupBits.or(specBits);
            otherBits = otherBits.or(specBits);
            break;
          default:
            // Should never get here as already checked for invalid targets
            throw new IllegalStateException("Unknown target character: " + targetChar);
        }
      }
    }

    // Return the resulting mode
    return new Mode(ownerBits, groupBits, otherBits);
  }
}
