import { EventHandlers } from "./eventhandlers";
import { keyCode, keys } from "./keycode.js";
import {
  determineLastRequiredPosition,
  determineNewCaretPosition,
  getBuffer,
  getLastValidPosition,
  isMask,
  resetMaskSet,
  seekNext,
  seekPrevious
} from "./positioning";
import {
  determineTestTemplate,
  getDecisionTaker,
  getPlaceholder,
  getTest,
  getTests,
  getTestTemplate
} from "./validation-tests";

export {
  alternate,
  checkAlternationMatch,
  isComplete,
  isSelection,
  isValid,
  refreshFromBuffer,
  revalidateMask,
  handleRemove
};

// tobe put on prototype?
function alternate(maskPos, c, strict, fromIsValid, rAltPos, selection) {
  // pos == true => generalize
  const inputmask = this,
    $ = this.dependencyLib,
    opts = this.opts,
    maskset = inputmask.maskset;

  if (!inputmask.hasAlternator) return false;

  let validPsClone = $.extend(true, [], maskset.validPositions),
    tstClone = $.extend(true, {}, maskset.tests),
    lastAlt,
    alternation,
    isValidRslt = false,
    returnRslt = false,
    altPos,
    prevAltPos,
    i,
    validPos,
    decisionPos,
    lAltPos =
      rAltPos !== undefined ? rAltPos : getLastValidPosition.call(inputmask),
    nextPos,
    input,
    begin,
    end;

  if (selection) {
    begin = selection.begin;
    end = selection.end;
    if (selection.begin > selection.end) {
      begin = selection.end;
      end = selection.begin;
    }
  }
  if (lAltPos === -1 && rAltPos === undefined) {
    // do not recurse when already paste the beginning
    lastAlt = 0;
    prevAltPos = getTest.call(inputmask, lastAlt);
    alternation = prevAltPos.alternation;
  } else {
    // find last modified alternation
    for (; lAltPos >= 0; lAltPos--) {
      altPos = maskset.validPositions[lAltPos];
      if (altPos && altPos.alternation !== undefined) {
        if (
          lAltPos <= (maskPos || 0) &&
          prevAltPos &&
          prevAltPos.locator[altPos.alternation] !==
            altPos.locator[altPos.alternation]
        ) {
          break;
        }
        lastAlt = lAltPos;
        alternation = maskset.validPositions[lastAlt].alternation;
        prevAltPos = altPos;
      }
    }
  }

  if (alternation !== undefined) {
    decisionPos = parseInt(lastAlt);
    maskset.excludes[decisionPos] = maskset.excludes[decisionPos] || [];
    if (maskPos !== true) {
      // generalize
      maskset.excludes[decisionPos].push(
        getDecisionTaker(prevAltPos) + ":" + prevAltPos.alternation
      );
    }

    let validInputs = [],
      resultPos = -1;
    for (
      i = decisionPos;
      decisionPos < getLastValidPosition.call(inputmask, undefined, true) + 1;
      i++
    ) {
      if (resultPos === -1 && maskPos <= i && c !== undefined) {
        validInputs.push(c);
        resultPos = validInputs.length - 1;
      }
      validPos = maskset.validPositions[decisionPos];
      if (
        validPos &&
        validPos.generatedInput !== true &&
        (selection === undefined || i < begin || i >= end)
      ) {
        validInputs.push(validPos.input);
      }
      // delete maskset.validPositions[i++];
      maskset.validPositions.splice(decisionPos, 1);
    }
    if (resultPos === -1 && c !== undefined) {
      validInputs.push(c);
      resultPos = validInputs.length - 1;
    }

    while (
      maskset.excludes[decisionPos] !== undefined &&
      maskset.excludes[decisionPos].length < 10
    ) {
      // maskset.tests[decisionPos] = undefined; //clear decisionPos
      maskset.tests = {}; // clear all
      resetMaskSet.call(inputmask, true); // clear getbuffer
      isValidRslt = true;
      for (i = 0; i < validInputs.length; i++) {
        nextPos =
          isValidRslt.caret ||
          (opts.insertMode == false && nextPos != undefined)
            ? seekNext.call(inputmask, nextPos)
            : getLastValidPosition.call(inputmask, undefined, true) + 1;
        input = validInputs[i];
        // nextPos = translatePosition.call(inputmask, nextPos);
        if (
          !(isValidRslt = isValid.call(
            inputmask,
            nextPos,
            input,
            false,
            fromIsValid,
            true
          ))
        ) {
          break;
        }
        if (i === resultPos) {
          returnRslt = isValidRslt;
        }
        if (maskPos == true && isValidRslt) {
          // return validposition on generalise
          returnRslt = { caretPos: i };
        }
      }
      if (!isValidRslt) {
        resetMaskSet.call(inputmask);
        prevAltPos = getTest.call(inputmask, decisionPos); // get the current decisionPos to exclude ~ needs to be before restoring the initial validation
        // reset & revert
        maskset.validPositions = $.extend(true, [], validPsClone);
        maskset.tests = $.extend(true, {}, tstClone); // refresh tests after possible alternating
        if (maskset.excludes[decisionPos]) {
          if (prevAltPos.alternation != undefined) {
            const decisionTaker = getDecisionTaker(prevAltPos);
            if (
              maskset.excludes[decisionPos].indexOf(
                decisionTaker + ":" + prevAltPos.alternation
              ) !== -1
            ) {
              returnRslt = alternate.call(
                inputmask,
                maskPos,
                c,
                strict,
                fromIsValid,
                decisionPos - 1,
                selection
              );
              break;
            }
            maskset.excludes[decisionPos].push(
              decisionTaker + ":" + prevAltPos.alternation
            );
            for (
              i = decisionPos;
              i < getLastValidPosition.call(inputmask, undefined, true) + 1;
              i++
            )
              maskset.validPositions.splice(decisionPos);
          } else delete maskset.excludes[decisionPos];
        } else {
          // latest alternation
          returnRslt = alternate.call(
            inputmask,
            maskPos,
            c,
            strict,
            fromIsValid,
            decisionPos - 1,
            selection
          );
          break;
        }
      } else {
        break;
      }
    }
  }

  // reset alternation excludes
  if (!returnRslt || opts.keepStatic !== false) {
    delete maskset.excludes[decisionPos];
  }
  return returnRslt;
}

