package act.db.morphia;

import act.ActComponent;
import act.app.App;
import act.app.DbServiceManager;
import act.db.Dao;
import act.db.DbService;
import act.db.morphia.util.FastJsonObjectIdCodec;
import act.util.FastJsonIterableSerializer;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.mongodb.MongoClient;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import org.mongodb.morphia.mapping.DefaultCreator;
import org.mongodb.morphia.mapping.Mapper;
import org.mongodb.morphia.mapping.MapperOptions;
import org.mongodb.morphia.query.MorphiaIterator;
import org.osgl.$;
import org.osgl.util.S;

import javax.inject.Singleton;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@ActComponent
@Singleton
public class MorphiaService extends DbService {

    // the morphia instance - keep track of class mapping
    private static Morphia morphia;

    private Datastore ds;
    private ConcurrentMap<Class<?>, Dao> daoMap;

    public MorphiaService(String id, final App app, Map<String, Object> conf) {
        super(id, app);
        if (null == morphia) {
            morphia = new Morphia();
            MapperOptions options = morphia.getMapper().getOptions();
            options.setObjectFactory(new DefaultCreator(){
                @Override
                protected ClassLoader getClassLoaderForClass() {
                    return app.classLoader();
                }
            });
            // the TypeConverterFinder will register it
            // morphia.getMapper().getConverters().addConverter(new DateTimeConverter());
        }
        daoMap = new ConcurrentHashMap<Class<?>, Dao>();
        MongoClient client = ClientManager.register(this, conf);
        initDataStore(client, conf);
        delayedEnsureIndexesAndCaps(app);
        registerFastJsonConfig();
    }

    @Override
    protected void releaseResources() {
        ClientManager.release(this);
        morphia = null;
    }

    @Override
    protected <DAO extends Dao> DAO defaultDao(Class<?> modelType) {
        return $.cast(new MorphiaDao(modelType, ds));
    }

    public Datastore ds() {
        return ds;
    }

    static Morphia morphia() {
        return morphia;
    }

    private void initDataStore(MongoClient client, Map<String, Object> conf) {
        String db = S.string(conf.get("db"));
        if (S.empty(db)) {
            db = id();
            if (DbServiceManager.DEFAULT.equals(db)) {
                db = "test";
                logger.warn("No \"db\" (database name) configured. Will use \"test\" as database name for the default service");
            } else {
                logger.warn("No \"db\" (database name) configured. Will use service id \"%s\" as database name", db);
            }
        }
        this.ds = morphia.createDatastore(client, db);
    }

    private void delayedEnsureIndexesAndCaps(App app) {
        app.jobManager().beforeAppStart(new Runnable() {
            @Override
            public void run() {
                ensureIndexesAndCaps();
            }
        });
    }

    private void ensureIndexesAndCaps() {
        ds.ensureIndexes();
        ds.ensureCaps();
    }

    private void registerFastJsonConfig() {
        FastJsonObjectIdCodec objectIdCodec = new FastJsonObjectIdCodec();

        SerializeConfig serializeConfig = SerializeConfig.getGlobalInstance();
        serializeConfig.put(ObjectId.class, objectIdCodec);
        serializeConfig.put(MorphiaIterator.class, FastJsonIterableSerializer.instance);

        ParserConfig parserConfig = ParserConfig.getGlobalInstance();
        parserConfig.putDeserializer(ObjectId.class, objectIdCodec);

    }

    public static Mapper mapper() {
        return morphia.getMapper();
    }
}

@@ -92,24 +92,57 @@
 iceManager;%0A
+import act.app.event.AppEventId;%0A
 import act.d
@@ -223,16 +223,40 @@
 dCodec;%0A
+import act.di.DiBinder;%0A
 import a
@@ -2020,24 +2020,786 @@
 onConfig();%0A
+        app.singletonRegistry().register(MorphiaService.class, this);%0A        app.jobManager().on(AppEventId.DEPENDENCY_INJECTOR_LOADED, new Runnable() %7B%0A            @Override%0A            public void run() %7B%0A                app().eventBus().emit(new DiBinder%3CMorphia%3E(this, Morphia.class) %7B%0A                    @Override%0A                    public Morphia resolve(App app) %7B%0A                        return MorphiaService.morphia();%0A                    %7D%0A                %7D);%0A                app().eventBus().emit(new DiBinder%3CMapper%3E(this, Mapper.class) %7B%0A                    @Override%0A                    public Mapper resolve(App app) %7B%0A                        return MorphiaService.mapper();%0A                    %7D%0A                %7D);%0A            %7D%0A        %7D);%0A
     %7D%0A%0A    @

Allow app to inject Morphia and Mapper instance