meta {
    title: "Charge validation rules";
    version: "1.3_2024-09-02";
    description: "Rules to validate the charge and charge:conditional keys.";
    author: "Famlam";
    min-josm-version: "6534";
    link: "https://josm.openstreetmap.de/wiki/Rules/ChargeRules";
}



/* Currency symbols (e.g. €) instead of codes (EUR) */
*[charge:conditional=~/\p{Sc}/],
*[charge=~/\p{Sc}/] {
  throwWarning: tr("Expected 3-character currency code in {0}, found {1} instead", "{0.key}", get(regexp_match("^.*(\\p{Sc}).*$", tag("{0.key}")), 1));
  group: tr("Invalid value of charge");
  assertMatch: "node charge=€12";
  assertMatch: "node charge:conditional=\"0.22 $/liter @ (Mo-Fr)\"";
  assertNoMatch: "node charge=\"0.223 USD/m³\"";
  set .hasSimpleChargeFix;
  -osmoseItemClassLevel: "3091/30916:1/3";
}

/* Currency code before the value instead of after */
*[charge:conditional=~/^[A-Z]{3} ?[0-9]/]!.hasSimpleChargeFix,
*[charge=~/^[A-Z]{3} ?[0-9]/]!.hasSimpleChargeFix {
  throwWarning: tr("The value should be before the currency code {0} in {1}", substring(tag("{0.key}"), 0, 3), "{0.key}");
  group: tr("Invalid value of charge");
  /* For the positive assertions, first remove !.hasSimpleChargeFix from the rule; don't forget to restore */
  /*
  assertMatch: "node charge=EUR12.45";
  assertMatch: "node charge=\"EUR 12\"";
  */
  assertNoMatch: "node charge=\"0.223 USD/m³\"";
  set .hasSimpleChargeFix;
  -osmoseItemClassLevel: "3091/30916:2/3";
}

/* Missing mandatory space between value and currency code */
*[charge:conditional=~/^[0-9]+(?:\.[0-9]+)?[A-Z]{3}/]!.hasSimpleChargeFix,
*[charge=~/^[0-9]+(?:\.[0-9]+)?[A-Z]{3}/]!.hasSimpleChargeFix {
  throwWarning: tr("The value and the currency symbol in {0} should be separated by a space", "{0.key}");
  group: tr("Invalid value of charge");
  /* For the positive assertions, first remove !.hasSimpleChargeFix from the rule; don't forget to restore */
  /*
  assertMatch: "node charge=12EUR";
  */
  assertNoMatch: "node charge=\"12 EUR\"";
  assertNoMatch: "node charge=\"0.223 USD/m³\"";
  set .hasSimpleChargeFix;
  -osmoseItemClassLevel: "3091/30916:3/3";
}

/* No currency, just a value */
/* Regexes differ, charge:conditional has "( ?@| ?\/)", charge has "($| ?\/)" at the end */
*[charge:conditional=~/^[0-9]+(?:\.[0-9]+)?( ?@| ?\/)/]!.hasSimpleChargeFix,
*[charge=~/^[0-9]+(?:\.[0-9]+)?($| ?\/)/][charge!=0]!.hasSimpleChargeFix {
  throwWarning: tr("Currency not specified in {0}", "{0.key}");
  group: tr("Invalid value of charge");
  /* For the positive assertions, first remove !.hasSimpleChargeFix from the rule; don't forget to restore */
  /*
  assertMatch: "node charge=12";
  assertMatch: "node charge:conditional=\"12.45/liter @ (Mo-Fr)\"";
  */
  assertNoMatch: "node charge=\"0.223 USD/m³\"";
  assertNoMatch: "node charge=0";
  set .hasSimpleChargeFix;
  -osmoseItemClassLevel: "3091/30916:4/3";
}

/* yes/no instead of the actual fee */
/* Regexes differ, charge:conditional has " ?@", charge has "$" at the end */
*[charge:conditional][charge:conditional=~/^(yes|no) ?@/],
*[charge][charge=~/^(yes|no)$/] {
  throwWarning: tr("Key {0} should contain the amount charged", "{0.key}");
  group: tr("Invalid value of charge");
  suggestAlternative: "fee={0.value}";
  suggestAlternative: "toll={0.value}";
  suggestAlternative: "fee:conditional={0.value} @ ...";
  suggestAlternative: "toll:conditional={0.value} @ ...";
  set .hasSimpleChargeFix;
  -osmoseItemClassLevel: "3091/30916:5/3";
}