function casing(elem, test, pos) {
  const opts = this.opts,
    maskset = this.maskset;

  switch (opts.casing || test.casing) {
    case "upper":
      elem = elem.toUpperCase();
      break;
    case "lower":
      elem = elem.toLowerCase();
      break;
    case "title":
      var posBefore = maskset.validPositions[pos - 1];
      if (
        pos === 0 ||
        (posBefore && posBefore.input === String.fromCharCode(keyCode.Space))
      ) {
        elem = elem.toUpperCase();
      } else {
        elem = elem.toLowerCase();
      }
      break;
    default:
      if (typeof opts.casing === "function") {
        const args = Array.prototype.slice.call(arguments);
        args.push(maskset.validPositions);
        elem = opts.casing.apply(this, args);
      }
  }

  return elem;
}

// tobe put on prototype?
function checkAlternationMatch(altArr1, altArr2, na) {
  const opts = this.opts;

  let altArrC = opts.greedy ? altArr2 : altArr2.slice(0, 1),
    isMatch = false,
    naArr = na !== undefined ? na.split(",") : [],
    naNdx;

  // remove no alternate indexes from alternation array
  for (let i = 0; i < naArr.length; i++) {
    if ((naNdx = altArr1.indexOf(naArr[i])) !== -1) {
      altArr1.splice(naNdx, 1);
    }
  }

  for (let alndx = 0; alndx < altArr1.length; alndx++) {
    if (altArrC.includes(altArr1[alndx])) {
      isMatch = true;
      break;
    }
  }
  return isMatch;
}

