/********************************************************************************
 * Copyright (c) 2019 Stephane Bastian
 *
 * This program and the accompanying materials are made available under the 2
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 3
 *
 * Contributors: 4
 *   Stephane Bastian - initial API and implementation
 ********************************************************************************/
package io.vertx.ext.auth.authorization.impl;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;

import io.vertx.ext.auth.authorization.AuthorizationContext;

class VariableAwareExpression {
  private String value;
  private transient Function<AuthorizationContext, String>[] parts;
  private transient boolean hasVariable = false;

  @SuppressWarnings("unchecked")
  public VariableAwareExpression(String value) {
    this.value = Objects.requireNonNull(value).trim();

    List<Function<AuthorizationContext, String>> tmpParts = new ArrayList<>();
    int currentPos = 0;
    while (currentPos != -1) {
      int openingCurlyBracePos = value.indexOf("{", currentPos);
      if (openingCurlyBracePos == -1) {
        if (currentPos < value.length()) {
          String authorizationPart = value.substring(currentPos, value.length() - currentPos);
          tmpParts.add(ctx -> authorizationPart);
        }
        break;
      } else {
        if (openingCurlyBracePos > currentPos) {
          String authorizationPart = value.substring(currentPos, openingCurlyBracePos);
          tmpParts.add(ctx -> authorizationPart);
        }
        int closingCurlyBracePos = value.indexOf("}", currentPos + 1);
        if (closingCurlyBracePos == -1) {
          throw new IllegalArgumentException("opening '{' without corresponding closing '}'");
        } else if (closingCurlyBracePos - openingCurlyBracePos == 1) {
          throw new IllegalArgumentException("empty '{}' is not allowed");
        } else {
          String part = value.substring(openingCurlyBracePos, closingCurlyBracePos + 1);
          String variableName = value.substring(openingCurlyBracePos + 1, closingCurlyBracePos);
          hasVariable = true;
          tmpParts.add(ctx -> {
            // substitute parameter
            String result = ctx.variables().get(variableName);
            if (result != null) {
              return result;
            }
            return part;
          });
          currentPos = closingCurlyBracePos + 1;
        }
      }
    }
    this.parts = (Function<AuthorizationContext, String>[]) Array.newInstance(Function.class, tmpParts.size());
    for (int i = 0; i < tmpParts.size(); i++) {
      this.parts[i] = tmpParts.get(i);
    }
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (!(obj instanceof VariableAwareExpression))
      return false;
    VariableAwareExpression other = (VariableAwareExpression) obj;
    return Objects.equals(value, other.value);
  }

  public boolean hasVariable() {
    return hasVariable;
  }

  public String getValue() {
    return value;
  }

  @Override
  public int hashCode() {
    return Objects.hash(value);
  }

  public Function<AuthorizationContext, String>[] parts() {
    return parts;
  }

  public String resolve(AuthorizationContext context) {
    if (parts.length == 1) {
      return parts[0].apply(context);
    } else if (parts.length > 1) {
      StringBuilder result = new StringBuilder();
      for (Function<AuthorizationContext, String> part : parts) {
        result.append(part.apply(context));
      }
      return result.toString();
    }
    // should only happen when the length is 0
    return "";
  }

}