/* Comma as decimal separator */
*[charge:conditional=~/(^|\/ ?|; ?)[0-9]+,[0-9]/]!.hasSimpleChargeFix,
*[charge=~/(^|\/ ?|; ?)[0-9]+,[0-9]/]!.hasSimpleChargeFix {
  throwWarning: tr("unusual value of {0}: use . instead of , as decimal separator", "{0.key}");
  group: tr("Invalid value of charge");
  /* For the positive assertions, first remove !.hasSimpleChargeFix from the rule; don't forget to restore */
  /*
  assertMatch: "node charge=\"0,223 USD/30.4 m³\"";
  assertMatch: "node charge=\"0.223 USD/30,4 m³\"";
  assertMatch: "node charge=\"0.223 USD;30,4 USD\"";
  */
  assertNoMatch: "node charge=\"0.223 USD/30.4 m³\"";
  set .hasSimpleChargeFix;
  -osmoseItemClassLevel: "3091/30916:6/3";
}

/* Currency (likely) written out or wrong abbreviation used */
*[charge:conditional=~/^([0-9]+(?:\.[0-9]+)? ?[A-Za-z _-]+|[A-Za-z _-]+ ?[0-9]+(?:\.[0-9]+)?)/][charge:conditional!~/^[0-9]+(?:\.[0-9]+)? ?[A-Z]{3}/]!.hasSimpleChargeFix,
*[charge=~/^([0-9]+(?:\.[0-9]+)? ?[A-Za-z _-]+|[A-Za-z _-]+ ?[0-9]+(?:\.[0-9]+)?)/][charge!~/^[0-9]+(?:\.[0-9]+)? ?[A-Z]{3}/]!.hasSimpleChargeFix {
  throwWarning: tr("Invalid currency code in {0}, should be a 3-letter (uppercase) code after the value", "{0.key}");
  group: tr("Invalid value of charge");
  /* For the positive assertions, first remove !.hasSimpleChargeFix from the rule; don't forget to restore */
  /*
  assertMatch: "node charge=\"12 euro\"";
  assertMatch: "node charge=\"12 US dollar\"";
  assertMatch: "node charge=\"E12.95\"";
  assertMatch: "node charge=\"12.95E\"";
  assertMatch: "node charge=\"usdollar 12.95\"";
  assertMatch: "node charge=\"US Dollar 12.95\"";
  assertMatch: "node charge:conditional=\"12.45E @ (Mo-Fr)\"";
  */
  assertNoMatch: "node charge=\"0.223 USD/m³\"";
  assertNoMatch: "node charge=\"0.223USD\"";
  assertNoMatch: "node charge:conditional=\"0.223 USD @ (Mo-Fr)\"";
  set .hasSimpleChargeFix;
  -osmoseItemClassLevel: "3091/30916:7/3";
}

/* Abbreviated time unit */
/* Regexes differ, charge:conditional has "^[^@]+" and " ?@.", charge has "" and "$" at the start and end respectively */
*[charge:conditional=~/^[^@]+\/ ?(?:[0-9]+(?:\.[0-9]+)? ?)?(min|s|h|d|w) ?@./]!.hasSimpleChargeFix,
*[charge=~/\/ ?(?:[0-9]+(?:\.[0-9]+)? ?)?(min|s|h|d|w)$/]!.hasSimpleChargeFix {
  throwWarning: tr("Abbreviated time unit in {0}", "{0.key}");
  group: tr("Invalid value of charge");
  /* For the positive assertions, first remove !.hasSimpleChargeFix from the rule; don't forget to restore */
  /*
  assertMatch: "node charge=\"12 EUR/h\"";
  assertMatch: "node charge:conditional=\"12 EUR/h @ Sa\"";
  assertMatch: "node charge=\"12 EUR/0.5 h\"";
  */
  assertNoMatch: "node charge=\"0.22 USD/liter/hour\"";
  assertNoMatch: "node charge=\"12.223 YEN/12.4 m³/30.1 minutes\"";
  set .hasSimpleChargeFix;
  -osmoseItemClassLevel: "3091/30916:8/3";
}

