package com.github.rnewson.couchdb.lucene.rhino;

/**
 * Copyright 2009 Robert Newson, Paul Davis
 * 
 * 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.
 */

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryParser.ParseException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;

import com.github.rnewson.couchdb.lucene.Tika;
import com.github.rnewson.couchdb.lucene.couchdb.Database;
import com.github.rnewson.couchdb.lucene.couchdb.FieldType;
import com.github.rnewson.couchdb.lucene.couchdb.ViewSettings;
import com.github.rnewson.couchdb.lucene.util.Utils;

/**
 * Collect data from the user.
 * 
 * @author rnewson
 * 
 */
public final class RhinoDocument extends ScriptableObject {

    private static class RhinoAttachment {
        private String attachmentName;
        private String fieldName;
    }

    private static class RhinoField {
        private NativeObject settings;
        private Object value;
    }

    private static final long serialVersionUID = 1L;

    public static Scriptable jsConstructor(final Context cx, final Object[] args, final Function ctorObj, final boolean inNewExpr) {
        final RhinoDocument doc = new RhinoDocument();
        if (args.length >= 2) {
            jsFunction_add(cx, doc, args, ctorObj);
        }
        return doc;
    }

    public static void jsFunction_add(final Context cx, final Scriptable thisObj, final Object[] args, final Function funObj) {
        final RhinoDocument doc = checkInstance(thisObj);

        if (args.length < 1 || args.length > 2) {
            throw Context.reportRuntimeError("Invalid number of arguments.");
        }

        if (args[0] == null) {
            throw Context.reportRuntimeError("first argument must be non-null.");
        }

        if (args[0] instanceof Undefined) {
            // Ignore
            return;
        }
        
        final String className = args[0].getClass().getName();
        
        if (className.equals("org.mozilla.javascript.NativeDate")) {
        	args[0] = (Date) Context.jsToJava(args[0], Date.class);
        }

        if (!className.startsWith("java.lang.") &&
        		!className.equals("org.mozilla.javascript.NativeObject") &&
        		!className.equals("org.mozilla.javascript.NativeDate")) {
        	throw Context.reportRuntimeError(className + " is not supported.");
        }

        if (args.length == 2 && (args[1] == null || args[1] instanceof NativeObject == false)) {
            throw Context.reportRuntimeError("second argument must be an object.");
        }

        final RhinoField field = new RhinoField();
        field.value = args[0];
        if (args.length == 2) {
            field.settings = (NativeObject) args[1];
        }

        doc.fields.add(field);
    }

    public static void jsFunction_attachment(final Context cx, final Scriptable thisObj, final Object[] args, final Function funObj)
            throws IOException {
        final RhinoDocument doc = checkInstance(thisObj);
        if (args.length < 2) {
            throw Context.reportRuntimeError("Invalid number of arguments.");
        }

        final RhinoAttachment attachment = new RhinoAttachment();
        attachment.fieldName = args[0].toString();
        attachment.attachmentName = args[1].toString();
        doc.attachments.add(attachment);
    }

    private static RhinoDocument checkInstance(final Scriptable obj) {
        if (obj == null || !(obj instanceof RhinoDocument)) {
            throw Context.reportRuntimeError("called on incompatible object.");
        }
        return (RhinoDocument) obj;
    }

    private final List<RhinoAttachment> attachments = new ArrayList<RhinoAttachment>();

    private final List<RhinoField> fields = new ArrayList<RhinoField>();

    public RhinoDocument() {
    }

    public Document toDocument(final String id, final ViewSettings defaults, final Database database) throws IOException,
            ParseException {
        final Document result = new Document();

        // Add id.
        result.add(Utils.token("_id", id, true));

        // Add user-supplied fields.
        for (final RhinoField field : fields) {
            addField(field, defaults, result);
        }

        // Parse user-requested attachments.
        for (final RhinoAttachment attachment : attachments) {
            addAttachment(attachment, id, database, result);
        }

        return result;
    }

    @Override
    public String getClassName() {
        return "Document";
    }

    private void addAttachment(final RhinoAttachment attachment, final String id, final Database database, final Document out)
            throws IOException {
        final ResponseHandler<Void> handler = new ResponseHandler<Void>() {

            public Void handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {
                final HttpEntity entity = response.getEntity();
                Tika.INSTANCE.parse(entity.getContent(), entity.getContentType().getValue(), attachment.fieldName, out);
                return null;
            }
        };

        database.handleAttachment(id, attachment.attachmentName, handler);
    }

    private void addField(final RhinoField field, final ViewSettings defaults, final Document out) throws ParseException {
        final ViewSettings settings = new ViewSettings(field.settings, defaults);
        final FieldType type = settings.getFieldType();
        out.add(type.toField(settings.getField(), field.value, settings));
    }

}

@@ -2662,80 +2662,38 @@
     
-    throw Context.reportRuntimeError(%22first argument must be non-null.%22)
+%09// Ignore.%0A            return
 ;%0A  

fix test to match new null behavior.