/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.commons.correctness.builder.options;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Base64.Encoder;

import java.util.List;

public class SelectionCorrectnessBuilder {

	private List<String> votingOptions;
	private Integer max;
	private Integer min = Integer.valueOf(0);
	private VotingOptionCorrectnessBuilder correctnessBuilder;
	private String selectionId;
	private List<String> blanks = new ArrayList<>();

	private Integer accumulation = Integer.valueOf(1);
	private FusionBuilder fusionBuilder = null;

	public SelectionCorrectnessBuilder(VotingOptionCorrectnessBuilder correctnessBuilder, String selectionId) {
		this.correctnessBuilder = correctnessBuilder;
		this.selectionId = selectionId;
	}

	public SelectionCorrectnessBuilder withVotingOptions(List<String> votingOptions) {
		this.votingOptions = votingOptions;
		return this;
	}

	public SelectionCorrectnessBuilder withMaxSelection(Integer max) {
		this.max = max;
		return this;
	}

	public SelectionCorrectnessBuilder withMinSelection(Integer min) {
		this.min = min;
		return this;
	}

	public SelectionCorrectnessBuilder withBlanks(List<String> blanks) {
		this.blanks = blanks;
		return this;
	}
	
	public SelectionCorrectnessBuilder withFusions(List<List<String>> fusions) {
		fusionBuilder = new FusionBuilder(this);
		return fusionBuilder.withFusionVotingOptions(fusions);
	}

	public VotingOptionCorrectnessBuilder finish() {
		return correctnessBuilder;
	}

	String build(boolean fullBlank, boolean isBase64Encoded) {
		StringBuilder sb = new StringBuilder();
		sb.append("function (subSelection, allBlanksSelected, callbackFunction){");
		sb.append("var partialResult = true;");
		String value = selectionId;
		if(isBase64Encoded){
			value = Base64.getUrlEncoder().encodeToString(selectionId.getBytes(StandardCharsets.UTF_8));
		}
		sb.append("var selectionName = '" + value + "';");
		createValidVotingOptionArray(sb, isBase64Encoded);
		createVotingOptionsThatAreRelevantToTheCurrentSelection(sb);
		checkMinimumOrBlankSelection(sb, fullBlank, isBase64Encoded);
		checkMaxSelection(sb);
		checkFusion(sb, isBase64Encoded);
		sb.append("return partialResult;");
		sb.append("}(encodedSelection, allBlanksSelected, callbackFunction)");
		return sb.toString();
	}

	private void createVotingOptionsThatAreRelevantToTheCurrentSelection(StringBuilder sb) {
		sb.append("var selectionRelevantVotingOptions = CorrectnessFunctions.getRelevantVotingOptions(votingOptions, subSelection);");
	}

	private void createValidVotingOptionArray(StringBuilder sb, boolean isBase64Encoded) {
		sb.append("var votingOptions = [");
		Encoder encoder = Base64.getUrlEncoder();
		for (int i = 0; i < votingOptions.size(); i++) {
			String value = votingOptions.get(i);
			if (isBase64Encoded) {
				value = encoder.encodeToString(votingOptions.get(i).getBytes(StandardCharsets.UTF_8));
			}
			sb.append("'").append(value).append("'");
			if (i < votingOptions.size() - 1) {
				sb.append(", ");
			}
		}
		sb.append("];");
	}

	private void checkMaxSelection(StringBuilder sb) {
		if (max != null) {
			sb.append("partialResult = CorrectnessFunctions.isMaxMatch(selectionRelevantVotingOptions, " + max.intValue() + ", selectionName, callbackFunction) && partialResult;");
		}
	}

	private void checkMinimumOrBlankSelection(StringBuilder sb, boolean fullBlank, boolean isBase64Encoded) {
		sb.append("partialResult = CorrectnessFunctions.isMinMatch(selectionRelevantVotingOptions, " + (min != null? min.intValue() : 0) + ", selectionName, callbackFunction) && partialResult;");
		if(blanks != null && !blanks.isEmpty()){
			sb.append("var blanks = [");
			Encoder encoder = Base64.getUrlEncoder();
			for (int i = 0; i < blanks.size(); i++) {
				String value = blanks.get(i);
				if (isBase64Encoded) {
					value = encoder.encodeToString(votingOptions.get(i).getBytes(StandardCharsets.UTF_8));
				}
				sb.append("'").append(value).append("'");
				if (i < blanks.size() - 1) {
					sb.append(", ");
				}
			}
			sb.append("];");
			sb.append("partialResult = CorrectnessFunctions.isMinWithBlankMatch(selectionRelevantVotingOptions, " + (min != null? min.intValue() : 0) + ", allBlanksSelected, " + fullBlank + ", blanks, selectionName, callbackFunction) && partialResult;");
		}
	}

	List<String> getVotingOptions() {
		return votingOptions;
	}

	List<String> getBlanks() {
		return blanks;
	}

	private void checkFusion(StringBuilder sb, boolean isBase64Encoded) {
		if(fusionBuilder != null) {
			fusionBuilder.build(sb, accumulation, isBase64Encoded);
		}
	}
	
	public SelectionCorrectnessBuilder withAccumulation(Integer accumulation) {
		this.accumulation = accumulation;
		return this;
	}

	private static class FusionBuilder {

		private SelectionCorrectnessBuilder correctnessBuilder;
		private List<List<String>> fusions;

		FusionBuilder(SelectionCorrectnessBuilder correctnessBuilder) {
			this.correctnessBuilder = correctnessBuilder;
		}

		SelectionCorrectnessBuilder withFusionVotingOptions(List<List<String>> fusions) {
			this.fusions = fusions;
			return correctnessBuilder;
		}

		private void build(StringBuilder sb, Integer accumulation, boolean isBase64Encoded) {
			sb.append("var fusionVotingOptions = ");
			sb.append("[");
			Encoder encoder = Base64.getUrlEncoder();
			for (int i = 0; i < this.fusions.size(); i++){
				sb.append("[");
				List<String> list = this.fusions.get(i);
				for (int j = 0; j < list.size(); j++){
					String value = list.get(j);
					if(isBase64Encoded){
						value = encoder.encodeToString(list.get(j).getBytes(StandardCharsets.UTF_8));
					}
					sb.append("'").append(value).append("'");
					if (j < list.size() - 1) {
						sb.append(", ");
					}
				}
				sb.append("]");
				if (i < this.fusions.size() - 1) {
					sb.append(", ");
				}
			}
			sb.append("];");
			sb.append("partialResult = CorrectnessFunctions.checkFusion(fusionVotingOptions, " + accumulation + ", selectionRelevantVotingOptions, callbackFunction) && partialResult;");
		}
	}
}
