(function (global) {
  var parse = function (data) {
    data = data.replace(/\n/, "");
    // replace spaces with regular space
    data = data.replace(/\s/g, " ");

    if (/^@/.test(data) === true) {
      return pdf417(data);
    } else if (/^%/.test(data) === true) {
      return stripe(data);
    } else {
      console.log("couldnt identify format");
    }
  };

  var parseDate = function (date) {
    var start = parseInt(date[0] + date[1]);
    if (start < 13) {
      return date[4] + date[5] + date[6] + date[7] + date[0] + date[1] + date[2] + date[3];
    }
    return date;
  };

  var stripe = function (data) {
    data = data.replace(/\n/, "");
    // replace spaces with regular space
    data = data.replace(/\s/g, " ");
    var track = data.match(/(.*?\?)(.*?\?)(.*?\?)/);
    var res1 = track[1].match(/(%)([A-Z]{2})([^^]{0,13})\^?([^^]{0,35})\^?([^^]{0,60})\^?\s*?\?/);
    var res2 = track[2].match(/(;)(\d{6})(\d{0,13})(=)(\d{4})(\d{8})(\d{0,5})=?\?/);
    var res3 = track[3].match(
      /(#|%|\+)(\d|!|")(\d|\s|.)([0-9A-Z ]{11})([0-9A-Z ]{2})([0-9A-Z ]{10})([0-9A-Z ]{4})([12MF ]{1})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})([0-9A-Z ]{3})(.*?)\?/
    );
    var state = res1[2];
    return {
      state: state,
      city: res1[3],
      name: (function () {
        var res = res1[4].match(/([^$]{0,35})\$?([^$]{0,35})?\$?([^$]{0,35})?/);
        if (!res) return;
        return {
          last: res[1],
          first: res[2],
          middle: res[3]
        };
      })(),
      address: res1[5],
      iso_iin: res2[2],
      dl: res2[3],
      expiration_date: parseDate(res2[5]),
      birthday: (function () {
        var dob = res2[6].match(/(\d{4})(\d{2})(\d{2})/);
        if (!dob) return;

        if (dob[2] === "99") {
          /* FL decided to reverse 2012 aamva spec, 99 means here
                                that dob month === to expiration month, it should be
                                opposite
                                */
          var exp_dt = res2[5].match(/(\d{2})(\d{2})/);
          dob[2] = exp_dt[2];
        }
        //dob[2]--; what was this for?
        return dob[1] + dob[2] + dob[3];
      })(),
      dl_overflow: res2[7],
      cds_version: res3[1],
      jurisdiction_version: res3[2],
      postal_code: res3[4],
      klass: res3[5],
      class: res3[5],
      restrictions: res3[6],
      endorsments: res3[7],
      sex: (function () {
        switch (res3[8]) {
          case "1":
          case "M":
            return "MALE";

          case "2":
          case "F":
            return "FEMALE";

          default:
            return res3[8] + ": MISSING/INVALID";
        }
      })(),
      height: res3[9],
      weight: res3[10],
      hair_color: res3[11],
      eye_color: res3[12],
      misc: res3[13],
      id: (function () {
        var id;
        switch (state) {
          case "FL":
            var res = res2[3].match(/(\d{2})(.*)/);
            if (!res) return;
            id = String.fromCharCode(Number(res[1]) + 64) + res[2] + res2[7];
            break;
          default:
            id = res2[3];
            break;
        }
        return id;
      })()
    };
  };

  var pdf417 = function (data) {
    data = data.replace(/\n/, "");
    // replace spaces with regular space
    data = data.replace(/\s/g, " ");

    // get version of aamva (before 2000 or after)
    var version = data.match(/[A-Z ]{5}\d{6}(\d{2})/);

    var parseRegex;

    /* version 01 year 2000 */
    switch (Number(version[1])) {
      case 1: {
        parseRegex = new RegExp(
          "(DAQ.*?)?" + // Drivers license number
            "(DAA.*?)?" + // Driver License Name
            "(DAG.*?)?" + // Driver Mailing Street Address
            "(DAI.*?)?" + // Driver Mailing City
            "(DAJ.*?)?" + // Driver Mailing Jurisdiction Code
            "(DAK.*?)?" + // Driver Mailing Postal Code
            "(DAQ.*?)?" + // Driver License/ID Number
            "(DAR.*?)?" + // Driver License Classification Code
            "(DAS.*?)?" + // Driver License Restriction Code
            "(DAT.*?)?" + // Driver License Endorsements Code
            "(DBA.*?)?" + // Driver License Expiration Date
            "(DBB.*?)?" + // Date of Birth
            "(DBC.*?)?" + // Driver Sex
            "(DBD.*?)?" + // Driver License or ID Document Issue Date
            /* optional
                          '(DAU.*?)?' + // Height (FT/IN)
                          '(DAW.*?)?' + // Weight (LBS)
                          '(DAY.*?)?' + // Eye Color
                          '(DAZ.*?)?' + // Hair Color
                          '(DBK.*?)?' + // Social Security Number
                          '(PAA.*?)?' + // Driver Permit Classification Code
                          '(PAB.*?)?' + // Driver Permit Expiration Date
                          '(PAC.*?)?' + // Permit Identifier
                          '(PAD.*?)?' + // Driver Permit Issue Date
                          '(PAE.*?)?' + // Driver Permit Restriction Code
                          '(PAF.*?)?' + // Driver Permit Endorsement Code
                          '(DAB.*?)?' + // Driver Last Name
                          '(DAC.*?)?' + // Driver First Name
                          '(DAD.*?)?' + // Driver Middle Name or Initial
                          '(DAE.*?)?' + // Driver Name Suffix
                          '(DAF.*?)?' + // Driver Name Prefix
                          '(DAH.*?)?' + // Driver Mailing Street Address 2
                          '(DAL.*?)?' + // Driver Residence Street Address 1
                          '(DAM.*?)?' + // Driver Residence Street Address 2
                          '(DAN.*?)?' + // Driver Residence City
                          '(DAO.*?)?' + // Driver Residence Jurisdiction Code
                          '(DAP.*?)?' + // Driver Residence Postal Code
                          '(DAV.*?)?' + // Height (CM)
                          '(DAX.*?)?' + // Weight (KG)
                          '(DBE.*?)?' + // Issue Timestamp
                          '(DBF.*?)?' + // Number of Duplicates
                          '(DBG.*?)?' + // Medical Indicator/Codes
                          '(DBH.*?)?' + // Organ Donor
                          '(DBI.*?)?' + // Non-Resident Indicator
                          '(DBJ.*?)?' + // Unique Customer Identifier
                          '(DBL.*?)?' + // Driver "AKA" Date Of Birth
                          '(DBM.*?)?' + // Driver "AKA" Social Security Number
                          '(DBN.*?)?' + // Driver "AKA" Name
                          '(DBO.*?)?' + // Driver "AKA" Last Name
                          '(DBP.*?)?' + // Driver "AKA" First Name
                          '(DBQ.*?)?' + // Driver "AKA" Middle Name
                          '(DBR.*?)?' + // Driver "AKA" Suffix
                          '(DBS.*?)?'   // Driver "AKA" Prefix
                          */
            "$"
        );
        break;
      }
      /* version 02 year 2003 */
      case 2: {
        parseRegex = new RegExp(
          "(DCA.*?)?" + // Jurisdiction-specific vehicle class
            "(DCB.*?)?" + // Jurisdiction-specific restriction codes
            "(DCD.*?)?" + // Jurisdiction-specific endorsement codes
            "(DBA.*?)?" + // Document Expiration Date
            "(DCS.*?)?" + // Customer Family Name
            "(DCT.*?)?" + // Customer Given Names
            "(DCU.*?)?" + // Name Suffix
            "(DBD.*?)?" + // Document Issue Date
            "(DBB.*?)?" + // Date of Birth
            "(DBC.*?)?" + // Physical Description – Sex
            "(DAY.*?)?" + // Physical Description – Eye Color
            "(DAU.*?)?" + // Physical Description – Height
            "(DCE.*?)?" + // Physical Description – Weight Range
            "(DAG.*?)?" + // Address – Street 1
            "(DAI.*?)?" + // Address – City
            "(DAJ.*?)?" + // Address – Jurisdiction Code
            "(DAK.*?)?" + // Address – Postal Code
            "(DAQ.*?)?" + // Customer ID Number
            "(DCF.*?)?" + // Document Discriminator
            "(DCG.*?)?" + // Country Identification
            "(DCH.*?)?" + // Federal Commercial Vehicle Codes
            /* optional elements
                          '(DAH.*?)?' + // Address – Street 2
                          '(DAZ.*?)?' + // Hair color
                          '(DCI.*?)?' + // Place of birth
                          '(DCJ.*?)?' + // Audit information
                          '(DCK.*?)?' + // Inventory control number
                          '(DBN.*?)?' + // Alias / AKA Family Name
                          '(DCL.*?)?' + // Race / ethnicity

                          '(DCM.*?)?' + // Standard vehicle classification
                          '(DCN.*?)?' + // Standard endorsement code
                          '(DCO.*?)?' + // Standard restriction code
                          '(DCP.*?)?' + // Jurisdiction- specific vehicle classification description
                          '(DCQ.*?)?' + // Jurisdiction- specific endorsement code description
                          '(DCR.*?)?'  // Jurisdiction- specific restriction code description
                          */
            "$"
        );
        break;
      }
      /* version 03 year 2005 */
      case 3: {
        parseRegex = new RegExp(
          "(DCA.*?)?" + // Jurisdiction-specific vehicle class
            "(DCB.*?)?" + // Jurisdiction-specific restriction codes
            "(DCD.*?)?" + // Jurisdiction-specific endorsement codes
            "(DBA.*?)?" + // Document Expiration Date
            "(DCS.*?)?" + // Customer Family Name
            "(DCT.*?)?" + // Customer Given Names
            "(DBD.*?)?" + // Document Issue Date
            "(DBB.*?)?" + // Date of Birth
            "(DBC.*?)?" + // Physical Description – Sex
            "(DAY.*?)?" + // Physical Description – Eye Color
            "(DAU.*?)?" + // Physical Description – Height
            "(DAG.*?)?" + // Address – Street 1
            "(DAI.*?)?" + // Address – City
            "(DAJ.*?)?" + // Address – Jurisdiction Code
            "(DAK.*?)?" + // Address – Postal Code
            "(DAQ.*?)?" + // Customer ID Number
            "(DCF.*?)?" + // Document Discriminator
            "(DCG.*?)?" + // Country Identification
            "(DCH.*?)?" + // Federal Commercial Vehicle Codes
            /* optional elements
                          + '(DAH.*?)?' + // Address – Street 2
                          '(DAZ.*?)?' + // Hair color
                          '(DCI.*?)?' + // Place of birth
                          '(DCJ.*?)?' + // Audit information
                          '(DCK.*?)?' + // Inventory control number
                          '(DBN.*?)?' + // Alias / AKA Family Name
                          '(DBG.*?)?' + // Alias / AKA Given Name
                          '(DBS.*?)?' + // Alias / AKA Suffix Name
                          '(DCU.*?)?' + // Name Suffix
                          '(DCE.*?)?' + // Physical Description – Weight Range
                          '(DCL.*?)?' + // Race / ethnicity
                          '(DCM.*?)?' + // Standard vehicle classification
                          '(DCN.*?)?' + // Standard endorsement code
                          '(DCO.*?)?' + // Standard restriction code
                          '(DCP.*?)?' + // Jurisdiction- specific vehicle classification description
                          '(DCQ.*?)?' + // Jurisdiction- specific endorsement code description
                          '(DCR.*?)?'  // Jurisdiction- specific restriction code description
                          */
            "$"
        );
        break;
      }
      case 6: {
        parseRegex = new RegExp(
          "(DAQ.*?)?" +
            "(DCS.*?)?" +
            "(DDE.*?)?" +
            "(DAC.*?)?" +
            "(DDF.*?)?" +
            "(DAD.*?)?" +
            "(DDG.*?)?" +
            "(DCA.*?)?" +
            "(DCB.*?)?" +
            "(DCD.*?)?" +
            "(DBD.*?)?" +
            "(DBB.*?)?" +
            "(DBA.*?)?" +
            "(DBC.*?)?" +
            "(DAU.*?)?" +
            "(DAY.*?)?" +
            "(DAG.*?)?" +
            "(DAI.*?)?" +
            "(DAJ.*?)?" +
            "(DAK.*?)?" +
            "(DCF.*?)?" +
            /* optional */
            "$"
        );
        break;
      }
      /* version 07 year 2012 */
      case 7: {
        parseRegex = new RegExp(
          "(DCA.*?)?" + // Jurisdiction-specific vehicle class
            "(DCB.*?)?" + // Jurisdiction-specific restriction codes
            "(DCD.*?)?" + // Jurisdiction-specific endorsement codes
            "(DBA.*?)?" + // Document Expiration Date
            "(DCS.*?)?" + // Customer Family Name
            "(DAC.*?)?" + // Customer First Name
            "(DAD.*?)?" + // Customer Middle Name(s)
            "(DBD.*?)?" + // Document Issue Date
            "(DBB.*?)?" + // Date of Birth
            "(DBC.*?)?" + // Physical Description – Sex
            "(DAY.*?)?" + // Physical Description – Eye Color
            "(DAU.*?)?" + // Physical Description – Height
            "(DAG.*?)?" + // Address – Street 1
            "(DAI.*?)?" + // Address – City
            "(DAJ.*?)?" + // Address – Jurisdiction Code
            "(DAK.*?)?" + // Address – Postal Code
            "(DAQ.*?)?" + // Customer ID Number
            "(DCF.*?)?" + // Document Discriminator
            "(DCG.*?)?" + // Country Identification
            "(DDE.*?)?" + // Family name truncation
            "(DDF.*?)?" + // First name truncation
            "(DDG.*?)?" + // Middle name truncation
            /* optional elements
                          '(DAH.*?)?' + // Address – Street 2
                          '(DAZ.*?)?' + // Hair color
                          '(DCI.*?)?' + // Place of birth
                          '(DCJ.*?)?' + // Audit information
                          '(DCK.*?)?' + // Inventory control number
                          '(DBN.*?)?' + // Alias / AKA Family Name
                          '(DBG.*?)?' + // Alias / AKA Given Name
                          '(DBS.*?)?' + // Alias / AKA Suffix Name
                          '(DCU.*?)?' + // Name Suffix
                          '(DCE.*?)?' + // Physical Description – Weight Range
                          '(DCL.*?)?' + // Race / ethnicity
                          '(DCM.*?)?' + // Standard vehicle classification
                          '(DCN.*?)?' + // Standard endorsement code
                          '(DCO.*?)?' + // Standard restriction code
                          '(DCP.*?)?' + // Jurisdiction- specific vehicle classification description
                          '(DCQ.*?)?' + // Jurisdiction- specific endorsement code description
                          '(DCR.*?)?' + // Jurisdiction- specific restriction code description
                          '(DDA.*?)?' + // Compliance Type
                          '(DDB.*?)?' + // Card Revision Date
                          '(DDC.*?)?' + // HAZMAT Endorsement Expiration Date
                          '(DDD.*?)?' + // Limited Duration Document Indicator
                          '(DAW.*?)?' + // Weight (pounds)
                          '(DAX.*?)?' + // Weight (kilograms)
                          '(DDH.*?)?' + // Under 18 Until
                          '(DDI.*?)?' + // Under 19 Until
                          '(DDJ.*?)?' + // Under 21 Until
                          '(DDK.*?)?' + // Organ Donor Indicator
                          '(DDL.*?)?'   // Veteran Indicator
                          */
            "$"
        );
        break;
      }
      case 8:
      case 9: {
        var prefixes = [
          "DCA", // jurisdiction vehicle class
          "DCB", // jurisdiction restriction codes
          "DCD", // jurisdiction endorsement codes
          "DBA", // doc. expiration date
          "DCS", // customer family name
          "DAC", // first name
          "DAD", // middle names (comma seperated)
          "DBD", // doc. issue date
          "DBB", // date of birth (MMDDCCYY for U.S., CCYYMMDD for Canada)
          "DBC", // gender (1-name, 2-female, 9-not specified)
          "DAY", // eye color (ansi d-20 codes)
          "DAU", // height
          "DAG", // street 1
          "DAI", // city
          "DAJ", // state
          "DAK", // zip
          "DAQ", // customer id number
          "DCF", // doc. distriminator
          "DCG", // country identification (USA/CAN)
          "DDE", // last name truncated (T-trucated, N-not, U-unknown)
          "DDF", // first name truncated (T-trucated, N-not, U-unknown)
          "DDG", // middle name truncated (T-trucated, N-not, U-unknown)
          // optionals
          "DAH", // street address line 2
          "DAZ", // hair color
          "DCI", // place of birth
          "DCJ", // audit info
          "DCK", // inventory control number
          "DBN", // alias last name
          "DBG", // alias first name
          "DBS", // aliast suffix name
          "DCU", // name suffix . (JR, SR, 1ST, 2ND...)
          "DCE", // weight range
          "DCL", // race / ethnicity (AAMVA D20 code)
          "DCM", // vehicle classification
          "DCN", // standard endorsement code
          "DCO", // standard restriction code
          "DCP", // vehicle classification description
          "DCQ", // endorsement code description
          "DCR", // restriction code description
          "DDA", // compliance type
          "DDB", // card revision date
          "DDC", // hazmat endorsement exp. date
          "DDD", // limited duration doc. indicator
          "DAW", // weight lbs
          "DAX", // weight kg
          "DDH", // under 18 until, date turns 18 (MMDDCCYY for U.S., CCYYMMDD for Canada)
          "DDI", // under 19 until, date turns 19 (MMDDCCYY for U.S., CCYYMMDD for Canada)
          "DDJ", // under 21 until, date turns 21 (MMDDCCYY for U.S., CCYYMMDD for Canada)
          "DDK", // organ donor (1-yes)
          "DDL" // veteran indicator (1-yes)
        ];
        var regExStr = "";
        var prefixIdxs = [];
        for (var i = 0; i < prefixes.length; i++) {
          var idx = data.indexOf(prefixes[i]);
          if (idx !== -1) {
            prefixIdxs.push({
              prefix: prefixes[i],
              index: idx
            });
          }
        }
        // if prefixes are not in order as found in the string, the regex will not perform as expected
        prefixIdxs.sort((a, b) => (a.index > b.index ? 1 : -1));
        prefixIdxs.forEach((obj) => (regExStr += `(${obj.prefix}.*?)?`));
        regExStr += "$";

        parseRegex = new RegExp(regExStr);
        break;
      }
      default: {
        console.log("unable to get version", version);
        // probably not a right parse...
      }
    }

    var parsedData = {};
    var res = data.match(parseRegex);

    for (i = 1; i < res.length; i++) {
      if (res[i] !== undefined) {
        parsedData[String(res[i]).substring(0, 3)] = res[i].substring(3).trim();
      }
    }

    var name;
    switch (Number(version[1])) {
      case 1: {
        // version one joining all of the names in one string
        name = parsedData.DAA.split(",");
        parsedData.DCS = name[0];
        parsedData.DAC = name[1];
        parsedData.DAD = name[2];

        // drivers license class
        parsedData.DCA = parsedData.DAR;

        // date on 01 is CCYYMMDD while on 07 MMDDCCYY
        parsedData.DBB =
          parsedData.DBB.substring(4, 6) + // month
          parsedData.DBB.substring(6, 8) + // day
          parsedData.DBB.substring(0, 4); // year
        break;
      }
      case 3: {
        // version 3 putting middle and first names in the same field
        name = parsedData.DCT.split(",");
        parsedData.DAC = name[0]; // first name
        parsedData.DAD = name[1]; // middle name
        break;
      }
      default: {
        console.log("no version matched");
        break;
      }
    }

    var rawData = {
      state: parsedData.DAJ,
      city: parsedData.DAI,
      name: (function () {
        return {
          last: parsedData.DCS,
          first: parsedData.DAC,
          middle: parsedData.DAD
        };
      })(),
      address: parsedData.DAG,
      iso_iin: undefined,
      dl: parsedData.DAQ,
      expiration_date: parseDate(parsedData.DBA),
      birthday: (function () {
        if (!parsedData.DBB) return;
        var match = parsedData.DBB.match(/(\d{2})(\d{2})(\d{4})/);
        if (!match) return;
        return match[3] + match[1] + match[2];
      })(),
      dl_overflow: undefined,
      cds_version: undefined,
      jurisdiction_version: undefined,
      postal_code: parsedData.DAK
        ? parsedData.DAK.match(/\d{-}\d+/)
          ? parsedData.DAK
          : parsedData.DAK.substring(0, 5)
        : undefined,
      klass: parsedData.DCA,
      class: parsedData.DCA,
      restrictions: undefined,
      endorsments: undefined,
      sex: (function () {
        switch (Number(parsedData.DBC)) {
          case 1:
            return "MALE";

          case 2:
            return "FEMALE";

          default:
            if (parsedData.DBC[0] === "M") {
              return "MALE";
            } else if (parsedData.DBC[0] === "F") {
              return "FEMALE";
            }
            return "MISSING/INVALID";
        }
      })(),
      height: undefined,
      weight: undefined,
      hair_color: undefined,
      eye_color: undefined,
      misc: undefined,
      id: (function () {
        if (!parsedData.DAQ) return;
        return parsedData.DAQ.replace(/[^A-ZA-Z0-9]/g, "");
      })()
    };

    return rawData;
  };

  global.parse = parse;
  global.stripe = stripe;
  global.pdf417 = pdf417;
})(this);
