package com.sora.util.akatsuki.analyzers;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Optional;

import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import com.sora.util.akatsuki.AndroidTypes;
import com.sora.util.akatsuki.MustacheUtils;
import com.sora.util.akatsuki.TransformationContext;
import com.sora.util.akatsuki.analyzers.CascadingTypeAnalyzer.Analysis;
import com.sora.util.akatsuki.analyzers.Element.Builder.SetterMode;
import com.sora.util.akatsuki.analyzers.PrimitiveTypeAnalyzer.Type;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.CodeBlock.Builder;

public class ArrayTypeAnalyzer
		extends CascadingTypeAnalyzer<ArrayTypeAnalyzer, ArrayType, Analysis> {

	static final AndroidTypes[] SUPPORTED_ARRAY_TYPES = { AndroidTypes.Parcelable,
			AndroidTypes.CharSequence, AndroidTypes.String };

	// for nested arrays (multidimensional arrays will have the same type)
	private final int depth;
	private static final String LOOP_EXPRESSION = "for (int $L = $L; $L < $L; $L++)";

	@Override
	protected ArrayTypeAnalyzer createInstance(TransformationContext context) {
		return new ArrayTypeAnalyzer(context, depth);
	}

	public ArrayTypeAnalyzer(TransformationContext context) {
		super(context);
		this.depth = 0;
	}

	private ArrayTypeAnalyzer(TransformationContext context, int depth) {
		super(context);
		this.depth = depth;
	}

	@Override
	public Analysis createAnalysis(InvocationContext<ArrayType> context)
			throws UnknownTypeException {
		final TypeMirror component = context.field.refinedMirror().getComponentType();

		// bundle supports all primitives
		if (utils().isPrimitive(component)) {
			return cascade(new PrimitiveTypeAnalyzer(this, Type.UNBOXED).suffix("Array"), context,
					component);
		}

		// bundle also supports some built in types
		final Optional<AndroidTypes> found = Arrays.stream(SUPPORTED_ARRAY_TYPES).sorted()
				.filter(t -> utils().isAssignable(component, utils().of(t.className), true))
				.findFirst();

		if (found.isPresent()) {
			return cascade(new ObjectTypeAnalyzer(this).suffix("Array")
					.target(types().getArrayType(found.get().asMirror(this))), context, component);
		} else {
			// everything else
			final Element<TypeMirror> refined = context.field.refine(component);

			CascadingTypeAnalyzer<?, ? extends TypeMirror, ? extends Analysis> resolved;
			if (component.getKind() == TypeKind.ARRAY) {
				throw new ConversionException(
						context.field + " is a multidimentional array, which is not supported "
								+ "(open an issue on Github if you want it)");
				// multidimensional array, we need to supply depth
				// resolved = new ArrayTypeAnalyzer(this, depth + 1);
			} else {
				// TODO, never really happens, handled by found type above
				resolved = resolve(refined).cast(TypeCastStrategy.NO_CAST);
			}

			// give up
			if (resolved == null)
				throw new UnknownTypeException(context.field);

			final String accessor = fieldAccessor(context);

			String currentCounter = counterName(context, depth);

			final Analysis cascade = cascade(resolved, context, f -> {
				return f.toBuilder().type(component)
						.keyName(SetterMode.APPEND, " + \"_\"+" + currentCounter)
						.accessor(SetterMode.APPEND, "[" + currentCounter + "]").build();
			});

			final HashMap<String, Object> scope = new HashMap<>();
			scope.put("bundle", context.bundleContext.bundleObjectName());
			scope.put("accessor", accessor + ".length");
			scope.put("lengthKey", "\"length_" + currentCounter + "_r\""
					+ (depth == 0 ? "" : " + " + counterName(context, depth - 1)));
			scope.put("lengthVar", "length_" + currentCounter);

			final String lengthStatement = MustacheUtils.render(scope,
					context.type == InvocationType.SAVE
							? "{{bundle}}.putInt({{lengthKey}}, {{accessor}})"
							: "int {{lengthVar}} = {{bundle}}.getInt({{lengthKey}})");

			cascade.wrap(original -> {
				final Builder builder = CodeBlock.builder();
				if (context.type == InvocationType.SAVE) {
					builder.beginControlFlow("if($L != null)", accessor)
							.addStatement(lengthStatement)
							.beginControlFlow(LOOP_EXPRESSION, currentCounter, 0, currentCounter,
									accessor + ".length", currentCounter)
							.add(original).endControlFlow().endControlFlow();
				} else {

					builder.addStatement(lengthStatement)
							.beginControlFlow(LOOP_EXPRESSION, currentCounter, 0, currentCounter,
									scope.get("lengthVar"), currentCounter)
							.add(original).endControlFlow();

				}
				return builder.build().toString();
			});

			return cascade;
		}

	}

	private String counterName(InvocationContext<?> context, int depth) {
		return context.field.uniqueName() + "_" + depth;
	}

}