// tobe put on prototype?
function handleRemove(input, c, pos, strict, fromIsValid) {
  const inputmask = this,
    maskset = this.maskset,
    opts = this.opts;

  if (opts.numericInput || inputmask.isRTL) {
    if (c === keys.Backspace) {
      c = keys.Delete;
    } else if (c === keys.Delete) {
      c = keys.Backspace;
    }

    if (inputmask.isRTL) {
      const pend = pos.end;
      pos.end = pos.begin;
      pos.begin = pend;
    }
  }

  const lvp = getLastValidPosition.call(inputmask, undefined, true);
  if (pos.end >= getBuffer.call(inputmask).length && lvp >= pos.end) {
    // handle numeric negate symbol offset, due to  dynamic jit masking
    pos.end = lvp + 1;
  }

  if (c === keys.Backspace) {
    if (pos.end - pos.begin < 1) {
      pos.begin = seekPrevious.call(inputmask, pos.begin);
    }
  } else if (c === keys.Delete) {
    if (pos.begin === pos.end) {
      pos.end = isMask.call(inputmask, pos.end, true, true)
        ? pos.end + 1
        : seekNext.call(inputmask, pos.end) + 1;
    }
  }
  let offset;
  if ((offset = revalidateMask.call(inputmask, pos)) !== false) {
    if (
      (strict !== true && opts.keepStatic !== false) ||
      (opts.regex !== null &&
        getTest.call(inputmask, pos.begin).match.def.indexOf("|") !== -1)
    ) {
      // TODO NEEDS BETTER CHECK WHEN TO ALTERNATE  ~ opts regex isn"t good enough
      alternate.call(inputmask, true);
    }

    if (strict !== true) {
      maskset.p = c === keys.Delete ? pos.begin + offset : pos.begin;
      maskset.p = determineNewCaretPosition.call(
        inputmask,
        {
          begin: maskset.p,
          end: maskset.p
        },
        false,
        opts.insertMode === false && c === keys.Backspace ? "none" : undefined
      ).begin;
    }
  }
}

// tobe put on prototype?
function isComplete(buffer) {
  // return true / false / undefined (repeat *)
  const inputmask = this,
    opts = this.opts,
    maskset = this.maskset;

  if (typeof opts.isComplete === "function")
    return opts.isComplete(buffer, opts);
  if (opts.repeat === "*") return undefined;
  let complete = false,
    lrp = determineLastRequiredPosition.call(inputmask, true),
    aml = lrp.l; // seekPrevious.call(inputmask, lrp.l);

  if (
    lrp.def === undefined ||
    lrp.def.newBlockMarker ||
    lrp.def.optionality ||
    lrp.def.optionalQuantifier
  ) {
    complete = true;
    for (let i = 0; i <= aml; i++) {
      const test = getTestTemplate.call(inputmask, i).match;
      if (
        (test.static !== true &&
          maskset.validPositions[i] === undefined &&
          (test.optionality === false ||
            test.optionality === undefined ||
            (test.optionality && test.newBlockMarker == false)) &&
          (test.optionalQuantifier === false ||
            test.optionalQuantifier === undefined)) ||
        (test.static === true &&
          test.def != "" &&
          buffer[i] !== getPlaceholder.call(inputmask, i, test))
      ) {
        complete = false;
        break;
      }
    }
  }

  return complete;
}

function isSelection(posObj) {
  const inputmask = this,
    opts = this.opts,
    insertModeOffset = opts.insertMode ? 0 : 1;
  return inputmask.isRTL
    ? posObj.begin - posObj.end > insertModeOffset
    : posObj.end - posObj.begin > insertModeOffset;
}

