package org.multiverse.stms.gamma;

import org.multiverse.api.*;
import org.multiverse.api.collections.TxnCollectionsFactory;
import org.multiverse.api.lifecycle.TxnListener;
import org.multiverse.collections.NaiveTxnCollectionFactory;
import org.multiverse.stms.gamma.transactionalobjects.*;
import org.multiverse.stms.gamma.transactions.*;
import org.multiverse.stms.gamma.transactions.fat.FatFixedLengthGammaTxn;
import org.multiverse.stms.gamma.transactions.fat.FatMonoGammaTxn;
import org.multiverse.stms.gamma.transactions.fat.FatVariableLengthGammaTxn;
import org.multiverse.stms.gamma.transactions.lean.LeanFixedLengthGammaTxn;
import org.multiverse.stms.gamma.transactions.lean.LeanMonoGammaTxn;

import static org.multiverse.stms.gamma.transactions.ThreadLocalGammaTxnPool.getThreadLocalGammaTxnPool;


@SuppressWarnings({"ClassWithTooManyFields"})
public final class GammaStm implements Stm {

    /**
     * Creates a GammaStm implementation optimized for speed. This method probably will be invoked
     * by the {@link GlobalStmInstance}.
     *
     * @return the created GammaStm.
     */
    @SuppressWarnings({"UnusedDeclaration"})
    public static GammaStm createFast() {
        return new GammaStm();
    }

    public final int defaultMaxRetries;
    public final int spinCount;
    public final BackoffPolicy defaultBackoffPolicy;
    public final GlobalConflictCounter globalConflictCounter = new GlobalConflictCounter();
    public final GammaTxnRefFactoryImpl defaultRefFactory = new GammaTxnRefFactoryImpl();
    public final GammaTxnRefFactoryBuilder refFactoryBuilder = new GammaTxnRefFactoryBuilderImpl();
    public final GammaTxnExecutor defaultxnExecutor;
    public final GammaTxnConfig defaultConfig;
    public final NaiveTxnCollectionFactory defaultTransactionalCollectionFactory
            = new NaiveTxnCollectionFactory(this);
    public final int readBiasedThreshold;
    public final GammaOrElseBlock defaultOrElseBlock = new GammaOrElseBlock();

    public GammaStm() {
        this(new GammaStmConfig());
    }

    public GammaStm(GammaStmConfig config) {
        config.validate();

        this.defaultMaxRetries = config.maxRetries;
        this.spinCount = config.spinCount;
        this.defaultBackoffPolicy = config.backoffPolicy;
        this.defaultConfig = new GammaTxnConfig(this, config)
                .setSpinCount(spinCount);
        this.defaultxnExecutor = newTxnFactoryBuilder()
                .setSpeculative(false)
                .newTxnExecutor();
        this.readBiasedThreshold = config.readBiasedThreshold;
    }

    @Override
    public final GammaTxn newDefaultTxn() {
        return new FatVariableLengthGammaTxn(this);
    }

    @Override
    public final GammaTxnExecutor getDefaultTxnExecutor() {
        return defaultxnExecutor;
    }

    @Override
    public final GammaOrElseBlock newOrElseBlock() {
        return defaultOrElseBlock;
    }

    public final GlobalConflictCounter getGlobalConflictCounter() {
        return globalConflictCounter;
    }

    private final class GammaTxnFactoryBuilderImpl implements GammaTxnFactoryBuilder {

        private final GammaTxnConfig config;

        GammaTxnFactoryBuilderImpl(final GammaTxnConfig config) {
            this.config = config;
        }

        @Override
        public final GammaTxnFactoryBuilder setFat() {
            if (config.isFat) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setFat());
        }

        @Override
        public final GammaTxnConfig getConfig() {
            return config;
        }

        @Override
        public GammaTxnFactoryBuilder addPermanentListener(final TxnListener listener) {
            return new GammaTxnFactoryBuilderImpl(config.addPermanentListener(listener));
        }

