// Copyright 2018 The Bazel Authors. All rights reserved.
//
// 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 com.google.devtools.build.lib.skyframe;

import com.google.common.collect.Interner;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.skyframe.AbstractSkyKey;
import com.google.devtools.build.skyframe.SkyFunctionName;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;

/**
 * Encapsulates the errors, if any, encountered when loading a specific package.
 *
 * <p>This is a change-pruning-friendly convenience {@link SkyValue} for use cases where {@link
 * Result} is sufficient.
 */
public abstract class PackageErrorMessageValue implements SkyValue {
  /** Tri-state result of loading the package. */
  enum Result {
    /**
     * There was no error loading the package and {@link
     * com.google.devtools.build.lib.packages.Package#containsErrors} returned {@code false}.
     */
    NO_ERROR,

    /**
     * There was no error loading the package and {@link
     * com.google.devtools.build.lib.packages.Package#containsErrors} returned {@code true}.
     */
    ERROR,

    /**
     * There was a {@link com.google.devtools.build.lib.packages.NoSuchPackageException} loading the
     * package.
     */
    NO_SUCH_PACKAGE_EXCEPTION,
  }

  /** Returns the {@link Result} from loading the package. */
  abstract Result getResult();

  /**
   * If {@code getResult().equals(NO_SUCH_PACKAGE_EXCEPTION)}, returns the error message from the
   * {@link com.google.devtools.build.lib.packages.NoSuchPackageException} encountered.
   */
  abstract String getNoSuchPackageExceptionMessage();

  static PackageErrorMessageValue ofPackageWithNoErrors() {
    return NO_ERROR_VALUE;
  }

  static PackageErrorMessageValue ofPackageWithErrors() {
    return ERROR_VALUE;
  }

  static PackageErrorMessageValue ofNoSuchPackageException(String errorMessage) {
    return new NoSuchPackageExceptionValue(errorMessage);
  }

  public static SkyKey key(PackageIdentifier pkgId) {
    return Key.create(pkgId);
  }

  @AutoCodec.VisibleForSerialization
  @AutoCodec
  static class Key extends AbstractSkyKey<PackageIdentifier> {
    private static final Interner<Key> interner = BlazeInterners.newWeakInterner();

    private Key(PackageIdentifier arg) {
      super(arg);
    }

    @AutoCodec.VisibleForSerialization
    @AutoCodec.Instantiator
    static Key create(PackageIdentifier arg) {
      return interner.intern(new Key(arg));
    }

    @Override
    public SkyFunctionName functionName() {
      return SkyFunctions.PACKAGE_ERROR_MESSAGE;
    }
  }

  @AutoCodec
  static final PackageErrorMessageValue NO_ERROR_VALUE =
      new PackageErrorMessageValue() {
        @Override
        Result getResult() {
          return Result.NO_ERROR;
        }

        @Override
        String getNoSuchPackageExceptionMessage() {
          throw new IllegalStateException();
        }
      };

  @AutoCodec
  static final PackageErrorMessageValue ERROR_VALUE =
      new PackageErrorMessageValue() {
        @Override
        Result getResult() {
          return Result.ERROR;
        }

        @Override
        String getNoSuchPackageExceptionMessage() {
          throw new IllegalStateException();
        }
      };

  @AutoCodec
  static class NoSuchPackageExceptionValue extends PackageErrorMessageValue {
    private final String errorMessage;

    public NoSuchPackageExceptionValue(String errorMessage) {
      this.errorMessage = errorMessage;
    }

    @Override
    Result getResult() {
      return Result.NO_SUCH_PACKAGE_EXCEPTION;
    }

    @Override
    String getNoSuchPackageExceptionMessage() {
      return errorMessage;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof NoSuchPackageExceptionValue)) {
        return false;
      }
      NoSuchPackageExceptionValue other = (NoSuchPackageExceptionValue) obj;
      return errorMessage.equals(other.errorMessage);
    }

    @Override
    public int hashCode() {
      return errorMessage.hashCode();
    }
  }
}