// tobe put on prototype?
function isValid(
  pos,
  c,
  strict,
  fromIsValid,
  fromAlternate,
  validateOnly,
  fromCheckval
) {
  // strict true ~ no correction or autofill
  const inputmask = this,
    $ = this.dependencyLib,
    opts = this.opts,
    maskset = inputmask.maskset;

  strict = strict === true; // always set a value to strict to prevent possible strange behavior in the extensions

  let maskPos = pos;
  if (pos.begin !== undefined) {
    // position was a position object - used to handle a delete by typing over a selection
    maskPos = inputmask.isRTL ? pos.end : pos.begin;
  }

  function processCommandObject(commandObj) {
    if (commandObj !== undefined) {
      if (commandObj.remove !== undefined) {
        // remove position(s)
        if (!Array.isArray(commandObj.remove))
          commandObj.remove = [commandObj.remove];
        commandObj.remove
          .sort(function (a, b) {
            return inputmask.isRTL ? a.pos - b.pos : b.pos - a.pos;
          })
          .forEach(function (lmnt) {
            revalidateMask.call(inputmask, { begin: lmnt, end: lmnt + 1 });
          });
        commandObj.remove = undefined;
      }
      if (commandObj.insert !== undefined) {
        // insert position(s)
        if (!Array.isArray(commandObj.insert))
          commandObj.insert = [commandObj.insert];
        commandObj.insert
          .sort(function (a, b) {
            return inputmask.isRTL ? b.pos - a.pos : a.pos - b.pos;
          })
          .forEach(function (lmnt) {
            if (lmnt.c !== "") {
              isValid.call(
                inputmask,
                lmnt.pos,
                lmnt.c,
                lmnt.strict !== undefined ? lmnt.strict : true,
                lmnt.fromIsValid !== undefined ? lmnt.fromIsValid : fromIsValid
              );
            }
          });
        commandObj.insert = undefined;
      }

      if (commandObj.refreshFromBuffer && commandObj.buffer) {
        const refresh = commandObj.refreshFromBuffer;
        refreshFromBuffer.call(
          inputmask,
          refresh === true ? refresh : refresh.start,
          refresh.end,
          commandObj.buffer
        );
        commandObj.refreshFromBuffer = undefined;
      }

      if (commandObj.rewritePosition !== undefined) {
        maskPos = commandObj.rewritePosition;
        // commandObj.rewritePosition = undefined;
        commandObj = true; // see prevalidation in isValid
      }
    }
    return commandObj;
  }

  function _isValid(position, c, strict) {
    let rslt = false;
    getTests.call(inputmask, position).every(function (tst, ndx) {
      const test = tst.match;
      // make sure the buffer is set and correct
      getBuffer.call(inputmask, true);
      if (
        test.jit &&
        maskset.validPositions[seekPrevious.call(inputmask, position)] ===
          undefined
      ) {
        // ignore if jit is not desirable
        rslt = false;
      } else {
        // return is false or a json object => { pos: ??, c: ??} or true
        rslt =
          test.fn != null
            ? test.fn.test(
                c,
                maskset,
                position,
                strict,
                opts,
                isSelection.call(inputmask, pos)
              )
            : (c === test.def || c === opts.skipOptionalPartCharacter) &&
              test.def !== "" // non mask
            ? {
                c:
                  getPlaceholder.call(inputmask, position, test, true) ||
                  test.def,
                pos: position
              }
            : false;
      }
      if (rslt !== false) {
        let elem = rslt.c !== undefined ? rslt.c : c,
          validatedPos = position;
        elem =
          elem === opts.skipOptionalPartCharacter && test.static === true
            ? getPlaceholder.call(inputmask, position, test, true) || test.def
            : elem;

        rslt = processCommandObject(rslt);

        if (rslt !== true && rslt.pos !== undefined && rslt.pos !== position) {
          // their is a position offset
          validatedPos = rslt.pos;
        }

        if (rslt !== true && rslt.pos === undefined && rslt.c === undefined) {
          return false; // breakout if nothing to insert
        }

        if (
          revalidateMask.call(
            inputmask,
            pos,
            $.extend({}, tst, {
              input: casing.call(inputmask, elem, test, validatedPos)
            }),
            fromIsValid,
            validatedPos
          ) === false
        ) {
          rslt = false;
        }
        return false; // break from loop
      }

      return true;
    });
    return rslt;
  }

  let result = true,
    positionsClone = $.extend(true, [], maskset.validPositions); // clone the currentPositions

  if (
    opts.keepStatic === false &&
    maskset.excludes[maskPos] !== undefined &&
    fromAlternate !== true &&
    fromIsValid !== true
  ) {
    for (let i = maskPos; i < (inputmask.isRTL ? pos.begin : pos.end); i++) {
      if (maskset.excludes[i] !== undefined) {
        maskset.excludes[i] = undefined;
        delete maskset.tests[i];
      }
    }
  }

  if (
    typeof opts.preValidation === "function" &&
    fromIsValid !== true &&
    validateOnly !== true
  ) {
    result = opts.preValidation.call(
      inputmask,
      getBuffer.call(inputmask),
      maskPos,
      c,
      isSelection.call(inputmask, pos),
      opts,
      maskset,
      pos,
      strict || fromAlternate
    );
    result = processCommandObject(result);
  }
  if (result === true) {
    // preValidation result
    result = _isValid(maskPos, c, strict);
    if (
      (!strict || fromIsValid === true) &&
      result === false &&
      validateOnly !== true
    ) {
      const currentPosValid = maskset.validPositions[maskPos];
      if (
        currentPosValid &&
        currentPosValid.match.static === true &&
        (currentPosValid.match.def === c ||
          c === opts.skipOptionalPartCharacter)
      ) {
        result = {
          caret: seekNext.call(inputmask, maskPos)
        };
      } else {
        if (
          opts.insertMode ||
          maskset.validPositions[seekNext.call(inputmask, maskPos)] ===
            undefined ||
          pos.end > maskPos
        ) {
          // does the input match on a further position?
          let skip = false;
          if (
            maskset.jitOffset[maskPos] &&
            maskset.validPositions[seekNext.call(inputmask, maskPos)] ===
              undefined
          ) {
            result = isValid.call(
              inputmask,
              maskPos + maskset.jitOffset[maskPos],
              c,
              true,
              true
            );
            if (result !== false) {
              if (fromAlternate !== true) result.caret = maskPos;
              skip = true;
            }
          }
          if (pos.end > maskPos) {
            maskset.validPositions[maskPos] = undefined;
          }
          if (
            !skip &&
            !isMask.call(inputmask, maskPos, opts.keepStatic && maskPos === 0)
          ) {
            for (
              let nPos = maskPos + 1,
                snPos = seekNext.call(inputmask, maskPos, false, maskPos !== 0);
              nPos <= snPos;
              nPos++
            ) {
              // if (!isMask(nPos, true)) {
              // 	continue;
              // }
              result = _isValid(nPos, c, strict);
              if (result !== false) {
                result =
                  trackbackPositions.call(
                    inputmask,
                    maskPos,
                    result.pos !== undefined ? result.pos : nPos
                  ) || result;
                maskPos = nPos;
                break;
              }
            }
          }
        }
      }
    }

    if (inputmask.hasAlternator && fromAlternate !== true && !strict) {
      fromAlternate = true; // stop possible loop
      if (
        result === false &&
        opts.keepStatic &&
        (isComplete.call(inputmask, getBuffer.call(inputmask)) || maskPos === 0)
      ) {
        // try fuzzy alternator logic
        result = alternate.call(
          inputmask,
          maskPos,
          c,
          strict,
          fromIsValid,
          undefined,
          pos
        );
      } else if (
        isSelection.call(inputmask, pos) &&
        maskset.tests[maskPos] &&
        maskset.tests[maskPos].length > 1 &&
        opts.keepStatic
      ) {
        // selection clears an alternated keepstatic mask ~ #2189
        result = alternate.call(inputmask, true);
      } else if (
        result == true &&
        opts.numericInput !== true &&
        maskset.tests[maskPos] &&
        maskset.tests[maskPos].length > 1 &&
        getLastValidPosition.call(inputmask, undefined, true) > maskPos
      ) {
        // console.log("Alternating");
        result = alternate.call(inputmask, true);
      }
    }

    if (result === true) {
      result = {
        pos: maskPos
      };
    }
  }
  if (
    typeof opts.postValidation === "function" &&
    fromIsValid !== true &&
    validateOnly !== true
  ) {
    const postResult = opts.postValidation.call(
      inputmask,
      getBuffer.call(inputmask, true),
      pos.begin !== undefined ? (inputmask.isRTL ? pos.end : pos.begin) : pos,
      c,
      result,
      opts,
      maskset,
      strict,
      fromCheckval
    );
    if (postResult !== undefined) {
      result = postResult === true ? result : postResult;
    }
  }

  if (result && result.pos === undefined) {
    result.pos = maskPos;
  }

  if (result === false || validateOnly === true) {
    resetMaskSet.call(inputmask, true);
    maskset.validPositions = $.extend(true, [], positionsClone); // revert validation changes
  } else {
    trackbackPositions.call(inputmask, undefined, maskPos, true);
  }

  let endResult = processCommandObject(result);
  // console.log("returned result " + JSON.stringify(endResult));
  if (inputmask.maxLength !== undefined) {
    const buffer = getBuffer.call(inputmask);
    if (buffer.length > inputmask.maxLength && !fromIsValid) {
      resetMaskSet.call(inputmask, true);
      maskset.validPositions = $.extend(true, [], positionsClone); // revert validation changes
      endResult = false;
    }
  }
  return endResult;
}

