package io.activej.inject.binding;

import io.activej.common.tuple.*;
import io.activej.inject.Key;
import io.activej.inject.impl.*;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceArray;

@SuppressWarnings({"unchecked", "rawtypes", "Convert2Lambda"})
public class Bindings {

	public static class BindingToInstance<T> extends Binding<T> {
		final T instance;

		BindingToInstance(T instance) {
			super(Set.of());
			this.instance = instance;
		}

		@Override
		public CompiledBinding<T> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
			final T instance = this.instance;
			return slot != null ?
				new CompiledBinding<>() {
					@Override
					public T getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						scopedInstances[scope].lazySet(slot, instance);
						return instance;
					}
				} :
				new CompiledBinding<>() {
					@Override
					public T getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return instance;
					}
				};
		}
	}

	public static class BindingToConstructorN<R> extends Binding<R> {
		final TupleConstructorN<R> constructor;
		final Key<?>[] dependencies;

		BindingToConstructorN(TupleConstructorN<R> constructor, Key<?>[] dependencies) {
			super(new HashSet<>(List.of(dependencies)));
			this.constructor = constructor;
			this.dependencies = dependencies;
		}

		@Override
		public CompiledBinding<R> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
			final TupleConstructorN<R> constructor = this.constructor;
			final CompiledBinding<?>[] bindings = Arrays.stream(dependencies)
				.map(compiledBindings::get)
				.toArray(CompiledBinding[]::new);
			return slot != null ? threadsafe ? scope == 0 ?
				new AbstractRootCompiledBinding<>(slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						Object[] args = new Object[bindings.length];
						for (int i = 0; i < bindings.length; i++) {
							args[i] = bindings[i].getInstance(scopedInstances, synchronizedScope);
						}
						return constructor.create(args);
					}
				} :
				new AbstractCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						Object[] args = new Object[bindings.length];
						for (int i = 0; i < bindings.length; i++) {
							args[i] = bindings[i].getInstance(scopedInstances, synchronizedScope);
						}
						return constructor.create(args);
					}
				} :
				new AbstractUnsyncCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						Object[] args = new Object[bindings.length];
						for (int i = 0; i < bindings.length; i++) {
							args[i] = bindings[i].getInstance(scopedInstances, synchronizedScope);
						}
						return constructor.create(args);
					}
				} :
				new CompiledBinding<>() {
					@Override
					public R getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						Object[] args = new Object[bindings.length];
						for (int i = 0; i < bindings.length; i++) {
							args[i] = bindings[i].getInstance(scopedInstances, synchronizedScope);
						}
						return constructor.create(args);
					}
				};
		}
	}

	public static class BindingToConstructor0<R> extends Binding<R> {
		final TupleConstructor0<R> constructor;

		BindingToConstructor0(TupleConstructor0<R> constructor) {
			super(Set.of());
			this.constructor = constructor;
		}

		@Override
		public CompiledBinding<R> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
			final TupleConstructor0<R> constructor = this.constructor;
			return slot != null ? threadsafe ? scope == 0 ?
				new AbstractRootCompiledBinding<>(slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create();
					}
				} :
				new AbstractCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create();
					}
				} :
				new AbstractUnsyncCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create();
					}
				} :
				new CompiledBinding<>() {
					@Override
					public R getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create();
					}
				};
		}
	}

	public static class BindingToConstructor1<R, T1> extends Binding<R> {
		final TupleConstructor1<T1, R> constructor;
		final Key<T1> dependency1;

		BindingToConstructor1(TupleConstructor1<T1, R> constructor, Key<T1> dependency1) {
			super(Set.of(dependency1));
			this.constructor = constructor;
			this.dependency1 = dependency1;
		}

		@Override
		public CompiledBinding<R> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
			final TupleConstructor1<T1, R> constructor = this.constructor;
			final CompiledBinding<T1> binding1 = compiledBindings.get(dependency1);
			return slot != null ? threadsafe ? scope == 0 ?
				new AbstractRootCompiledBinding<>(slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractUnsyncCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new CompiledBinding<>() {
					@Override
					public R getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope));
					}
				};
		}
	}

	public static class BindingToConstructor2<R, T1, T2> extends Binding<R> {
		final TupleConstructor2<T1, T2, R> constructor;
		final Key<T1> dependency1;
		final Key<T2> dependency2;

		BindingToConstructor2(Key<T1> dependency1, Key<T2> dependency2, TupleConstructor2<T1, T2, R> constructor) {
			super(new HashSet<>(List.of(dependency1, dependency2)));
			this.constructor = constructor;
			this.dependency1 = dependency1;
			this.dependency2 = dependency2;
		}

		@Override
		public CompiledBinding<R> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
			final TupleConstructor2<T1, T2, R> constructor = this.constructor;
			final CompiledBinding<T1> binding1 = compiledBindings.get(dependency1);
			final CompiledBinding<T2> binding2 = compiledBindings.get(dependency2);
			return slot != null ? threadsafe ? scope == 0 ?
				new AbstractRootCompiledBinding<>(slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractUnsyncCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new CompiledBinding<>() {
					@Override
					public R getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope));
					}
				};
		}
	}

	public static class BindingToConstructor3<R, T1, T2, T3> extends Binding<R> {
		final TupleConstructor3<T1, T2, T3, R> constructor;
		final Key<T1> dependency1;
		final Key<T2> dependency2;
		final Key<T3> dependency3;

		BindingToConstructor3(TupleConstructor3<T1, T2, T3, R> constructor, Key<T1> dependency1, Key<T2> dependency2, Key<T3> dependency3) {
			super(new HashSet<>(List.of(dependency1, dependency2, dependency3)));
			this.dependency1 = dependency1;
			this.dependency2 = dependency2;
			this.dependency3 = dependency3;
			this.constructor = constructor;
		}

		@Override
		public CompiledBinding<R> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
			final TupleConstructor3<T1, T2, T3, R> constructor = this.constructor;
			final CompiledBinding<T1> binding1 = compiledBindings.get(dependency1);
			final CompiledBinding<T2> binding2 = compiledBindings.get(dependency2);
			final CompiledBinding<T3> binding3 = compiledBindings.get(dependency3);
			return slot != null ? threadsafe ? scope == 0 ?
				new AbstractRootCompiledBinding<>(slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractUnsyncCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new CompiledBinding<>() {
					@Override
					public R getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope));
					}
				};
		}
	}

	public static class BindingToConstructor4<R, T1, T2, T3, T4> extends Binding<R> {
		final TupleConstructor4<T1, T2, T3, T4, R> constructor;
		final Key<T1> dependency1;
		final Key<T2> dependency2;
		final Key<T3> dependency3;
		final Key<T4> dependency4;

		BindingToConstructor4(TupleConstructor4<T1, T2, T3, T4, R> constructor, Key<T1> dependency1, Key<T2> dependency2, Key<T3> dependency3, Key<T4> dependency4) {
			super(new HashSet<>(List.of(dependency1, dependency2, dependency3, dependency4)));
			this.constructor = constructor;
			this.dependency1 = dependency1;
			this.dependency2 = dependency2;
			this.dependency3 = dependency3;
			this.dependency4 = dependency4;
		}

		@Override
		public CompiledBinding<R> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
			final TupleConstructor4<T1, T2, T3, T4, R> constructor = this.constructor;
			final CompiledBinding<T1> binding1 = compiledBindings.get(dependency1);
			final CompiledBinding<T2> binding2 = compiledBindings.get(dependency2);
			final CompiledBinding<T3> binding3 = compiledBindings.get(dependency3);
			final CompiledBinding<T4> binding4 = compiledBindings.get(dependency4);
			return slot != null ? threadsafe ? scope == 0 ?
				new AbstractRootCompiledBinding<>(slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractUnsyncCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new CompiledBinding<>() {
					@Override
					public R getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope));
					}
				};
		}
	}

	public static class BindingToConstructor5<R, T1, T2, T3, T4, T5> extends Binding<R> {
		final TupleConstructor5<T1, T2, T3, T4, T5, R> constructor;
		final Key<T1> dependency1;
		final Key<T2> dependency2;
		final Key<T3> dependency3;
		final Key<T4> dependency4;
		final Key<T5> dependency5;

		BindingToConstructor5(TupleConstructor5<T1, T2, T3, T4, T5, R> constructor, Key<T1> dependency1, Key<T2> dependency2, Key<T3> dependency3, Key<T4> dependency4, Key<T5> dependency5) {
			super(new HashSet<>(List.of(dependency1, dependency2, dependency3, dependency4, dependency5)));
			this.constructor = constructor;
			this.dependency1 = dependency1;
			this.dependency2 = dependency2;
			this.dependency3 = dependency3;
			this.dependency4 = dependency4;
			this.dependency5 = dependency5;
		}

		@Override
		public CompiledBinding<R> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
			final TupleConstructor5<T1, T2, T3, T4, T5, R> constructor = this.constructor;
			final CompiledBinding<T1> binding1 = compiledBindings.get(dependency1);
			final CompiledBinding<T2> binding2 = compiledBindings.get(dependency2);
			final CompiledBinding<T3> binding3 = compiledBindings.get(dependency3);
			final CompiledBinding<T4> binding4 = compiledBindings.get(dependency4);
			final CompiledBinding<T5> binding5 = compiledBindings.get(dependency5);
			return slot != null ? threadsafe ? scope == 0 ?
				new AbstractRootCompiledBinding<>(slot) {

					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope),
							binding5.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope),
							binding5.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractUnsyncCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope),
							binding5.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new CompiledBinding<>() {
					@Override
					public R getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope),
							binding5.getInstance(scopedInstances, synchronizedScope));
					}
				};
		}
	}

	public static class BindingToConstructor6<R, T1, T2, T3, T4, T5, T6> extends Binding<R> {
		final TupleConstructor6<T1, T2, T3, T4, T5, T6, R> constructor;
		final Key<T1> dependency1;
		final Key<T2> dependency2;
		final Key<T3> dependency3;
		final Key<T4> dependency4;
		final Key<T5> dependency5;
		final Key<T6> dependency6;

		BindingToConstructor6(TupleConstructor6<T1, T2, T3, T4, T5, T6, R> constructor, Key<T1> dependency1, Key<T2> dependency2, Key<T3> dependency3, Key<T4> dependency4, Key<T5> dependency5, Key<T6> dependency6) {
			super(new HashSet<>(List.of(dependency1, dependency2, dependency3, dependency4, dependency5, dependency6)));
			this.constructor = constructor;
			this.dependency1 = dependency1;
			this.dependency2 = dependency2;
			this.dependency3 = dependency3;
			this.dependency4 = dependency4;
			this.dependency5 = dependency5;
			this.dependency6 = dependency6;
		}

		@Override
		public CompiledBinding<R> compile(CompiledBindingLocator compiledBindings, boolean threadsafe, int scope, @Nullable Integer slot) {
			final TupleConstructor6<T1, T2, T3, T4, T5, T6, R> constructor = this.constructor;
			final CompiledBinding<T1> binding1 = compiledBindings.get(dependency1);
			final CompiledBinding<T2> binding2 = compiledBindings.get(dependency2);
			final CompiledBinding<T3> binding3 = compiledBindings.get(dependency3);
			final CompiledBinding<T4> binding4 = compiledBindings.get(dependency4);
			final CompiledBinding<T5> binding5 = compiledBindings.get(dependency5);
			final CompiledBinding<T6> binding6 = compiledBindings.get(dependency6);
			return slot != null ? threadsafe ? scope == 0 ?
				new AbstractRootCompiledBinding<>(slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope),
							binding5.getInstance(scopedInstances, synchronizedScope),
							binding6.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope),
							binding5.getInstance(scopedInstances, synchronizedScope),
							binding6.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new AbstractUnsyncCompiledBinding<>(scope, slot) {
					@Override
					protected R doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope),
							binding5.getInstance(scopedInstances, synchronizedScope),
							binding6.getInstance(scopedInstances, synchronizedScope));
					}
				} :
				new CompiledBinding<>() {
					@Override
					public R getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
						return constructor.create(
							binding1.getInstance(scopedInstances, synchronizedScope),
							binding2.getInstance(scopedInstances, synchronizedScope),
							binding3.getInstance(scopedInstances, synchronizedScope),
							binding4.getInstance(scopedInstances, synchronizedScope),
							binding5.getInstance(scopedInstances, synchronizedScope),
							binding6.getInstance(scopedInstances, synchronizedScope));
					}
				};
		}
	}
}
