package com.josketres.rfcfacil;

import java8.util.stream.Stream;
import java8.util.stream.StreamSupport;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;

/**
 * Calculates the first ten digits of the RFC for a juristic person.
 */
public class JuristicPersonTenDigitsCodeCalculator {

    public static final String JURISTIC_PERSON_TYPE = " (S\\.? EN N\\.?C\\.?|" +
            "S\\.? EN C\\.? POR A\\.?|" +
            "S\\.? EN C\\.?|" +
            "S\\.? DE R\\.?L\\.?|" +
            "S\\.?A\\.? DE C\\.?V\\.?|" +
            "A\\.? EN P\\.?|" +
            "S\\.?C\\.?[LS]\\.?|" +
            "S\\.?[AC]\\.?|" +
            "S\\.?N\\.?C\\.?|" +
            "A\\.?C\\.?)$";

    public static final String[] FORBIDDEN_WORDS = {
            "A", "ANTE", "BAJO", "CABE", "CON", "CONTRA", "DE", "DESDE",
            "DURANTE", "EN", "ENTRE", "HACIA", "HASTA", "MEDIANTE", "PARA",
            "POR", "SEGUN", "SIN", "SO", "SOBRE", "TRAS", "VERSUS", "VIA",
            "EL", "LA", "LOS", "LAS",
            "UN", "UNA", "UNOS", "UNAS",
            "LO", /*"AL",*/ "DEL",
            "Y",
            "COMPANIA", "CIA.",
            "SOCIEDAD", "SOC."
    };

    private final JuristicPerson person;

    private String normalizedPersonLegalName;
    private String[] words;

    public JuristicPersonTenDigitsCodeCalculator(JuristicPerson person) {

        this.person = person;
    }

    public String calculate() {

        normalizePersonLegalName();
        ignoreJuristicPersonTypeAbbreviations();

        words = splitWords()
                .filter(this::ignoreForbiddenWords)
                .flatMap(this::splitAbbreviations)
                .flatMap(this::expandNumerals)
                .toArray(String[]::new);

        return threeDigitsCode() + "-" + birthdayCode();
    }

    private void normalizePersonLegalName() {

        normalizedPersonLegalName = normalize(person.legalName);
    }

    // to upper case, no leading nor trailing space,  and no accents
    private String normalize(String word) {

        if (StringUtils.isEmpty(word)) {
            return word;
        } else {
            return StringUtils.stripAccents(word)
                    .toUpperCase()
                    .trim();
        }
    }

    private void ignoreJuristicPersonTypeAbbreviations() {

        normalizedPersonLegalName = normalizedPersonLegalName.replaceAll(JURISTIC_PERSON_TYPE, "");
    }

    private Stream<String> splitWords() {

        return stream(normalizedPersonLegalName.split("[, ]+"));
    }

    private boolean ignoreForbiddenWords(String word) {
        return stream(FORBIDDEN_WORDS).noneMatch(f -> word.equalsIgnoreCase(f));
    }

    private Stream<String> splitAbbreviations(String w) {
        return stream(w.split("[\\. ]+"));
    }

    private Stream<String> expandNumerals(String word) {
        if (word.matches("[0-9]+")) {
            String number = normalize(SpanishNumbers.cardinal(Long.parseLong(word)));
            return stream(number.split(" "));
        } else {
            return StreamSupport.stream(Collections.singletonList(word));
        }
    }

    private String threeDigitsCode() {

        if (words.length >= 3) {
            return "" + words[0].charAt(0) + words[1].charAt(0) + words[2].charAt(0);
        } else if (words.length == 2) {
            return "" + words[0].charAt(0) + words[1].substring(0, 2);
        } else {
            return firstThreeCharactersWithRightPad(words[0]);
        }
    }

    private String firstThreeCharactersWithRightPad(String word) {

        if (word.length() >= 3) {
            return word.substring(0, 3);
        }
        return StringUtils.rightPad(word, 3, "X");
    }

    private String birthdayCode() {

        return lastTwoDigitsOf(person.year)
                + formattedInTwoDigits(person.month)
                + formattedInTwoDigits(person.day);
    }

    private String formattedInTwoDigits(int number) {

        return String.format(Locale.ENGLISH, "%02d", number);
    }

    private String lastTwoDigitsOf(int number) {

        return formattedInTwoDigits(number % 100);
    }

    private static Stream<String> stream(String[] array) {

        return StreamSupport.stream(Arrays.asList(array));
    }
}

@@ -1277,54 +1277,8 @@
 n;%0A%0A
-    private String normalizedPersonLegalName;%0A
     
@@ -1459,32 +1459,39 @@
     
-normalizeP
+words = stream(p
 erson
-L
+.l
 egalName
 ();%0A
@@ -1486,28 +1486,83 @@
 egalName
-();%0A        
+)%0A                .map(this::normalize)%0A                .map(this::
 ignoreJu
@@ -1594,21 +1594,20 @@
 ions
-();%0A%0A
+)%0A  
         
 word
@@ -1602,24 +1602,37 @@
         
-words = 
+      .flatMap(this::
 splitWor
@@ -1633,17 +1633,16 @@
 litWords
-(
 )%0A      
@@ -1895,126 +1895,8 @@
  %7D%0A%0A
-    private void normalizePersonLegalName() %7B%0A%0A        normalizedPersonLegalName = normalize(person.legalName);%0A    %7D%0A
 %0A   
@@ -1989,36 +1989,38 @@
 ormalize(String 
-word
+string
 ) %7B%0A%0A        if 
@@ -2040,20 +2040,22 @@
 isEmpty(
-word
+string
 )) %7B%0A   
@@ -2070,20 +2070,22 @@
  return 
-word
+string
 ;%0A      
@@ -2139,20 +2139,22 @@
 Accents(
-word
+string
 )%0A      
@@ -2244,12 +2244,14 @@
 ate 
-void
+String
  ign
@@ -2289,74 +2289,47 @@
 ons(
-) %7B%0A%0A        normalizedPersonLegalName = normalizedPersonLegalName
+String string) %7B%0A%0A        return string
 .rep
@@ -2408,16 +2408,29 @@
 itWords(
+String string
 ) %7B%0A%0A   
@@ -2452,33 +2452,14 @@
 eam(
-normalizedPersonLegalName
+string
 .spl
@@ -2998,61 +2998,20 @@
 urn 
-StreamSupport.stream(Collections.singletonList
+stream
 (word)
-)
 ;%0A  
@@ -4152,11 +4152,149 @@
 );%0A    %7D
+%0A%0A    private static Stream%3CString%3E stream(String string) %7B%0A%0A        return StreamSupport.stream(Collections.singletonList(string));%0A    %7D
 %0A%7D%0A

Refactor using java8 streams with StreamSupport and retrolambda for support in older versions of java.