// tobe put on prototype?
function positionCanMatchDefinition(pos, testDefinition, opts) {
  const inputmask = this,
    maskset = this.maskset;

  let valid = false,
    tests = getTests.call(inputmask, pos);
  for (let tndx = 0; tndx < tests.length; tndx++) {
    if (
      tests[tndx].match &&
      ((tests[tndx].match.nativeDef ===
        testDefinition.match[opts.shiftPositions ? "def" : "nativeDef"] &&
        (!opts.shiftPositions || !testDefinition.match.static)) ||
        tests[tndx].match.nativeDef === testDefinition.match.nativeDef ||
        (opts.regex &&
          !tests[tndx].match.static &&
          tests[tndx].match.fn.test(
            testDefinition.input,
            maskset,
            pos,
            false,
            opts
          )))
    ) {
      valid = true;
      break;
    } else if (
      tests[tndx].match &&
      tests[tndx].match.def === testDefinition.match.nativeDef
    ) {
      valid = undefined;
      break;
    }
  }
  if (valid === false) {
    if (maskset.jitOffset[pos] !== undefined) {
      valid = positionCanMatchDefinition.call(
        inputmask,
        pos + maskset.jitOffset[pos],
        testDefinition,
        opts
      );
    }
  }
  return valid;
}

// tobe put on prototype?
function refreshFromBuffer(start, end, buffer) {
  const inputmask = this,
    maskset = this.maskset,
    opts = this.opts,
    $ = this.dependencyLib;
  // checkVal.call(inputmask, el, false, true, isRTL ? buffer.reverse() : buffer);
  let i,
    p,
    skipOptionalPartCharacter = opts.skipOptionalPartCharacter,
    bffr = inputmask.isRTL ? buffer.slice().reverse() : buffer;
  opts.skipOptionalPartCharacter = "";
  if (start === true) {
    resetMaskSet.call(inputmask, false);
    start = 0;
    end = buffer.length;
    p = determineNewCaretPosition.call(
      inputmask,
      { begin: 0, end: 0 },
      false
    ).begin;
  } else {
    for (i = start; i < end; i++) {
      maskset.validPositions.splice(start, 0);
    }
    p = start;
  }

  const keypress = new $.Event("keypress");
  for (i = start; i < end; i++) {
    keypress.key = bffr[i].toString();
    inputmask.ignorable = false; // make sure ignorable is ignored ;-)
    const valResult = EventHandlers.keypressEvent.call(
      inputmask,
      keypress,
      true,
      false,
      false,
      p
    );
    if (valResult !== false && valResult !== undefined) {
      p = valResult.forwardPosition;
    }
  }

  opts.skipOptionalPartCharacter = skipOptionalPartCharacter;
}

