/*
 * Copyright 2018 Google LLC
 *
 * 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
 *
 *     https://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.photos.library.sample.demos;

import static com.google.photos.library.sample.Resources.TITLE;
import static com.google.photos.library.sample.demos.FilterDemo.getOnApplicationClosedFn;
import static com.google.photos.library.sample.demos.FilterDemo.getOnBackClickedFn;
import static com.google.photos.library.sample.demos.FilterDemo.getOnItemClicked;
import static com.google.photos.library.sample.helpers.UIHelper.getFormattedText;

import com.google.api.core.ApiFuture;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.photos.library.sample.components.AppPanel;
import com.google.photos.library.sample.components.CreateAlbumToolPanel;
import com.google.photos.library.sample.components.UploadToAlbumToolPanel;
import com.google.photos.library.sample.factories.PhotosLibraryClientFactory;
import com.google.photos.library.sample.helpers.UIHelper;
import com.google.photos.library.sample.suppliers.ListAlbumsSupplier;
import com.google.photos.library.sample.suppliers.SearchMediaItemSupplier;
import com.google.photos.library.sample.views.AbstractCustomView;
import com.google.photos.library.sample.views.AlbumListView;
import com.google.photos.library.sample.views.ConnectToPhotosView;
import com.google.photos.library.sample.views.LoadingView;
import com.google.photos.library.sample.views.PhotoListView;
import com.google.photos.library.v1.PhotosLibraryClient;
import com.google.photos.library.v1.proto.BatchCreateMediaItemsRequest;
import com.google.photos.library.v1.proto.ListAlbumsRequest;
import com.google.photos.library.v1.proto.SearchMediaItemsRequest;
import com.google.photos.library.v1.upload.UploadMediaItemRequest;
import com.google.photos.library.v1.upload.UploadMediaItemResponse;
import com.google.photos.types.proto.Album;
import java.awt.FontFormatException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URLConnection;
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.swing.JOptionPane;

/**
 * Google Photos Library API Sample.
 *
 * <p>Sample app for album use cases: read, add and create.
 *
 * <p>This sample uses the following api methods:
 *
 * <ul>
 *   <li>batchCreateMediaItems: create media items in an album
 *   <li>createAlbum: create a new album
 *   <li>listAlbums: list albums in a library
 *   <li>searchMediaItems: list photos in an album
 *   <li>uploadMediaItem: upload a photo
 * </ul>
 */
public final class AlbumDemo {
  public static final String ALBUM_TITLE = "Photos album sample app";
  public static final String ALBUM_SAMPLE_IMAGE_RESOURCE = "/assets/album.png";
  public static final String ALBUM_INTRODUCTION =
      "<html>"
          + getFormattedText(
              "Google Photos Library API Sample", 14 /* fontSize */, 2 /* lineMargin */)
          + getFormattedText("Albums: read, add + create", 22 /* fontSize */, 10 /* lineMargin */)
          + getFormattedText("This sample will cover", 12 /* fontSize */, 2 /* lineMargin */)
          + getFormattedText(
              " - Connecting to a Google Photos library", 12 /* fontSize */, 2 /* lineMargin */)
          + getFormattedText(
              " - Requesting scopes from the Google Photos user",
              12 /* fontSize */,
              2 /* lineMargin */)
          + getFormattedText(" - Reading the album list", 12 /* fontSize */, 2 /* lineMargin */)
          + getFormattedText(" - Creating an album", 12 /* fontSize */, 2 /* lineMargin */)
          + getFormattedText(
              " - Adding media items to an album", 12 /* fontSize */, 2 /* lineMargin */)
          + "</html>";

  private static final List<String> REQUIRED_SCOPES =
      ImmutableList.of(
          "https://www.googleapis.com/auth/photoslibrary.readonly",
          "https://www.googleapis.com/auth/photoslibrary.appendonly");

  private static final String FILE_ACCESS_MODE = "r";

  private AlbumDemo() {}

  /**
   * Runs the album sample. An optional path to a credentials file can be specified as the first
   * commandline argument.
   */
  public static void main(String[] args) {
    // If the first argument is set, it contains the path to the credentials file.
    Optional<String> credentialsFile = Optional.empty();

    if (args.length > 0) {
      credentialsFile = Optional.of(args[0]);
    }

    UIHelper.setUp();

    ConnectToPhotosView connectToPhotosView =
        new ConnectToPhotosView(
            TITLE,
            ALBUM_INTRODUCTION,
            ALBUM_SAMPLE_IMAGE_RESOURCE,
            credentialsFile,
            getOnCredentialsSelectedFn(),
            getOnApplicationClosedFn() /* onViewClosed */);
    connectToPhotosView.showView();
  }

  private static BiConsumer<ConnectToPhotosView, String> getOnCredentialsSelectedFn() {
    return (connectToPhotosView, credentialsPath) -> {
      connectToPhotosView.hideView();
      try {
        PhotosLibraryClient client =
            PhotosLibraryClientFactory.createClient(credentialsPath, REQUIRED_SCOPES);
        connectToPhotosView.hideView();
        showAlbums(client);
      } catch (Exception e) {
        JOptionPane.showMessageDialog(connectToPhotosView, e.getMessage());
      }
    };
  }

