import { Parser, ParsingContext } from "../../chrono";
import { ParsingResult } from "../../results";
import { findMostLikelyADYear, findYearClosestToRef } from "../../calculation/years";

/**
 * Date format with slash "/" (or dot ".") between numbers.
 * For examples:
 * - 7/10
 * - 7/12/2020
 * - 7.12.2020
 */
const PATTERN = new RegExp(
    "([^\\d]|^)" +
        "([0-3]{0,1}[0-9]{1})[\\/\\.\\-]([0-3]{0,1}[0-9]{1})" +
        "(?:[\\/\\.\\-]([0-9]{4}|[0-9]{2}))?" +
        "(\\W|$)",
    "i"
);

const OPENING_GROUP = 1;
const ENDING_GROUP = 5;

const FIRST_NUMBERS_GROUP = 2;
const SECOND_NUMBERS_GROUP = 3;

const YEAR_GROUP = 4;

export default class SlashDateFormatParser implements Parser {
    groupNumberMonth: number;
    groupNumberDay: number;

    constructor(littleEndian: boolean) {
        this.groupNumberMonth = littleEndian ? SECOND_NUMBERS_GROUP : FIRST_NUMBERS_GROUP;
        this.groupNumberDay = littleEndian ? FIRST_NUMBERS_GROUP : SECOND_NUMBERS_GROUP;
    }

    pattern(): RegExp {
        return PATTERN;
    }

    extract(context: ParsingContext, match: RegExpMatchArray): ParsingResult {
        // Because of how pattern is executed on remaining text in `chrono.ts`, the character before the match could
        // still be a number (e.g. X[X/YY/ZZ] or XX[/YY/ZZ] or [XX/YY/]ZZ). We want to check and skip them.
        const index = match.index + match[OPENING_GROUP].length;
        const indexEnd = match.index + match[0].length - match[ENDING_GROUP].length;
        if (index > 0) {
            const textBefore = context.text.substring(0, index);
            if (textBefore.match("\\d/?$")) {
                return;
            }
        }
        if (indexEnd < context.text.length) {
            const textAfter = context.text.substring(indexEnd);
            if (textAfter.match("^/?\\d")) {
                return;
            }
        }

        const text = context.text.substring(index, indexEnd);

        // '1.12', '1.12.12' is more like a version numbers
        if (text.match(/^\d\.\d$/) || text.match(/^\d\.\d{1,2}\.\d{1,2}\s*$/)) {
            return;
        }

        // MM/dd -> OK
        // MM.dd -> NG
        if (!match[YEAR_GROUP] && text.indexOf("/") < 0) {
            return;
        }

        const result = context.createParsingResult(index, text);
        let month = parseInt(match[this.groupNumberMonth]);
        let day = parseInt(match[this.groupNumberDay]);
        if (month < 1 || month > 12) {
            if (month > 12) {
                if (day >= 1 && day <= 12 && month <= 31) {
                    [day, month] = [month, day];
                } else {
                    return null;
                }
            }
        }

        if (day < 1 || day > 31) {
            return null;
        }

        result.start.assign("day", day);
        result.start.assign("month", month);

        if (match[YEAR_GROUP]) {
            const rawYearNumber = parseInt(match[YEAR_GROUP]);
            const year = findMostLikelyADYear(rawYearNumber);
            result.start.assign("year", year);
        } else {
            const year = findYearClosestToRef(context.refDate, day, month);
            result.start.imply("year", year);
        }

        return result.addTag("parser/SlashDateFormatParser");
    }
}