// tobe put on prototype?
// fill in best positions according the current input
function trackbackPositions(originalPos, newPos, fillOnly) {
  const inputmask = this,
    maskset = this.maskset,
    $ = this.dependencyLib;

  // console.log("trackbackPositions " + originalPos + " " + newPos);
  if (originalPos === undefined) {
    // find previous valid
    for (originalPos = newPos - 1; originalPos > 0; originalPos--) {
      if (maskset.validPositions[originalPos]) break;
    }
  }
  for (let ps = originalPos; ps < newPos; ps++) {
    if (
      maskset.validPositions[ps] === undefined &&
      !isMask.call(inputmask, ps, false)
    ) {
      const vp =
        ps == 0 ? getTest.call(inputmask, ps) : maskset.validPositions[ps - 1];
      if (vp) {
        const tests = getTests.call(inputmask, ps).slice();
        if (tests[tests.length - 1].match.def === "") tests.pop();
        var bestMatch = determineTestTemplate.call(inputmask, ps, tests),
          np;
        if (
          bestMatch &&
          (bestMatch.match.jit !== true ||
            (bestMatch.match.newBlockMarker === "master" &&
              (np = maskset.validPositions[ps + 1]) &&
              np.match.optionalQuantifier === true))
        ) {
          bestMatch = $.extend({}, bestMatch, {
            input:
              getPlaceholder.call(inputmask, ps, bestMatch.match, true) ||
              bestMatch.match.def
          });
          bestMatch.generatedInput = true;
          revalidateMask.call(inputmask, ps, bestMatch, true);

          if (fillOnly !== true) {
            // revalidate the new position to update the locator value
            const cvpInput = maskset.validPositions[newPos].input;
            maskset.validPositions[newPos] = undefined;
            return isValid.call(inputmask, newPos, cvpInput, true, true);
          }
        }
      }
    }
  }
}

