package ru.vyarus.guice.persist.orient.repository.command.live.listener;

import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandResultListener;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.sql.query.OLiveQuery;
import com.orientechnologies.orient.core.sql.query.OLiveResultListener;
import ru.vyarus.guice.persist.orient.db.PersistentContext;
import ru.vyarus.guice.persist.orient.repository.command.ext.listen.Listen;
import ru.vyarus.guice.persist.orient.repository.command.ext.listen.support.ListenerParameterSupport;
import ru.vyarus.guice.persist.orient.repository.command.live.LiveQuery;
import ru.vyarus.guice.persist.orient.repository.command.live.listener.mapper.LiveQueryListener;
import ru.vyarus.guice.persist.orient.repository.command.live.listener.mapper.LiveResultMapper;
import ru.vyarus.guice.persist.orient.repository.core.ext.service.result.converter.RecordConverter;
import ru.vyarus.guice.persist.orient.repository.core.spi.parameter.ParamInfo;

import java.lang.annotation.Annotation;

import static ru.vyarus.guice.persist.orient.repository.core.MethodDefinitionException.check;
import static ru.vyarus.guice.persist.orient.repository.core.MethodExecutionException.checkExec;

/**
 * Handler for {@link Listen} parameters within {@link LiveQuery}.
 * <p>
 * Listener may be {@link OLiveResultListener} (orient) or {@link LiveQueryListener} if special result
 * conversions required.
 * <p>
 * Listener wrapped with an external transaction to be able to use thread bound listener connection through guice.
 * <p>
 * If no transaction is required and  listener implement {@link OCommandResultListener} directly then no wrapping
 * applied.
 *
 * @author Vyacheslav Rusakov
 * @since 29.09.2017
 */
public class LiveListenerParameterSupport implements ListenerParameterSupport {

    private static final Key<PersistentContext<ODatabaseDocument>> CONTEXT_KEY =
            Key.get(new TypeLiteral<PersistentContext<ODatabaseDocument>>() {
            });

    @Override
    public boolean accept(final Class<? extends Annotation> extension) {
        return LiveQuery.class.equals(extension);
    }

    @Override
    public void checkParameter(final String query, final ParamInfo<Listen> param, final Class<?> returnType) {
        check(int.class.equals(returnType) || Integer.class.equals(returnType),
                "Live query method must have int return type to receive subscription token");
        check(OLiveResultListener.class.isAssignableFrom(param.type)
                        || LiveQueryListener.class.isAssignableFrom(param.type),
                "Only %s or %s can be used as live listener",
                OLiveResultListener.class.getName(), LiveQueryListener.class.getName());
    }

    @Override
    public OCommandResultListener processListener(final OCommandRequest command,
                                                  final Object listener,
                                                  final Injector injector,
                                                  final Class<?> conversionTarget) {
        checkExec(command instanceof OLiveQuery,
                "Live listener (@%s parameter) can only be used with @%s",
                Listen.class.getSimpleName(), LiveQuery.class.getSimpleName());
        return wrap(listener, injector, conversionTarget);
    }

    private OCommandResultListener wrap(final Object listener,
                                        final Injector injector,
                                        final Class<?> targetType) {
        final OLiveResultListener adaptedListener = listener instanceof LiveQueryListener
                // special listener with custom mapping
                ? wrap((LiveQueryListener) listener, injector, targetType) : (OLiveResultListener) listener;

        // apply external transaction
        return new TransactionalLiveAdapter(injector.getInstance(CONTEXT_KEY), adaptedListener);
    }

    private OLiveResultListener wrap(final LiveQueryListener listener,
                                     final Injector injector,
                                     final Class<?> targetType) {
        final RecordConverter converter = injector.getInstance(RecordConverter.class);
        return new LiveResultMapper(converter, listener, targetType);
    }

}