package com.google.sps.workspace;

import com.google.api.core.ApiFuture;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.google.sps.firebase.FirebaseAppManager;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;

/**
 * Provides an interface to access and modify workspaces in the datastore. The contents of this
 * object will only reflect the current state of the entities. It will not update with the
 * datastore.
 */
public class Workspace {
  private final DatabaseReference reference;

  public Workspace(String studentUID, String TaUID)
      throws IOException, InterruptedException, ExecutionException {
    reference = FirebaseDatabase.getInstance(FirebaseAppManager.getApp()).getReference().push();

    ApiFuture<Void> studentFuture = reference.child("student").setValueAsync(studentUID);
    ApiFuture<Void> taFuture = reference.child("ta").setValueAsync(TaUID);

    studentFuture.get();
    taFuture.get();
  }

  public Workspace(String workspaceID) throws IOException {
    reference =
        FirebaseDatabase.getInstance(FirebaseAppManager.getApp()).getReference().child(workspaceID);
  }

  /** @return the studentUID */
  public Future<String> getStudentUID() {
    CompletableFuture<String> future = new CompletableFuture<>();

    reference
        .child("student")
        .addListenerForSingleValueEvent(
            new ValueEventListener() {

              @Override
              public void onDataChange(DataSnapshot snapshot) {
                future.complete((String) snapshot.getValue());
              }

              @Override
              public void onCancelled(DatabaseError error) {
                future.completeExceptionally(error.toException());
              }
            });

    return future;
  }

  /** @return the taUID */
  public Future<String> getTaUID() {
    CompletableFuture<String> future = new CompletableFuture<>();

    reference
        .child("ta")
        .addListenerForSingleValueEvent(
            new ValueEventListener() {

              @Override
              public void onDataChange(DataSnapshot snapshot) {
                future.complete((String) snapshot.getValue());
              }

              @Override
              public void onCancelled(DatabaseError error) {
                future.completeExceptionally(error.toException());
              }
            });

    return future;
  }

  public Future<List<WorkspaceFile>> getFiles() {
    CompletableFuture<List<WorkspaceFile>> future = new CompletableFuture<>();

    reference
        .child("files")
        .addListenerForSingleValueEvent(
            new ValueEventListener() {

              @Override
              public void onDataChange(DataSnapshot snapshot) {
                try {
                  ArrayList<WorkspaceFile> children = new ArrayList<>();
                  for (DataSnapshot child : snapshot.getChildren()) {
                    children.add(new WorkspaceFile(child));
                  }

                  future.complete(children);
                } catch (Exception e) {
                  future.completeExceptionally(e);
                }
              }

              @Override
              public void onCancelled(DatabaseError error) {
                future.completeExceptionally(error.toException());
              }
            });

    return future;
  }

  public String getArchive(OutputStream out)
      throws InterruptedException, ExecutionException, IOException {
    List<WorkspaceFile> files = getFiles().get();

    TarArchiveOutputStream tarOut =
        new TarArchiveOutputStream(new GZIPOutputStream(new BufferedOutputStream(out)));

    for (WorkspaceFile file : files) {
      byte contents[] = file.getContents().get().getBytes();

      TarArchiveEntry entry = new TarArchiveEntry(file.getFilename());
      entry.setSize(contents.length);

      tarOut.putArchiveEntry(entry);
      tarOut.write(contents);

      tarOut.closeArchiveEntry();
    }

    tarOut.close();

    return "";
  }

  /** @return the workspaceID */
  public String getWorkspaceID() {
    return reference.getKey();
  }
}

@@ -524,24 +524,50 @@
 .util.List;%0A
+import java.util.Objects;%0A
 import java.
@@ -1269,27 +1269,64 @@
 n %7B%0A    
-reference =
+this(%0A        studentUID,%0A        TaUID,%0A       
  Firebas
@@ -1397,16 +1397,183 @@
 ).push()
+);%0A  %7D%0A%0A  protected Workspace(String studentUID, String TaUID, DatabaseReference reference)%0A      throws InterruptedException, ExecutionException %7B%0A    this(reference)
 ;%0A%0A    A
@@ -1594,32 +1594,40 @@
  studentFuture =
+%0A       
  reference.child
@@ -1652,16 +1652,39 @@
 ueAsync(
+Objects.requireNonNull(
 studentU
@@ -1686,16 +1686,17 @@
 dentUID)
+)
 ;%0A    Ap
@@ -1756,22 +1756,46 @@
 ueAsync(
+Objects.requireNonNull(
 TaUID)
+)
 ;%0A%0A    s
@@ -1907,19 +1907,13 @@
     
-reference =
+this(
 %0A   
@@ -1966,32 +1966,45 @@
 anager.getApp())
+%0A            
 .getReference().
@@ -2002,16 +2002,29 @@
 erence()
+%0A            
 .child(w
@@ -2034,16 +2034,131 @@
 spaceID)
+);%0A  %7D%0A%0A  protected Workspace(DatabaseReference reference) %7B%0A    this.reference = Objects.requireNonNull(reference)
 ;%0A  %7D%0A%0A 

Refactor workspace constructors for testing.