// tobe put on prototype?
function revalidateMask(pos, validTest, fromIsValid, validatedPos) {
  // console.log("revalidateMask " + fromIsValid);
  const inputmask = this,
    maskset = this.maskset,
    opts = this.opts,
    $ = this.dependencyLib;

  function IsEnclosedStatic(pos, valids, selection) {
    const posMatch = valids[pos];
    if (
      posMatch !== undefined &&
      posMatch.match.static === true &&
      posMatch.match.optionality !== true &&
      (valids[0] === undefined || valids[0].alternation === undefined)
    ) {
      const prevMatch =
          selection.begin <= pos - 1
            ? valids[pos - 1] &&
              valids[pos - 1].match.static === true &&
              valids[pos - 1]
            : valids[pos - 1],
        nextMatch =
          selection.end > pos + 1
            ? valids[pos + 1] &&
              valids[pos + 1].match.static === true &&
              valids[pos + 1]
            : valids[pos + 1];
      return prevMatch && nextMatch;
    }
    return false;
  }

  let offset = 0,
    begin = pos.begin !== undefined ? pos.begin : pos,
    end = pos.end !== undefined ? pos.end : pos,
    valid = true;
  if (pos.begin > pos.end) {
    begin = pos.end;
    end = pos.begin;
  }

  validatedPos = validatedPos !== undefined ? validatedPos : begin;
  if (
    fromIsValid === undefined &&
    (begin !== end ||
      (opts.insertMode && maskset.validPositions[validatedPos] !== undefined) ||
      validTest === undefined ||
      validTest.match.optionalQuantifier ||
      validTest.match.optionality)
  ) {
    // reposition & revalidate others
    let positionsClone = $.extend(true, [], maskset.validPositions),
      lvp = getLastValidPosition.call(inputmask, undefined, true),
      i;
    maskset.p = begin; // needed for alternated position after overtype selection

    const clearpos = isSelection.call(inputmask, pos) ? begin : validatedPos;
    for (i = lvp; i >= clearpos; i--) {
      maskset.validPositions.splice(i, 1);
      if (validTest === undefined) delete maskset.tests[i + 1];
    }

    let j = validatedPos,
      posMatch = j,
      t,
      canMatch,
      test;

    if (validTest) {
      maskset.validPositions[validatedPos] = $.extend(true, {}, validTest);
      posMatch++;
      j++;
    }

    if (positionsClone[end] == undefined && maskset.jitOffset[end]) {
      end += maskset.jitOffset[end] + 1;
    }
    for (i = validTest ? end : end - 1; i <= lvp; i++) {
      if (
        (t = positionsClone[i]) !== undefined &&
        t.generatedInput !== true &&
        (i >= end ||
          (i >= begin &&
            IsEnclosedStatic(i, positionsClone, {
              begin,
              end
            })))
      ) {
        while (
          ((test = getTest.call(inputmask, posMatch)), test.match.def !== "")
        ) {
          // loop needed to match further positions
          if (
            (canMatch = positionCanMatchDefinition.call(
              inputmask,
              posMatch,
              t,
              opts
            )) !== false ||
            t.match.def === "+"
          ) {
            // validated match //we still need some hackery for the + validator (numeric alias)
            if (t.match.def === "+") getBuffer.call(inputmask, true);
            const result = isValid.call(
              inputmask,
              posMatch,
              t.input,
              t.match.def !== "+",
              /* t.match.def !== "+" */ true
            );
            valid = result !== false;
            j = (result.pos || posMatch) + 1;
            if (!valid && canMatch) break;
          } else {
            valid = false;
          }
          if (valid) {
            if (validTest === undefined && t.match.static && i === pos.begin)
              offset++;
            break;
          }
          if (
            (!valid && getBuffer.call(inputmask), posMatch > maskset.maskLength)
          ) {
            break;
          }
          posMatch++;
        }
        if (getTest.call(inputmask, posMatch).match.def == "") {
          valid = false;
        }
        // restore position
        posMatch = j;
      }
      if (!valid) break;
    }
    if (!valid) {
      maskset.validPositions = $.extend(true, [], positionsClone);
      resetMaskSet.call(inputmask, true);
      return false;
    }
  } else if (
    validTest &&
    getTest.call(inputmask, validatedPos).match.cd === validTest.match.cd
  ) {
    maskset.validPositions[validatedPos] = $.extend(true, {}, validTest);
  }

  resetMaskSet.call(inputmask, true);
  return offset;
}
