/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Nam Nguyen
 */

package com.caucho.quercus.lib.curl;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import com.caucho.quercus.QuercusContext;
import com.caucho.quercus.env.Callable;
import com.caucho.quercus.env.Callback;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.StringValue;
import com.caucho.quercus.env.Value;
import com.caucho.quercus.servlet.api.QuercusServletContext;
import com.caucho.util.L10N;
import com.caucho.util.RandomUtil;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.TempBuffer;

public class MultipartBody extends PostBody
{
  private static final L10N L = new L10N(CurlHttpRequest.class);

  private ArrayList<MultipartEntry> _postItems
    = new ArrayList<MultipartEntry>();

  private String _boundary;
  private byte []_boundaryBytes;
  private long _length;

  public MultipartBody(Env env, Value body)
  {
    _boundary = createBoundary();
    _boundaryBytes = _boundary.getBytes();

    Iterator<Map.Entry<Value,Value>> iter = body.getIterator(env);

    while (iter.hasNext()) {
      Map.Entry<Value,Value> entry = iter.next();

      StringValue key = entry.getKey().toString(env);
      StringValue value = entry.getValue().toString(env);

      if (value.length() > 0 && value.charAt(0) == '@') {
        StringValue fileName = value.substring(1);

        Path path = env.lookup(fileName);

        if (path == null || ! path.canRead()) {
          env.warning(L.l("cannot read file '{0}'", fileName));

          setValid(false);

          return;
        }

        _postItems.add(new PathEntry(env, key.toString(), path));
      }
      else
        _postItems.add(new UrlEncodedEntry(env, key.toString(), value));
    }

    _length = getContentLength(_postItems, _boundary);
  }

  private static String createBoundary()
  {
    return "boundary" + RandomUtil.getRandomLong();
  }

  private static long getContentLength(ArrayList<MultipartEntry> list,
                                       String boundary)
  {
    long size = (boundary.length() + 2) + 4;

    for (MultipartEntry entry : list) {
      size += entry.getLength() + (boundary.length() + 4) + 2;
    }

    return size;
  }

  @Override
  public String getContentType()
  {
    return "multipart/form-data; boundary=\"" + _boundary + "\"";
  }

  @Override
  public long getContentLength()
  {
    return _length;
  }

  public void writeTo(Env env,
                      OutputStream os)
    throws IOException
  {
    for (MultipartEntry entry : _postItems) {
      os.write('-');
      os.write('-');
      os.write(_boundaryBytes);

      os.write('\r');
      os.write('\n');

      entry.write(env, os);

      os.write('\r');
      os.write('\n');
    }

    os.write('-');
    os.write('-');
    os.write(_boundaryBytes);
    os.write('-');
    os.write('-');

    os.write('\r');
    os.write('\n');
  }

  static abstract class MultipartEntry {
    final String _name;
    final String _header;

    MultipartEntry(Env env, String name, String header)
    {
      _name = name;
      _header = header;
    }

    final String getName()
    {
      return _name;
    }

    static String getHeader(String name,
                            String contentType,
                            String fileName)
    {
      StringBuilder sb = new StringBuilder();

      sb.append("Content-Disposition: form-data;");

      sb.append(" name=\"");
      sb.append(name);
      sb.append("\"");

      if (fileName != null) {
        sb.append("; filename=\"");
        sb.append(fileName);
        sb.append("\"");

        sb.append('\r');
        sb.append('\n');
        sb.append("Content-Type: ");
        sb.append(contentType);
      }

      return sb.toString();
    }

    final void write(Env env, OutputStream os)
      throws IOException
    {
      int len = _header.length();

      for (int i = 0; i < len; i++) {
        os.write(_header.charAt(i));
      }

      os.write('\r');
      os.write('\n');
      os.write('\r');
      os.write('\n');

      writeData(env, os);
    }

    final long getLength()
    {
      return _header.length() + 4 + getLengthImpl();
    }

    abstract long getLengthImpl();

    abstract void writeData(Env env, OutputStream os) throws IOException;
  }

  static class UrlEncodedEntry extends MultipartEntry {
    StringValue _value;

    UrlEncodedEntry(Env env, String name, StringValue value)
    {
      super(env, name, getHeader(name,
                                 "application/x-www-form-urlencoded",
                                 null));
      _value = value;
    }

    long getLengthImpl()
    {
      return _value.length();
    }

    void writeData(Env env, OutputStream os)
      throws IOException
    {
      os.write(_value.toString().getBytes());
    }
  }

  static class PathEntry extends MultipartEntry {
    Path _path;

    PathEntry(Env env, String name, Path path)
    {
      super(env, name, getHeader(name,
                                 getContentType(env, path.getTail()),
                                 path.getTail()));
      _path = path;
    }

    long getLengthImpl()
    {
      return _path.getLength();
    }

    void writeData(Env env, OutputStream os)
      throws IOException
    {
      TempBuffer tempBuf = null;

      try {
       tempBuf = TempBuffer.allocate();
        byte []buf = tempBuf.getBuffer();

        ReadStream is = _path.openRead();

        int len;
        while ((len = is.read(buf, 0, buf.length)) > 0) {
          os.write(buf, 0, len);
        }

      } finally {
        if (tempBuf != null)
          TempBuffer.free(tempBuf);
      }
    }

    private static String getContentType(Env env, String name)
    {
      QuercusContext quercus = env.getQuercus();

      QuercusServletContext context = quercus.getServletContext();

      if (context != null) {
        String mimeType = context.getMimeType(name);

        if (mimeType != null)
          return mimeType;
        else
          return "application/octet-stream";
      }
      else {
        int i = name.lastIndexOf('.');

        if (i < 0)
          return "application/octet-stream";
        else if (name.endsWith(".txt"))
          return "text/plain";
        else if (name.endsWith(".jpg") || name.endsWith(".jpeg"))
          return "image/jpeg";
        else if (name.endsWith(".gif"))
          return "image/gif";
        else if (name.endsWith(".tif") || name.endsWith(".tiff"))
          return "image/tiff";
        else if (name.endsWith(".png"))
          return "image/png";
        else if (name.endsWith(".htm") || name.endsWith(".html"))
          return "text/html";
        else if (name.endsWith(".xml"))
          return "text/xml";
        else
          return "application/octet-stream";
      }

    }
  }
}
