// Copyright 2014 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.rules.cpp;

import com.google.common.base.Preconditions;
import com.google.devtools.build.lib.rules.cpp.CcLinkParams.Builder;
import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;

/**
 * A cache of C link parameters.
 *
 * <p>The cache holds instances of {@link CcLinkParams} for combinations of linkingStatically and
 * linkShared. If a requested value is not available in the cache, it is computed and then stored.
 *
 * <p>Typically this class is used on targets that may be linked in as C libraries as in the
 * following example:
 *
 * <pre>
 * class SomeTarget implements CcLinkParamsInfo {
 *   private final CcLinkParamsStore ccLinkParamsStore = new CcLinkParamsStore() {
 *     @Override
 *     protected void collect(CcLinkParams.Builder builder, boolean linkingStatically,
 *                            boolean linkShared) {
 *       builder.add[...]
 *     }
 *   };
 *
 *   @Override
 *   public CcLinkParams getCcLinkParams(boolean linkingStatically, boolean linkShared) {
 *     return ccLinkParamsStore.get(linkingStatically, linkShared);
 *   }
 * }
 * </pre>
 */
public abstract class CcLinkParamsStore {
  private CcLinkParams staticSharedParams;
  private CcLinkParams staticNoSharedParams;
  private CcLinkParams noStaticSharedParams;
  private CcLinkParams noStaticNoSharedParams;

  private CcLinkParams compute(boolean linkingStatically, boolean linkShared) {
    CcLinkParams.Builder builder = CcLinkParams.builder(linkingStatically, linkShared);
    collect(builder, linkingStatically, linkShared);
    return builder.build();
  }

  /**
   * Returns {@link CcLinkParams} for a combination of parameters.
   *
   * <p>The {@link CcLinkParams} instance is computed lazily and cached.
   */
  public synchronized CcLinkParams get(boolean linkingStatically, boolean linkShared) {
    CcLinkParams result = lookup(linkingStatically, linkShared);
    if (result == null) {
      result = compute(linkingStatically, linkShared);
      put(linkingStatically, linkShared, result);
    }
    return result;
  }

  private CcLinkParams lookup(boolean linkingStatically, boolean linkShared) {
    if (linkingStatically) {
      return linkShared ? staticSharedParams : staticNoSharedParams;
    } else {
      return linkShared ? noStaticSharedParams : noStaticNoSharedParams;
    }
  }

  private void put(boolean linkingStatically, boolean linkShared, CcLinkParams params) {
    Preconditions.checkNotNull(params);
    if (linkingStatically) {
      if (linkShared) {
        staticSharedParams = params;
      } else {
        staticNoSharedParams = params;
      }
    } else {
      if (linkShared) {
        noStaticSharedParams = params;
      } else {
        noStaticNoSharedParams = params;
      }
    }
  }

  /**
   * Hook for building the actual link params.
   *
   * <p>Users should override this method and call methods of the builder to
   * set up the actual CcLinkParams objects.
   *
   * <p>Implementations of this method must not fail or try to report errors on the
   * configured target.
   */
  protected abstract void collect(CcLinkParams.Builder builder, boolean linkingStatically,
                                  boolean linkShared);

  @AutoCodec
  @VisibleForSerialization
  static class EmptyCcLinkParamsStore extends CcLinkParamsStore {
    public static final ObjectCodec<EmptyCcLinkParamsStore> CODEC =
        new CcLinkParamsStore_EmptyCcLinkParamsStore_AutoCodec();

    @Override
    protected void collect(Builder builder, boolean linkingStatically, boolean linkShared) {}
  }

  /** An empty CcLinkParamStore. */
  public static final CcLinkParamsStore EMPTY = new EmptyCcLinkParamsStore();

  /** An implementation class for the CcLinkParamsStore. */
  @AutoCodec
  public static final class CcLinkParamsStoreImpl extends CcLinkParamsStore {
    public static final ObjectCodec<CcLinkParamsStoreImpl> CODEC =
        new CcLinkParamsStore_CcLinkParamsStoreImpl_AutoCodec();

    public CcLinkParamsStoreImpl(CcLinkParamsStore store) {
      this(
          store.get(true, true),
          store.get(true, false),
          store.get(false, true),
          store.get(false, false));
    }

    @VisibleForSerialization
    @AutoCodec.Instantiator
    CcLinkParamsStoreImpl(
        CcLinkParams staticSharedParams,
        CcLinkParams staticNoSharedParams,
        CcLinkParams noStaticSharedParams,
        CcLinkParams noStaticNoSharedParams) {
      super.staticSharedParams = staticSharedParams;
      super.staticNoSharedParams = staticNoSharedParams;
      super.noStaticSharedParams = noStaticSharedParams;
      super.noStaticNoSharedParams = noStaticNoSharedParams;
    }

    @Override
    protected void collect(Builder builder, boolean linkingStatically, boolean linkShared) {}
  }
}

