/*
 * Copyright 2017, Sascha Häberling
 *
 * 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 org.retrostore.request;

import com.google.appengine.api.blobstore.BlobInfo;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.retrostore.util.NumUtil;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Default RequestData implementation based on an HttpServletRequest.
 */
public class RequestDataImpl implements RequestData {
  private static final Logger LOG = Logger.getLogger("RequestDataImpl");
  private final HttpServletRequest mRequest;
  private final Type mType;
  private final Map<String, List<BlobInfo>> mBlobInfos;

  public static RequestData create(HttpServletRequest request,
                                   Type type,
                                   BlobstoreService blobstoreService) {
    Map<String, List<BlobInfo>> blobInfos = blobstoreService.getBlobInfos(request);
    blobInfos = blobInfos != null ? blobInfos : new HashMap<String, List<BlobInfo>>();
    return new RequestDataImpl(request, type, ImmutableMap.copyOf(blobInfos));

  }

  RequestDataImpl(HttpServletRequest request, Type type, Map<String, List<BlobInfo>> blobInfos) {
    mRequest = checkNotNull(request);
    mType = checkNotNull(type);
    mBlobInfos =  checkNotNull(blobInfos);
  }

  @Override
  public Type getType() {
    return mType;
  }

  @Override
  public String getUrl() {
    return mRequest.getRequestURI();
  }

  @Override
  public Optional<Integer> getInt(String name) {
    String value = getParameter(name);
    if (value == null) {
      return Optional.absent();
    }
    return NumUtil.parseInteger(value);
  }

  @Override
  public Optional<Long> getLong(String name) {
    String value = getParameter(name);
    if (value == null) {
      return Optional.absent();
    }
    return NumUtil.parseLong(name);
  }

  @Override
  public Optional<String> getString(String name) {
    return Optional.fromNullable(getParameter(name));
  }

  @Override
  public String getBody() {
    try {
      return CharStreams.toString(
          new InputStreamReader(mRequest.getInputStream(), Charsets.UTF_8));
    } catch (IOException ex) {
      LOG.warning(String.format("Could not read request body: '%s'.", ex.getMessage()));
      return "";
    }
  }

  @Override
  public byte[] getRawBody() {
    try {
      return ByteStreams.toByteArray(mRequest.getInputStream());
    } catch (IOException ex) {
      LOG.warning(String.format("Could not read request body: '%s'.", ex.getMessage()));
      return new byte[0];
    }
  }

  @Override
  public List<UploadFile> getFiles() {
    if (!ServletFileUpload.isMultipartContent(mRequest)) {
      return new ArrayList<>();
    }

    ServletFileUpload upload = new ServletFileUpload();
    try {
      List<UploadFile> files = new ArrayList<>();
      FileItemIterator itemIterator = upload.getItemIterator(mRequest);
      while (itemIterator.hasNext()) {
        FileItemStream file = itemIterator.next();
        ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
        ByteStreams.copy(file.openStream(), bytesOut);
        files.add(new UploadFile(file.getName(), bytesOut.toByteArray()));
      }
      return files;
    } catch (FileUploadException | IOException e) {
      LOG.log(Level.WARNING, "Cannot parse request for filename.", e);
      return new ArrayList<>();
    }
  }

  @Override
  public Map<String, List<String>> getBlobKeys() {
    Map<String, List<String>> blobKeys = new HashMap<>();
    for (String name : mBlobInfos.keySet()) {
      List<String> keys = new ArrayList<>();
      for (BlobInfo info : mBlobInfos.get(name)) {
        keys.add(info.getBlobKey().getKeyString());
      }
      blobKeys.put(name, keys);
    }
    return ImmutableMap.copyOf(blobKeys);
  }

  private String getParameter(String name) {
    return mRequest.getParameter(name);
  }

}

@@ -1893,35 +1893,20 @@
 nal 
-Map%3CString, List%3CBlobInfo%3E%3E
+BlobProvider
  mBl
@@ -1950,16 +1950,22 @@
  create(
+final 
 HttpServ
@@ -2065,16 +2065,22 @@
         
+final 
 Blobstor
@@ -2104,24 +2104,151 @@
 eService) %7B%0A
+    BlobProvider blobProvider = new BlobProvider() %7B%0A      @Override%0A      public Map%3CString, List%3CBlobInfo%3E%3E getBlobs() %7B%0A    
     Map%3CStri
@@ -2315,24 +2315,28 @@
 s(request);%0A
+    
     blobInfo
@@ -2406,24 +2406,86 @@
 obInfo%3E%3E();%0A
+        return ImmutableMap.copyOf(blobInfos);%0A      %7D%0A    %7D;%0A
     return n
@@ -2522,38 +2522,20 @@
 pe, 
-ImmutableMap.copyOf(blobInfos)
+blobProvider
 );%0A%0A
@@ -2600,35 +2600,20 @@
 pe, 
-Map%3CString, List%3CBlobInfo%3E%3E
+BlobProvider
  blo
@@ -2709,30 +2709,16 @@
 Infos = 
- checkNotNull(
 blobInfo
@@ -2714,25 +2714,24 @@
  = blobInfos
-)
 ;%0A  %7D%0A%0A  @Ov
@@ -4960,16 +4960,27 @@
 obInfos.
+getBlobs().
 keySet()
@@ -5066,16 +5066,27 @@
 obInfos.
+getBlobs().
 get(name
@@ -5325,10 +5325,86 @@
 );%0A  %7D%0A%0A
+  interface BlobProvider %7B%0A    Map%3CString, List%3CBlobInfo%3E%3E getBlobs();%0A  %7D%0A%0A
 %7D%0A

Make blob info lazily loaded.