  private static void showAlbums(PhotosLibraryClient client)
      throws IOException, FontFormatException {
    ListAlbumsRequest request = ListAlbumsRequest.getDefaultInstance();
    final ListAlbumsSupplier listAlbumsSupplier = new ListAlbumsSupplier(client, request);
    AppPanel appPanel =
        new AppPanel(ALBUM_TITLE, getOnApplicationClosedFn() /* onSignoutClicked */);
    CreateAlbumToolPanel createAlbumToolPanel =
        new CreateAlbumToolPanel(getOnCreateClickedFn(client));
    AlbumListView albumListView =
        new AlbumListView(
            appPanel,
            createAlbumToolPanel,
            listAlbumsSupplier,
            getOnAlbumClickedFn(client),
            getOnApplicationClosedFn() /* onViewClosed */);
    albumListView.showView();
  }

  private static BiConsumer<AbstractCustomView, String> getOnCreateClickedFn(
      PhotosLibraryClient client) {
    return (abstractCustomView, newAlbumTitle) -> {
      try {
        client.createAlbum(Album.newBuilder().setTitle(newAlbumTitle).build());
        abstractCustomView.updateView();
      } catch (Exception e) {
        JOptionPane.showMessageDialog(abstractCustomView, e.getMessage());
        abstractCustomView.showView();
      }
    };
  }

  private static BiConsumer<AlbumListView, Album> getOnAlbumClickedFn(PhotosLibraryClient client) {
    return (albumListView, album) -> {
      albumListView.hideView();
      try {
        showPhotosInAlbum(albumListView, client, album, photoListView -> albumListView.showView());
      } catch (Exception e) {
        JOptionPane.showMessageDialog(albumListView, e.getMessage());
        albumListView.showView();
      }
    };
  }

  private static void showPhotosInAlbum(
      AlbumListView albumListView,
      PhotosLibraryClient client,
      Album album,
      Consumer<PhotoListView> onAlbumClosed)
      throws IOException {
    SearchMediaItemsRequest request =
        SearchMediaItemsRequest.newBuilder().setAlbumId(album.getId()).build();
    SearchMediaItemSupplier mediaItemSupplier = new SearchMediaItemSupplier(client, request);

    AppPanel appPanel =
        new AppPanel(
            ALBUM_TITLE,
            getOnBackClickedFn(albumListView),
            getOnApplicationClosedFn() /* onSignoutClicked */);
    UploadToAlbumToolPanel uploadToAlbumToolPanel =
        new UploadToAlbumToolPanel(album, getOnFileSelectedFn(client, album));

    PhotoListView photoListView =
        new PhotoListView(
            appPanel, uploadToAlbumToolPanel, mediaItemSupplier, getOnItemClicked(), onAlbumClosed);
    photoListView.showView();
  }

  private static BiConsumer<AbstractCustomView, List<String>> getOnFileSelectedFn(
      PhotosLibraryClient client, Album album) {
    return (abstractCustomView, mediaItemPaths) -> {
      PhotoListView photoListView = (PhotoListView) abstractCustomView;
      for (String mediaItemPath : mediaItemPaths) {
        try {
          UploadMediaItemRequest.Builder uploadRequestBuilder = UploadMediaItemRequest.newBuilder();
          uploadRequestBuilder
              .setMimeType(URLConnection.guessContentTypeFromName(mediaItemPath))
              .setDataFile(new RandomAccessFile(mediaItemPath, FILE_ACCESS_MODE));
          ApiFuture<UploadMediaItemResponse> uploadResponseFuture =
              client.uploadMediaItemCallable().futureCall(uploadRequestBuilder.build());

          // Show loading dialog while uploading
          LoadingView.getLoadingView().showView();

          uploadResponseFuture.addListener(
              getOnUploadFinished(
                  client, photoListView, uploadResponseFuture, album, mediaItemPath),
              MoreExecutors.directExecutor());
        } catch (FileNotFoundException e) {
          LoadingView.getLoadingView().hideView();
          JOptionPane.showMessageDialog(photoListView, e.getMessage());
        }
      }
    };
  }

  private static Runnable getOnUploadFinished(
      PhotosLibraryClient client,
      PhotoListView photoListView,
      ApiFuture<UploadMediaItemResponse> uploadResponseFuture,
      Album album,
      String fileName) {
    return () -> {
      try {
        UploadMediaItemResponse uploadResponse = uploadResponseFuture.get();
        // Check if the upload is successful
        if (uploadResponse.getUploadToken().isPresent()) {
          BatchCreateMediaItemsRequest.Builder createRequestBuilder =
              BatchCreateMediaItemsRequest.newBuilder();
          createRequestBuilder.setAlbumId(album.getId());
          createRequestBuilder
              .addNewMediaItemsBuilder()
              .getSimpleMediaItemBuilder()
              .setFileName(fileName)
              .setUploadToken(uploadResponse.getUploadToken().get());
          client.batchCreateMediaItems(createRequestBuilder.build());

          // Hide loading dialog after finishing creating
          LoadingView.getLoadingView().hideView();

          photoListView.updateView();
        } else {
          LoadingView.getLoadingView().hideView();
          JOptionPane.showMessageDialog(photoListView, uploadResponse.getError());
        }
      } catch (Exception e) {
        LoadingView.getLoadingView().hideView();
        JOptionPane.showMessageDialog(photoListView, e.getMessage());
      }
    };
  }
}