// 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.devtools.build.lib.rules.cpp.CcLinkParams.Builder;
import com.google.devtools.build.lib.util.Preconditions;

/**
 * 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 CcLinkParamsProvider {
 *   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);

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

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

  /**
   * An implementation class for the CcLinkParamsStore.
   */
  public static final class CcLinkParamsStoreImpl extends CcLinkParamsStore {

    public CcLinkParamsStoreImpl(CcLinkParamsStore store) {
      super.staticSharedParams = store.get(true, true);
      super.staticNoSharedParams = store.get(true, false);
      super.noStaticSharedParams = store.get(false, true);
      super.noStaticNoSharedParams = store.get(false, false);
    }

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

