package com.clarifai.api;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * A request for recognition to be performed on one or more images. See
 * http://developer.clarifai.com for complete documentation on the Clarifai image recognition API.
 */
public class RecognitionRequest extends ClarifaiRequest {
  private static class Item {
    File file;
    byte[] imageBytes;
    String url;

    Item(File file) { this.file = file; }
    Item(byte[] imageBytes) { this.imageBytes = imageBytes; }
    Item(String url) { this.url = url; }

    InputStream getStream() throws IOException {
      if (file != null) {
        return new FileInputStream(file);
      } else if (imageBytes != null) {
        return new ByteArrayInputStream(imageBytes);
      }
      return null;
    }
  }

  private final List<Item> items = new ArrayList<Item>();
  private String model = "default";
  private boolean includeTags = true;
  private boolean includeEmbedding = false;
  private List<String> customOperations = new ArrayList<String>();
  private final Multipart multipart = new Multipart();

  /**
   * Constructs a new request for recognition on one or more images
   * @param files Files containing the image data to be recognized
   */
  public RecognitionRequest(File ... files) {
    for (File file : files) {
      items.add(new Item(file));
    }
  }

  /**
   * Constructs a new request for recognition on one or more images. The RecognitionRequest will
   * close the input streams when it is done sending them to the server.
   * @param imageByteArrays byte arrays containing image data to be recognized
   */
  public RecognitionRequest(byte[] ... imageByteArrays) {
    for (byte[] imageBytes : imageByteArrays) {
      items.add(new Item(imageBytes));
    }
  }

  /**
   * Constructs a new request for recognition on one or more images
   * @param urls publicly-accessible URLs for images to be recognized
   */
  public RecognitionRequest(String ... urls) {
    for (String url : urls) {
      items.add(new Item(url));
    }
  }

  /** Returns the name of the model to use for recognition. */
  public String getModel() {
    return model;
  }

  /** Sets the model to use for recognition. */
  public RecognitionRequest setModel(String model) {
    this.model = model;
    return this;
  }

  /** Returns true (default) if tags should be included in the result, or false if not. */
  public boolean getIncludeTags() {
    return includeTags;
  }

  /** Sets whether to include tags in the result. */
  public RecognitionRequest setIncludeTags(boolean includeTags) {
    this.includeTags = includeTags;
    return this;
  }

  /** Returns true if embeddings should be included in the result, or false (default) if not. */
  public boolean getIncludeEmbedding() {
    return includeEmbedding;
  }

  /** Sets whether to include embeddings in the result. */
  public RecognitionRequest setIncludeEmbedding(boolean includeEmbedding) {
    this.includeEmbedding = includeEmbedding;
    return this;
  }

  /** Adds a custom operation to the list of operations the server should perform on the image. */
  public RecognitionRequest addCustomOperation(String customOperation) {
    customOperations.add(customOperation);
    return this;
  }

  @Override String getContentType() {
    return "multipart/form-data; boundary=" + multipart.getBoundary();
  }

  @Override void writeContent(OutputStream out) throws IOException {
    List<String> ops = new ArrayList<String>();
    if (includeTags) {
      ops.add("tag");
    }
    if (includeEmbedding) {
      ops.add("embed");
    }
    ops.addAll(customOperations);

    StringBuilder opParam = new StringBuilder();
    for (String op : ops) {
      if (opParam.length() > 0) {
        opParam.append(',');
      }
      opParam.append(op);
    }

    multipart.start(out);
    multipart.writeParameter("op", opParam.toString());
    multipart.writeParameter("model", model);
    for (Item item : items) {
      if (item.url != null) {
        multipart.writeParameter("url", item.url);
      } else {
        InputStream in = item.getStream();
        try {
          multipart.writeMedia("encoded_data", "media", in);
        } finally {
          in.close();
        }
      }
    }
    multipart.finish();
  }
}

@@ -240,19 +240,67 @@
 va.util.
-Lis
+HashSet;%0Aimport java.util.List;%0Aimport java.util.Se
 t;%0A%0A/**%0A
@@ -1016,24 +1016,186 @@
 %0A%0A  private 
+static Set%3CString%3E defaultOperations() %7B%0A    HashSet%3CString%3E operations = new HashSet%3CString%3E();%0A    operations.add(%22tag%22);%0A    return operations;%0A  %7D%0A%0A  private 
 final List%3CI
@@ -1268,90 +1268,8 @@
 ult%22
-;%0A  private boolean includeTags = true;%0A  private boolean includeEmbedding = false
 ;%0A  
@@ -1276,19 +1276,24 @@
 private 
-Lis
+final Se
 t%3CString
@@ -1294,23 +1294,17 @@
 String%3E 
-customO
+o
 peration
@@ -1307,37 +1307,33 @@
 tions = 
-new ArrayList%3CString%3E
+defaultOperations
 ();%0A  pr
@@ -2738,27 +2738,42 @@
  return 
-includeTags
+operations.contains(%22tag%22)
 ;%0A  %7D%0A%0A 
@@ -2890,29 +2890,28 @@
 Tags) %7B%0A    
-this.
+if (
 includeTags 
@@ -2913,23 +2913,91 @@
 Tags
- = includeTags;
+) %7B%0A      operations.add(%22tag%22);%0A    %7D else %7B%0A      operations.remove(%22tag%22);%0A    %7D
 %0A   
@@ -3164,32 +3164,44 @@
  return 
-includeEmbedding
+operations.contains(%22embed%22)
 ;%0A  %7D%0A%0A 
@@ -3338,21 +3338,20 @@
 ) %7B%0A    
-this.
+if (
 includeE
@@ -3362,28 +3362,95 @@
 ding
- = includeEmbedding;
+) %7B%0A      operations.add(%22embed%22);%0A    %7D else %7B%0A      operations.remove(%22embed%22);%0A    %7D
 %0A   
@@ -3644,23 +3644,17 @@
 ) %7B%0A    
-customO
+o
 peration
@@ -3886,200 +3886,8 @@
 n %7B%0A
-    List%3CString%3E ops = new ArrayList%3CString%3E();%0A    if (includeTags) %7B%0A      ops.add(%22tag%22);%0A    %7D%0A    if (includeEmbedding) %7B%0A      ops.add(%22embed%22);%0A    %7D%0A    ops.addAll(customOperations);%0A%0A
     
@@ -3954,16 +3954,23 @@
  op : op
+eration
 s) %7B%0A   

Use a Set for operations.