package com.devskiller.jfairy.producer.person.locale.pl;

import javax.inject.Inject;
import java.time.LocalDate;

import com.google.common.annotations.VisibleForTesting;

import com.devskiller.jfairy.producer.BaseProducer;
import com.devskiller.jfairy.producer.DateProducer;
import com.devskiller.jfairy.producer.person.NationalIdentityCardNumberProvider;
import com.devskiller.jfairy.producer.util.AlphaNumberSystem;

import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.copyValueOf;
import static java.lang.String.valueOf;
import static java.lang.System.arraycopy;
import static org.apache.commons.lang3.StringUtils.leftPad;

/**
 * Polish Identity Card Number
 * <p>
 * http://en.wikipedia.org/wiki/Polish_identity_card
 */
public class PlNationalIdentityCardNumberProvider implements NationalIdentityCardNumberProvider {

	@VisibleForTesting
	static final int ISSUING_BEGIN = 2000;

	private static final int[] WEIGHTS = new int[]{7, 3, 1, 0, 7, 3, 1, 7, 3};
	private static final int CHECKSUM_INDEX = 3;

	private static final int DIGITS_PART_SIZE = 5;

	@VisibleForTesting
	static final int MAX_DIGITS_PART_VALUE = 99999;

	@VisibleForTesting
	static final int LETTER_WEIGHT = 45;
	private static final int LETTERS_PART_SIZE = 3;
	private static final int LETTER_VALUE_MODIFIER = 10;
	private static final int ALPHABET_SIZE = 26;

	private static final int BASE_TEN = 10;

	private final DateProducer dateProducer;
	private final BaseProducer baseProducer;

	@Inject
	public PlNationalIdentityCardNumberProvider(DateProducer dateProducer, BaseProducer baseProducer) {
		this.dateProducer = dateProducer;
		this.baseProducer = baseProducer;
	}

	@Override
	public String get() {

		LocalDate dateTime = dateProducer.randomDateBetweenYearAndNow(ISSUING_BEGIN).toLocalDate();

		return get(dateTime);
	}

	public String get(LocalDate date) {

		checkArgument(date.getYear() >= ISSUING_BEGIN, "Polish ID was introduced in 2000");

		char[] id = new char[WEIGHTS.length];

		fillLettersPart(date.getYear(), id);
		fillDigitsPart(id);

		char checksum = calculateChecksum(id);

		id[CHECKSUM_INDEX] = checksum;

		return copyValueOf(id);

	}

	public boolean isValid(String id) {
		int checksum = calculateChecksum(id.toCharArray());

		return id.charAt(CHECKSUM_INDEX) == checksum;
	}

	private char calculateChecksum(char[] id) {
		int index = 0;
		int checksum = 0;

		for (int weight : WEIGHTS) {
			int value = 0;
			if (index < CHECKSUM_INDEX) {
				value = id[index] - 'A' + LETTER_VALUE_MODIFIER;
			} else if (index > CHECKSUM_INDEX) {
				value = id[index] - '0';
			}
			index++;
			checksum += weight * value;
		}

		return valueOf(checksum % BASE_TEN).charAt(0);
	}

	private void fillDigitsPart(char[] id) {
		String num = valueOf(baseProducer.randomInt(MAX_DIGITS_PART_VALUE));
		char[] digits = leftPad(num, DIGITS_PART_SIZE, '0').toCharArray();
		arraycopy(digits, 0, id, CHECKSUM_INDEX + 1, digits.length);
	}

	private void fillLettersPart(int year, char[] id) {
		int maxPrefix = (year - ISSUING_BEGIN) * LETTER_WEIGHT;
		int range = baseProducer.randomBetween(maxPrefix, maxPrefix + LETTER_WEIGHT);
		String prefix = AlphaNumberSystem.convertToString(range, ALPHABET_SIZE);
		char[] charArray = leftPad(prefix, LETTERS_PART_SIZE, 'A').toCharArray();
		arraycopy(charArray, 0, id, 0, charArray.length);
	}

}