        @Override
        public final GammaTxnFactoryBuilder setControlFlowErrorsReused(final boolean reused) {
            if (config.controlFlowErrorsReused = reused) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setControlFlowErrorsReused(reused));
        }

        @Override
        public final GammaTxnFactoryBuilder setReadLockMode(final LockMode lockMode) {
            if (config.readLockMode == lockMode) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setReadLockMode(lockMode));
        }

        @Override
        public final GammaTxnFactoryBuilder setWriteLockMode(final LockMode lockMode) {
            if (config.writeLockMode == lockMode) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setWriteLockMode(lockMode));
        }

        @Override
        public final GammaTxnFactoryBuilder setFamilyName(final String familyName) {
            if (config.familyName.equals(familyName)) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setFamilyName(familyName));
        }

        @Override
        public final GammaTxnFactoryBuilder setPropagationLevel(final PropagationLevel level) {
            if (level == config.propagationLevel) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setPropagationLevel(level));
        }

        @Override
        public final GammaTxnFactoryBuilder setBlockingAllowed(final boolean blockingAllowed) {
            if (blockingAllowed == config.blockingAllowed) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setBlockingAllowed(blockingAllowed));
        }

        @Override
        public final GammaTxnFactoryBuilder setIsolationLevel(final IsolationLevel isolationLevel) {
            if (isolationLevel == config.isolationLevel) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setIsolationLevel(isolationLevel));
        }

        @Override
        public final GammaTxnFactoryBuilder setTraceLevel(final TraceLevel traceLevel) {
            if (traceLevel == config.traceLevel) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setTraceLevel(traceLevel));
        }

        @Override
        public final GammaTxnFactoryBuilder setTimeoutNs(final long timeoutNs) {
            if (timeoutNs == config.timeoutNs) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setTimeoutNs(timeoutNs));
        }

        @Override
        public final GammaTxnFactoryBuilder setInterruptible(final boolean interruptible) {
            if (interruptible == config.interruptible) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setInterruptible(interruptible));
        }

        @Override
        public final GammaTxnFactoryBuilder setBackoffPolicy(final BackoffPolicy backoffPolicy) {
            //noinspection ObjectEquality
            if (backoffPolicy == config.backoffPolicy) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setBackoffPolicy(backoffPolicy));
        }

        @Override
        public final GammaTxnFactoryBuilder setDirtyCheckEnabled(final boolean dirtyCheckEnabled) {
            if (dirtyCheckEnabled == config.dirtyCheck) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setDirtyCheckEnabled(dirtyCheckEnabled));
        }

        @Override
        public final GammaTxnFactoryBuilder setSpinCount(final int spinCount) {
            if (spinCount == config.spinCount) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setSpinCount(spinCount));
        }

        @Override
        public final GammaTxnFactoryBuilder setSpeculative(final boolean enabled) {
            if (enabled == config.speculative) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(
                    config.setSpeculative(enabled));
        }

        @Override
        public final GammaTxnFactoryBuilder setReadonly(final boolean readonly) {
            if (readonly == config.readonly) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setReadonly(readonly));
        }

        @Override
        public final GammaTxnFactoryBuilder setReadTrackingEnabled(final boolean enabled) {
            if (enabled == config.trackReads) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setReadTrackingEnabled(enabled));
        }

        @Override
        public final GammaTxnFactoryBuilder setMaxRetries(final int maxRetries) {
            if (maxRetries == config.maxRetries) {
                return this;
            }

            return new GammaTxnFactoryBuilderImpl(config.setMaxRetries(maxRetries));
        }

        @Override
        public final GammaTxnExecutor newTxnExecutor() {
            config.init();

            if (isLean()) {
                return new LeanGammaTxnExecutor(newTransactionFactory());
            } else {
                return new FatGammaTxnExecutor(newTransactionFactory());
            }
        }

        private boolean isLean() {
            return config.propagationLevel == PropagationLevel.Requires;
        }

        @Override
        public GammaTxnFactory newTransactionFactory() {
            config.init();

            if (config.isSpeculative()) {
                return new SpeculativeGammaTxnFactory(config, this);
            } else {
                return new NonSpeculativeGammaTxnFactory(config,this);
            }
        }
    }

    @Override
    public final GammaTxnRefFactory getDefaultRefFactory() {
        return defaultRefFactory;
    }

    private final class GammaTxnRefFactoryImpl implements GammaTxnRefFactory {
        @Override
        public final <E> GammaTxnRef<E> newTxnRef(E value) {
            return new GammaTxnRef<E>(GammaStm.this, value);
        }

        @Override
        public final GammaTxnInteger newTxnInteger(int value) {
            return new GammaTxnInteger(GammaStm.this, value);
        }

        @Override
        public final GammaTxnBoolean newTxnBoolean(boolean value) {
            return new GammaTxnBoolean(GammaStm.this, value);
        }

        @Override
        public final GammaTxnDouble newTxnDouble(double value) {
            return new GammaTxnDouble(GammaStm.this, value);
        }

        @Override
        public final GammaTxnLong newTxnLong(long value) {
            return new GammaTxnLong(GammaStm.this, value);
        }
    }

    @Override
    public final GammaTxnFactoryBuilder newTxnFactoryBuilder() {
        final GammaTxnConfig config = new GammaTxnConfig(this);
        return new GammaTxnFactoryBuilderImpl(config);
    }

    @Override
    public final TxnCollectionsFactory getDefaultTxnCollectionFactory() {
        return defaultTransactionalCollectionFactory;
    }

    @Override
    public final GammaTxnRefFactoryBuilder getTxRefFactoryBuilder() {
        return refFactoryBuilder;
    }

    private final class GammaTxnRefFactoryBuilderImpl implements GammaTxnRefFactoryBuilder {
        @Override
        public GammaTxnRefFactory build() {
            return new GammaTxnRefFactoryImpl();
        }
    }

    private static final class NonSpeculativeGammaTxnFactory implements GammaTxnFactory {

        private final GammaTxnConfig config;
        private final GammaTxnFactoryBuilder builder;

        NonSpeculativeGammaTxnFactory(final GammaTxnConfig config, GammaTxnFactoryBuilder builder) {
            this.config = config.init();
            this.builder = builder;
        }

        @Override
        public TxnFactoryBuilder getTxnFactoryBuilder() {
            return builder;
        }

        @Override
        public final GammaTxnConfig getConfig() {
            return config;
        }

        @Override
        public final GammaTxn newTxn() {
            return newTransaction(getThreadLocalGammaTxnPool());
        }

        @Override
        public final GammaTxn newTransaction(final GammaTxnPool pool) {
            FatVariableLengthGammaTxn tx = pool.takeMap();

            if (tx == null) {
                tx = new FatVariableLengthGammaTxn(config);
            } else {
                tx.init(config);
            }

            return tx;
        }

        @Override
        public final GammaTxn upgradeAfterSpeculativeFailure(final GammaTxn tailingTx, final GammaTxnPool pool) {
            throw new UnsupportedOperationException();
        }
    }

    private static final class SpeculativeGammaTxnFactory implements GammaTxnFactory {

        private final GammaTxnConfig config;
        private final GammaTxnFactoryBuilder builder;

        SpeculativeGammaTxnFactory(final GammaTxnConfig config, GammaTxnFactoryBuilder builder) {
            this.config = config.init();
            this.builder = builder;
        }

        @Override
        public GammaTxnFactoryBuilder getTxnFactoryBuilder() {
            return builder;
        }

        @Override
        public final GammaTxnConfig getConfig() {
            return config;
        }

        @Override
        public final GammaTxn newTxn() {
            return newTransaction(getThreadLocalGammaTxnPool());
        }

        @Override
        public final GammaTxn upgradeAfterSpeculativeFailure(final GammaTxn failingTx, final GammaTxnPool pool) {
            final GammaTxn tx = newTransaction(pool);
            tx.copyForSpeculativeFailure(failingTx);
            return tx;
        }

        @Override
        public final GammaTxn newTransaction(final GammaTxnPool pool) {
            final SpeculativeGammaConfiguration speculativeConfiguration = config.speculativeConfiguration.get();
            final int length = speculativeConfiguration.minimalLength;

            if (length <= 1) {
                if (speculativeConfiguration.fat) {
                    FatMonoGammaTxn tx = pool.takeFatMono();
                    if (tx == null) {
                        return new FatMonoGammaTxn(config);
                    }

                    tx.init(config);
                    return tx;
                } else {
                    LeanMonoGammaTxn tx = pool.takeLeanMono();
                    if (tx == null) {
                        return new LeanMonoGammaTxn(config);
                    }

                    tx.init(config);
                    return tx;
                }

            } else if (length <= config.maxFixedLengthTransactionSize) {
                if (speculativeConfiguration.fat) {
                    final FatFixedLengthGammaTxn tx = pool.takeFatFixedLength();
                    if (tx == null) {
                        return new FatFixedLengthGammaTxn(config);
                    }

                    tx.init(config);
                    return tx;
                } else {
                    final LeanFixedLengthGammaTxn tx = pool.takeLeanFixedLength();
                    if (tx == null) {
                        return new LeanFixedLengthGammaTxn(config);
                    }

                    tx.init(config);
                    return tx;
                }

            } else {
                final FatVariableLengthGammaTxn tx = pool.takeMap();
                if (tx == null) {
                    return new FatVariableLengthGammaTxn(config);
                }

                tx.init(config);
                return tx;
            }
        }
    }
}
