/*
 * Copyright (C) 2011 Benoit GUEROUT <bguerout at gmail dot com> and Yves AMSELLEM <amsellem dot yves at gmail dot com>
 *
 * 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.jongo;

import com.mongodb.*;
import org.bson.LazyBSONCallback;
import org.bson.types.ObjectId;
import org.jongo.bson.Bson;
import org.jongo.bson.BsonDocument;
import org.jongo.marshall.Marshaller;
import org.jongo.query.QueryFactory;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

class Insert {

    private final Marshaller marshaller;
    private final DBCollection collection;
    private final ObjectIdUpdater objectIdUpdater;
    private final QueryFactory queryFactory;
    private WriteConcern writeConcern;

    Insert(DBCollection collection, WriteConcern writeConcern, Marshaller marshaller, ObjectIdUpdater objectIdUpdater, QueryFactory queryFactory) {
        this.writeConcern = writeConcern;
        this.marshaller = marshaller;
        this.collection = collection;
        this.objectIdUpdater = objectIdUpdater;
        this.queryFactory = queryFactory;
    }

    public WriteResult save(Object pojo) {
        Object id = preparePojo(pojo);
        DBObject dbo = convertToDBObject(pojo, id);
        return collection.save(dbo, writeConcern);
    }

    public WriteResult insert(Object... pojos) {
        List<DBObject> dbos = new ArrayList<DBObject>(pojos.length);
        for (Object pojo : pojos) {
            Object id = preparePojo(pojo);
            DBObject dbo = convertToDBObject(pojo, id);
            dbos.add(dbo);
        }
        return collection.insert(dbos, writeConcern);
    }

    public WriteResult insert(String query, Object... parameters) {
        DBObject dbQuery = queryFactory.createQuery(query, parameters).toDBObject();
        if (dbQuery instanceof BasicDBList) {
            return insert(((BasicDBList) dbQuery).toArray());
        } else {
            return collection.insert(dbQuery, writeConcern);
        }
    }

    private Object preparePojo(Object pojo) {
        if (objectIdUpdater.mustGenerateObjectId(pojo)) {
            ObjectId newOid = ObjectId.get();
            objectIdUpdater.setObjectId(pojo, newOid);
            return newOid;
        }
        return objectIdUpdater.getId(pojo);
    }

    private DBObject convertToDBObject(Object pojo, Object id) {
        BsonDocument document = marshallDocument(pojo);
        if (!Bson.isPrimitive(id)) {
            id = marshallDocument(id).toDBObject();
        }
        return new AlreadyCheckedDBObject(document.toByteArray(), id);
    }

    private BsonDocument marshallDocument(Object pojo) {
        try {
            return marshaller.marshall(pojo);
        } catch (Exception e) {
            String message = String.format("Unable to save object %s due to a marshalling error", pojo);
            throw new IllegalArgumentException(message, e);
        }
    }

    private final static class AlreadyCheckedDBObject extends LazyWriteableDBObject {

        private final Set<String> keys;
        private final Object id;

        private AlreadyCheckedDBObject(byte[] data, Object id) {
            super(data, new LazyBSONCallback());
            this.id = id;
            this.keys = new HashSet<String>();
        }

        @Override
        public Object get(String key) {
            if ("_id".equals(key) && id != null) {
                ObjectId oid = ObjectId.massageToObjectId(id);
                return oid != null ? oid : id;
            }
            return super.get(key);
        }

        @Override
        public Set<String> keySet() {
            Set<String> combined = new HashSet<String>();
            combined.addAll(keys);
            combined.addAll(super.keySet());
            return combined;
        }
    }
}

@@ -2915,24 +2915,22 @@
 ument = 
-marshall
+asBson
 Document
@@ -2934,114 +2934,27 @@
 ent(
-pojo);%0A        if (!Bson.isPrimitive(id)) %7B%0A            id = marshallDocument(id).toDBObject(
+marshaller, pojo
 );%0A
-        %7D%0A
     
@@ -3019,343 +3019,24 @@
 (), 
-id);%0A    %7D%0A%0A    private BsonDocument marshallDocument(Object pojo) %7B%0A        try %7B%0A            return marshaller.marshall(pojo);%0A        %7D catch (Exception e) %7B%0A            String message = String.format(%22Unable to save object %25s due to a marshalling error%22, pojo);%0A            throw new IllegalArgumentException(message, e);%0A        %7D
+marshaller, id);
 %0A   
@@ -3152,24 +3152,62 @@
 nal 
-Set%3CString%3E keys
+Object id;%0A        private final Marshaller marshaller
 ;%0A  
@@ -3226,25 +3226,32 @@
 e final 
-Object id
+Set%3CString%3E keys
 ;%0A%0A     
@@ -3296,16 +3296,39 @@
 %5B%5D data,
+ Marshaller marshaller,
  Object 
@@ -3378,24 +3378,66 @@
 allback());%0A
+            this.marshaller = marshaller;%0A
             
@@ -3613,24 +3613,72 @@
  != null) %7B%0A
+                if (Bson.isPrimitive(id)) %7B%0A    
             
@@ -3736,32 +3736,36 @@
                 
+  
+  
 return oid != nu
@@ -3775,24 +3775,139 @@
 ? oid : id;%0A
+                %7D else %7B%0A                    return asBsonDocument(marshaller, id).toDBObject();%0A                %7D%0A
             
@@ -4192,11 +4192,367 @@
  %7D%0A    %7D
+%0A%0A    private static BsonDocument asBsonDocument(Marshaller marshaller, Object obj) %7B%0A        try %7B%0A            return marshaller.marshall(obj);%0A        %7D catch (Exception e) %7B%0A            String message = String.format(%22Unable to save object %25s due to a marshalling error%22, obj);%0A            throw new IllegalArgumentException(message, e);%0A        %7D%0A    %7D
 %0A%7D%0A

Move complex id marshalling into custom DBObject