Subversion Repositories SmartDukaan

Rev

Blame | Last modification | View Log | RSS feed

package com.smartdukaan.cron.offercircular;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Parses the "EMI tenure" cell into (months, scheme) pairs.
 *
 * The scheme marker always FOLLOWS the group of months it applies to, so months are
 * accumulated and flushed each time a marker is seen:
 *   "3,6 NC 9,12,18,24 LC" -> 3/NCE, 6/NCE, 9/LCE, 12/LCE, 18/LCE, 24/LCE
 *
 * Some cells name a payment mode rather than a tenure ("Full Swipe", "UPI", "CIB"
 * alone) and correctly yield nothing.
 */
public final class TenureParser {

    /** Tenures the OEMs actually offer; anything else is reported, not stored. */
    private static final Set<Integer> VALID_MONTHS = new LinkedHashSet<>(
            java.util.Arrays.asList(3, 6, 9, 12, 18, 24));

    private static final Pattern TOKEN = Pattern.compile("\\d+|[A-Z]+");
    private static final Pattern HAS_DIGIT = Pattern.compile("\\d");
    private static final Pattern MODE_ONLY = Pattern.compile("FULL SWIPE|UPI|CIB");

    public static final class Tenure {
        private final int months;
        private final String scheme;
        Tenure(int months, String scheme) { this.months = months; this.scheme = scheme; }
        public int getMonths() { return months; }
        public String getScheme() { return scheme; }
        @Override public boolean equals(Object o) {
            if (!(o instanceof Tenure)) { return false; }
            Tenure t = (Tenure) o;
            return months == t.months && scheme.equals(t.scheme);
        }
        @Override public int hashCode() { return months * 31 + scheme.hashCode(); }
        @Override public String toString() { return months + ":" + scheme; }
    }

    public static final class Result {
        private final List<Tenure> tenures = new ArrayList<>();
        private String warning;
        public List<Tenure> getTenures() { return tenures; }
        public String getWarning() { return warning; }
    }

    private TenureParser() { }

    private static String scheme(String token) {
        switch (token) {
            case "NCE": case "NC": return "NCE";
            case "LCE": case "LC": return "LCE";
            case "CIB":            return "CIB";
            default:               return null;
        }
    }

    public static Result parse(String cell) {
        Result result = new Result();
        if (cell == null) {
            return result;
        }
        String text = cell.trim();
        if (text.isEmpty() || "-".equals(text) || "NA".equals(text) || "N/A".equals(text)) {
            return result;
        }
        // PDF line-wrap artefacts: "(L CE)" and "1 8" (meaning 18) are one token split
        // across a line break and rejoined with a space.
        text = text.replaceAll("(?i)\\bL\\s+CE\\b", "LCE")
                   .replaceAll("(?i)\\bN\\s+CE\\b", "NCE")
                   .replaceAll("(?<=\\d)\\s+(?=\\d)", "");
        String upper = text.toUpperCase();

        if (!HAS_DIGIT.matcher(upper).find()) {
            result.warning = MODE_ONLY.matcher(upper).find() ? "mode-only" : "no-digits";
            return result;
        }

        List<Integer> pending = new ArrayList<>();
        List<Integer> rejected = new ArrayList<>();
        Set<Tenure> collected = new LinkedHashSet<>();

        Matcher m = TOKEN.matcher(upper);
        while (m.find()) {
            String token = m.group();
            if (Character.isDigit(token.charAt(0))) {
                pending.add(Integer.parseInt(token));
                continue;
            }
            String scheme = scheme(token);
            if (scheme != null) {
                flush(pending, scheme, collected, rejected);
            } else if ("M".equals(token)) {
                flush(pending, "NONE", collected, rejected);
            }
        }
        // trailing months with no marker: CIB if the cell mentions it, else unscheme
        if (!pending.isEmpty()) {
            flush(pending, upper.contains("CIB") ? "CIB" : "NONE", collected, rejected);
        }

        result.tenures.addAll(collected);
        result.tenures.sort((a, b) -> a.months != b.months
                ? Integer.compare(a.months, b.months)
                : a.scheme.compareTo(b.scheme));
        if (!rejected.isEmpty()) {
            result.warning = "bad-tenure:" + rejected;
        }
        return result;
    }

    private static void flush(List<Integer> pending, String scheme,
                              Set<Tenure> collected, List<Integer> rejected) {
        for (Integer months : pending) {
            if (VALID_MONTHS.contains(months)) {
                collected.add(new Tenure(months, scheme));
            } else {
                rejected.add(months);
            }
        }
        pending.clear();
    }
}