// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2009-2011 Google, All Rights reserved
// Copyright 2011-2012 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0

package com.google.appinventor.server;

import com.google.appinventor.server.storage.StorageIo;
import com.google.appinventor.server.storage.StorageIoInstanceHolder;
import com.google.appinventor.server.storage.UnauthorizedAccessException;
import com.google.appinventor.shared.rpc.project.Project;
import com.google.appinventor.shared.rpc.project.ProjectSourceZip;
import com.google.appinventor.shared.rpc.project.RawFile;
import com.google.appinventor.shared.rpc.project.TextFile;
import com.google.appinventor.shared.storage.StorageUtil;
import com.google.common.io.ByteStreams;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * Tests for {@link FileExporterImpl}.
 *
 */
public class FileExporterImplTest extends LocalDatastoreTestCase {
  private static final String USER_ID = "1";

  // The following represent a fake project, containing both source and
  // output files, for the purpose of testing.
  private static final String FAKE_PROJECT_TYPE = "FakeProjectType";
  private static final String PROJECT_NAME = "Project1";
  private static final String FORM1_NAME = "Screen1";
  private static final String FORM1_QUALIFIED_NAME = "com.yourdomain." + FORM1_NAME;
  private static final String FORM1_CONTENT = "Form A\nEnd Form";
  private static final String IMAGE1_NAME = "Image.jpg";
  private static final byte[] IMAGE_CONTENT = { (byte) 0, (byte) 1, (byte) 32, (byte) 255};
  private static final String TARGET1_NAME = "Project1.apk";
  private static final String TARGET1_QUALIFIED_NAME = "build/target1/" + TARGET1_NAME;
  private static final byte[] TARGET1_CONTENT = "pk1".getBytes();
  private static final String TARGET2_NAME = "Project2.pak";
  private static final String TARGET2_QUALIFIED_NAME = "build/target2/" + TARGET2_NAME;
  private static final byte[] TARGET2_CONTENT = "pk2".getBytes();
  private static final String SETTINGS = "";
  private static final String HISTORY = "1:History";

  private StorageIo storageIo;
  private FileExporterImpl exporter;
  private long projectId;

  @Override
  protected void setUp() throws Exception {
    super.setUp();
    storageIo = StorageIoInstanceHolder.INSTANCE;
    exporter = new FileExporterImpl();
    Project project = new Project(PROJECT_NAME);
    project.setProjectType(FAKE_PROJECT_TYPE);
    project.setProjectHistory(HISTORY);
    project.addTextFile(new TextFile(FORM1_QUALIFIED_NAME, ""));
    projectId = storageIo.createProject(USER_ID, project, SETTINGS);
    storageIo.uploadFile(projectId, FORM1_QUALIFIED_NAME, USER_ID, FORM1_CONTENT,
        StorageUtil.DEFAULT_CHARSET);
    storageIo.addSourceFilesToProject(USER_ID, projectId, false, IMAGE1_NAME);
    storageIo.uploadRawFile(projectId, IMAGE1_NAME, USER_ID, true, IMAGE_CONTENT);
    storageIo.addOutputFilesToProject(USER_ID, projectId, TARGET1_QUALIFIED_NAME);
    storageIo.uploadRawFile(projectId, TARGET1_QUALIFIED_NAME, USER_ID,
        true, TARGET1_CONTENT);
    storageIo.addOutputFilesToProject(USER_ID, projectId, TARGET2_QUALIFIED_NAME);
    storageIo.uploadRawFile(projectId, TARGET2_QUALIFIED_NAME, USER_ID,
        true, TARGET2_CONTENT);
  }

  private Map<String, byte[]> testExportProjectSourceZipHelper(ProjectSourceZip project)
      throws IOException {
    ZipInputStream zis =
        new ZipInputStream(new ByteArrayInputStream(project.getContent()));
    Map<String, byte[]> content = new HashMap<String, byte[]>();
    ZipEntry zipEntry;

    while ((zipEntry = zis.getNextEntry()) != null) {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      ByteStreams.copy(zis, baos);
      content.put(zipEntry.getName(), baos.toByteArray());
    }

    assertEquals(content.size(), project.getFileCount());

    assertTrue(content.containsKey(FORM1_QUALIFIED_NAME));
    assertTrue(content.containsKey(IMAGE1_NAME));
    assertFalse(content.containsKey(TARGET1_NAME));

    assertEquals(FORM1_CONTENT, new String(content.get(FORM1_QUALIFIED_NAME),
        StorageUtil.DEFAULT_CHARSET));
    assertTrue(Arrays.equals(IMAGE_CONTENT, content.get(IMAGE1_NAME)));

    return content;
  }

  public void testExportProjectSourceZipWithoutHistory() throws IOException {
    ProjectSourceZip project = exporter.exportProjectSourceZip(USER_ID, projectId,
        false, false, null);
    Map<String, byte[]> content = testExportProjectSourceZipHelper(project);
    assertEquals(2, content.size());
    /* Do not expect remix history when includeProjectHistory parameter is false
     * as in the publish case. */
    assertFalse(content.containsKey(FileExporter.REMIX_INFORMATION_FILE_PATH));
  }

  // TODO(user): Add test with properly formatted history
  public void testExportProjectSourceZipWithHistory() throws IOException {
    ProjectSourceZip project = exporter.exportProjectSourceZip(USER_ID, projectId,
        true, false, null);
    Map<String, byte[]> content = testExportProjectSourceZipHelper(project);
    assertEquals(3, content.size());
    // Expect the remix file to be in
    assertTrue(content.containsKey(FileExporter.REMIX_INFORMATION_FILE_PATH));
    assertEquals(HISTORY, new String(content.get(FileExporter.REMIX_INFORMATION_FILE_PATH),
        StorageUtil.DEFAULT_CHARSET));
  }

  public void testExportProjectSourceZipWithNonExistingProject() throws IOException {
    try {
      exporter.exportProjectSourceZip(USER_ID, projectId + 1, false, false, null);
      fail();
    } catch (Exception e) {
      assertTrue(e instanceof IllegalArgumentException ||
                 e.getCause() instanceof IllegalArgumentException);
    }
  }

  public void testExportProjectOutputFileWithTarget() throws IOException {
    RawFile file = exporter.exportProjectOutputFile(USER_ID, projectId, "target1");
    assertEquals(TARGET1_NAME, file.getFileName());
    assertTrue(Arrays.equals(TARGET1_CONTENT, file.getContent()));
  }

  public void testExportProjectOutputFileWithNonExistingTraget() throws IOException {
    try {
      exporter.exportProjectOutputFile(USER_ID, projectId, "target3");
      fail();
    } catch (IllegalArgumentException e) {
      // expected
    }
  }

  public void testExportFile() throws IOException {
    RawFile file = exporter.exportFile(USER_ID, projectId, FORM1_QUALIFIED_NAME);
    assertEquals(FORM1_QUALIFIED_NAME, file.getFileName());
    assertEquals(FORM1_CONTENT, new String(file.getContent(), StorageUtil.DEFAULT_CHARSET));
  }

  public void testExportFileWithNonExistingFile() throws IOException {
    final String nonExistingFileName = FORM1_QUALIFIED_NAME + "1";
    try {
      exporter.exportFile(USER_ID, projectId, nonExistingFileName);
      fail();
    } catch (RuntimeException e) {
      // expected
      // note that FileExporter throws an explicit RuntimeException
    }
  }

  // TODO(user): Add test of exportAllProjectsSourceZip().
}
