/*
 * Copyright 2017 Google Inc.
 *
 * 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.cloud.tools.eclipse.googleapis.internal;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.util.Utils;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.services.appengine.v1.Appengine;
import com.google.api.services.appengine.v1.Appengine.Apps;
import com.google.api.services.cloudresourcemanager.CloudResourceManager;
import com.google.api.services.cloudresourcemanager.CloudResourceManager.Projects;
import com.google.api.services.iam.v1.Iam;
import com.google.api.services.servicemanagement.ServiceManagement;
import com.google.api.services.storage.Storage;
import com.google.cloud.tools.eclipse.googleapis.IGoogleApiFactory;
import com.google.cloud.tools.eclipse.util.CloudToolsInfo;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import org.eclipse.core.net.proxy.IProxyChangeEvent;
import org.eclipse.core.net.proxy.IProxyChangeListener;
import org.eclipse.core.net.proxy.IProxyService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;

/**
 * Class to obtain various Google Cloud Platform related APIs.
 */
@Component
public class GoogleApiFactory implements IGoogleApiFactory {

  private IProxyService proxyService;

  private final JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
  private final ProxyFactory proxyFactory;
  private LoadingCache<GoogleApi, HttpTransport> transportCache;

  private final IProxyChangeListener proxyChangeListener = new IProxyChangeListener() {
    @Override
    public void proxyInfoChanged(IProxyChangeEvent event) {
      if (transportCache != null) {
        transportCache.invalidateAll();
      }
    }
  };

  public GoogleApiFactory() {
    this(new ProxyFactory());
  }

  @VisibleForTesting
  public GoogleApiFactory(ProxyFactory proxyFactory) {
    Preconditions.checkNotNull(proxyFactory, "proxyFactory is null");
    this.proxyFactory = proxyFactory;
  }

  @Activate
  public void init() {
    // NetHttpTransport advises: "For maximum efficiency, applications should use a single
    // globally-shared instance of the HTTP transport." But as we need a separate proxy per URL,
    // we cannot reuse the same httptransport.
    transportCache =
        CacheBuilder.newBuilder().weakValues().build(new TransportCacheLoader(proxyFactory));
  }

  @Override
  public Projects newProjectsApi(Credential credential) {
    Preconditions.checkNotNull(transportCache, "transportCache is null");
    HttpTransport transport = transportCache.getUnchecked(GoogleApi.CLOUDRESOURCE_MANAGER_API);
    Preconditions.checkNotNull(transport, "transport is null");
    Preconditions.checkNotNull(jsonFactory, "jsonFactory is null");

    CloudResourceManager resourceManager =
        new CloudResourceManager.Builder(transport, jsonFactory, credential)
            .setApplicationName(CloudToolsInfo.USER_AGENT).build();
    return resourceManager.projects();
  }

  @Override
  public Storage newStorageApi(Credential credential) {
    Preconditions.checkNotNull(transportCache, "transportCache is null");
    HttpTransport transport = transportCache.getUnchecked(GoogleApi.CLOUD_STORAGE_API);
    Preconditions.checkNotNull(transport, "transport is null");
    Preconditions.checkNotNull(jsonFactory, "jsonFactory is null");

    Storage.Builder builder = new Storage.Builder(transport, jsonFactory, credential)
        .setApplicationName(CloudToolsInfo.USER_AGENT);
    Storage storage = builder.build();
    return storage;
  }

  @Override
  public Apps newAppsApi(Credential credential) {
    Preconditions.checkNotNull(transportCache, "transportCache is null");
    HttpTransport transport = transportCache.getUnchecked(GoogleApi.APPENGINE_ADMIN_API);
    Preconditions.checkNotNull(transport, "transport is null");
    Preconditions.checkNotNull(jsonFactory, "jsonFactory is null");

    Appengine appengine =
        new Appengine.Builder(transport, jsonFactory, credential)
            .setApplicationName(CloudToolsInfo.USER_AGENT).build();
    return appengine.apps();
  }

  @Override
  public ServiceManagement newServiceManagementApi(Credential credential) {
    Preconditions.checkNotNull(transportCache, "transportCache is null");
    HttpTransport transport = transportCache.getUnchecked(GoogleApi.SERVICE_MANAGEMENT_API);
    Preconditions.checkNotNull(transport, "transport is null");
    Preconditions.checkNotNull(jsonFactory, "jsonFactory is null");

    ServiceManagement serviceManagement =
        new ServiceManagement.Builder(transport, jsonFactory, credential)
            .setApplicationName(CloudToolsInfo.USER_AGENT).build();
    return serviceManagement;
  }

  @Override
  public Iam newIamApi(Credential credential) {
    Preconditions.checkNotNull(transportCache, "transportCache is null");
    HttpTransport transport = transportCache.getUnchecked(GoogleApi.IAM_API);
    Preconditions.checkNotNull(transport, "transport is null");
    Preconditions.checkNotNull(jsonFactory, "jsonFactory is null");

    Iam iam = new Iam.Builder(transport, jsonFactory, credential)
        .setApplicationName(CloudToolsInfo.USER_AGENT).build();
    return iam;
  }

  @Reference(policy=ReferencePolicy.DYNAMIC, cardinality=ReferenceCardinality.OPTIONAL)
  public void setProxyService(IProxyService proxyService) {
    this.proxyService = proxyService;
    this.proxyService.addProxyChangeListener(proxyChangeListener);
    proxyFactory.setProxyService(this.proxyService);
    if (transportCache != null) {
      transportCache.invalidateAll();
    }
  }

  public void unsetProxyService(IProxyService proxyService) {
    if (this.proxyService == proxyService) {
      proxyService.removeProxyChangeListener(proxyChangeListener);
      this.proxyService = null;
      proxyFactory.setProxyService(null);
      if (transportCache != null) {
        transportCache.invalidateAll();
      }
    }
  }

  @VisibleForTesting
  void setTransportCache(LoadingCache<GoogleApi, HttpTransport> transportCache) {
    this.transportCache = transportCache;
  }
}