/* Missing space between value and unit in the optional unit parts */
/* Regexes differ, charge:conditional has "^[^@]+" and "( ?@| ?\/).", charge has "" and "($| ?\/)" at the start and end respectively */
*[charge:conditional=~/^[^@]+\/ ?[0-9]+(?:\.[0-9]+)?[a-zA-Z³]+( ?@| ?\/)./]!.hasSimpleChargeFix,
*[charge=~/\/ ?[0-9]+(?:\.[0-9]+)?[a-zA-Z³]+($| ?\/)/]!.hasSimpleChargeFix {
  throwWarning: tr("No space between value and unit in {0}", "{0.key}");
  group: tr("Invalid value of charge");
  /* For the positive assertions, first remove !.hasSimpleChargeFix from the rule; don't forget to restore */
  /*
  assertMatch: "node charge=\"12 EUR/0.5hour\"";
  assertMatch: "node charge=\"12 EUR/3kWh/0.5 hour\"";
  assertMatch: "node charge=\"12 EUR/3kWh/0.5hour\"";
  assertMatch: "node charge=\"12 EUR/3 kWh/2hours\"";
  assertMatch: "node charge=\"12 EUR/3kWh/hour\"";
  assertMatch: "node charge=\"12 EUR/kWh/0.5hour\"";
  assertMatch: "node charge:conditional=\"12.5 EUR/0.5hour @ Sa\"";
  */
  assertNoMatch: "node charge=\"0.22 USD/liter/hour\"";
  assertNoMatch: "node charge=\"12.223 YEN/12.4 m³/30.1 minutes\"";
  set .hasSimpleChargeFix;
  -osmoseItemClassLevel: "3091/30916:9/3";
}

/* For charge:conditional, only validate the first condition. The @-condition may contain any character (esp. in fallback conditions).*/
/* The regexes are the same until the "(?:; ?(?!$)|$))+$" vs " ?@)."-part at the end */
*[charge:conditional][charge:conditional!~/^(?:[0-9]+(?:\.[0-9]+)? [A-Z]{3}(?: ?\/ ?(?:[0-9]+(?:\.[0-9]+)? )?[a-zA-Z³]+)?(?: ?\/ ?(?:[0-9]+(?:\.[0-9]+)? )?[a-z]{3,})? ?@)./]!.hasSimpleChargeFix,
*[charge][charge!=0][charge!~/^(?:[0-9]+(?:\.[0-9]+)? [A-Z]{3}(?: ?\/ ?(?:[0-9]+(?:\.[0-9]+)? )?[a-zA-Z³]+)?(?: ?\/ ?(?:[0-9]+(?:\.[0-9]+)? )?[a-z]{3,})?(?:; ?(?!$)|$))+$/]!.hasSimpleChargeFix {
  throwWarning: tr("The charge in {0} should be structured as <(decimal) number><space><(uppercase) three letter currency code>[/optional unit][/optional time unit]", "{0.key}");
  group: tr("Invalid value of charge");
  /* For the positive assertions, first remove !.hasSimpleChargeFix from the rule; don't forget to restore */
  /*
  assertMatch: "node charge=€12";
  assertMatch: "node charge=\"0.22 $/liter\"";
  assertMatch: "node charge=12.22";
  assertMatch: "node charge=\"12 EURO\"";
  assertMatch: "node charge=\"12 eur\"";
  assertMatch: "node charge=12EUR";
  assertMatch: "node charge=EUR12";
  assertMatch: "node charge:conditional=\"EUR12 @ (Fr-Su)\"";
  assertMatch: "node charge=\"12 EUR/5\"";
  assertMatch: "node charge=\"12 EUR;\"";
  */
  assertNoMatch: "node charge=0"; /* Zero is zero regardless of the unit, ignore */
  assertNoMatch: "node charge=\"12 EUR\"";
  assertNoMatch: "node charge=\"12 EUR/person; 6 EUR/child\"";
  assertNoMatch: "node charge=\"0.223 USD/liter\"";
  assertNoMatch: "node charge=\"0.22 USD/liter/hour\"";
  assertNoMatch: "node charge=\"0.22 USD / liter / hour\"";
  assertNoMatch: "node charge=\"12.223 YEN/1 person/1 hour\"";
  assertNoMatch: "node charge=\"12.223 YEN/12.4 m³/30.1 minutes\"";
  assertNoMatch: "node charge:conditional=\"12.223 YEN/12.4 m³/30.1 minutes @ Fr-Su\"";
  assertNoMatch: "node charge=\"12.223 YEN/100 kWh/day\"";
  -osmoseItemClassLevel: "3091/30916:999/3";
}
