#include "country.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

// ISO 3166-1 alpha-2 codes, sorted ascending by `code` for bsearch.
// `name` is for a future name-lookup feature; `method` is the best-fit
// calculation method per country (CALC_MWL where none is dedicated).
static const Country ISO_3166_1_ALPHA2[] = {
    {"AD", "Andorra", CALC_MWL},
    {"AE", "United Arab Emirates", CALC_DUBAI},
    {"AF", "Afghanistan", CALC_KARACHI},
    {"AG", "Antigua and Barbuda", CALC_MWL},
    {"AI", "Anguilla", CALC_MWL},
    {"AL", "Albania", CALC_MWL},
    {"AM", "Armenia", CALC_MWL},
    {"AO", "Angola", CALC_MWL},
    {"AQ", "Antarctica", CALC_MWL},
    {"AR", "Argentina", CALC_MWL},
    {"AS", "American Samoa", CALC_MWL},
    {"AT", "Austria", CALC_MWL},
    {"AU", "Australia", CALC_MWL},
    {"AW", "Aruba", CALC_MWL},
    {"AX", "Åland Islands", CALC_MWL},
    {"AZ", "Azerbaijan", CALC_MWL},
    {"BA", "Bosnia and Herzegovina", CALC_MWL},
    {"BB", "Barbados", CALC_MWL},
    {"BD", "Bangladesh", CALC_KARACHI},
    {"BE", "Belgium", CALC_MWL},
    {"BF", "Burkina Faso", CALC_MWL},
    {"BG", "Bulgaria", CALC_MWL},
    {"BH", "Bahrain", CALC_GULF},
    {"BI", "Burundi", CALC_MWL},
    {"BJ", "Benin", CALC_MWL},
    {"BL", "Saint Barthélemy", CALC_MWL},
    {"BM", "Bermuda", CALC_MWL},
    {"BN", "Brunei Darussalam", CALC_JAKIM},
    {"BO", "Bolivia, Plurinational State of", CALC_MWL},
    {"BQ", "Bonaire, Sint Eustatius and Saba", CALC_MWL},
    {"BR", "Brazil", CALC_MWL},
    {"BS", "Bahamas", CALC_MWL},
    {"BT", "Bhutan", CALC_MWL},
    {"BV", "Bouvet Island", CALC_MWL},
    {"BW", "Botswana", CALC_MWL},
    {"BY", "Belarus", CALC_MWL},
    {"BZ", "Belize", CALC_MWL},
    {"CA", "Canada", CALC_ISNA},
    {"CC", "Cocos (Keeling) Islands", CALC_MWL},
    {"CD", "Congo, The Democratic Republic of the", CALC_MWL},
    {"CF", "Central African Republic", CALC_MWL},
    {"CG", "Congo", CALC_MWL},
    {"CH", "Switzerland", CALC_MWL},
    {"CI", "Côte d'Ivoire", CALC_MWL},
    {"CK", "Cook Islands", CALC_MWL},
    {"CL", "Chile", CALC_MWL},
    {"CM", "Cameroon", CALC_MWL},
    {"CN", "China", CALC_MWL},
    {"CO", "Colombia", CALC_MWL},
    {"CR", "Costa Rica", CALC_MWL},
    {"CU", "Cuba", CALC_MWL},
    {"CV", "Cabo Verde", CALC_MWL},
    {"CW", "Curaçao", CALC_MWL},
    {"CX", "Christmas Island", CALC_MWL},
    {"CY", "Cyprus", CALC_MWL},
    {"CZ", "Czechia", CALC_MWL},
    {"DE", "Germany", CALC_MWL},
    {"DJ", "Djibouti", CALC_MWL},
    {"DK", "Denmark", CALC_MWL},
    {"DM", "Dominica", CALC_MWL},
    {"DO", "Dominican Republic", CALC_MWL},
    {"DZ", "Algeria", CALC_ALGERIA},
    {"EC", "Ecuador", CALC_MWL},
    {"EE", "Estonia", CALC_MWL},
    {"EG", "Egypt", CALC_EGYPT},
    {"EH", "Western Sahara", CALC_MWL},
    {"ER", "Eritrea", CALC_MWL},
    {"ES", "Spain", CALC_MWL},
    {"ET", "Ethiopia", CALC_MWL},
    {"FI", "Finland", CALC_MWL},
    {"FJ", "Fiji", CALC_MWL},
    {"FK", "Falkland Islands (Malvinas)", CALC_MWL},
    {"FM", "Micronesia, Federated States of", CALC_MWL},
    {"FO", "Faroe Islands", CALC_MWL},
    {"FR", "France", CALC_FRANCE},
    {"GA", "Gabon", CALC_MWL},
    {"GB", "United Kingdom", CALC_MWL},
    {"GD", "Grenada", CALC_MWL},
    {"GE", "Georgia", CALC_MWL},
    {"GF", "French Guiana", CALC_MWL},
    {"GG", "Guernsey", CALC_MWL},
    {"GH", "Ghana", CALC_MWL},
    {"GI", "Gibraltar", CALC_MWL},
    {"GL", "Greenland", CALC_MWL},
    {"GM", "Gambia", CALC_MWL},
    {"GN", "Guinea", CALC_MWL},
    {"GP", "Guadeloupe", CALC_MWL},
    {"GQ", "Equatorial Guinea", CALC_MWL},
    {"GR", "Greece", CALC_MWL},
    {"GS", "South Georgia and the South Sandwich Islands", CALC_MWL},
    {"GT", "Guatemala", CALC_MWL},
    {"GU", "Guam", CALC_MWL},
    {"GW", "Guinea-Bissau", CALC_MWL},
    {"GY", "Guyana", CALC_MWL},
    {"HK", "Hong Kong", CALC_MWL},
    {"HM", "Heard Island and McDonald Islands", CALC_MWL},
    {"HN", "Honduras", CALC_MWL},
    {"HR", "Croatia", CALC_MWL},
    {"HT", "Haiti", CALC_MWL},
    {"HU", "Hungary", CALC_MWL},
    {"ID", "Indonesia", CALC_KEMENAG},
    {"IE", "Ireland", CALC_MWL},
    {"IL", "Israel", CALC_MWL},
    {"IM", "Isle of Man", CALC_MWL},
    {"IN", "India", CALC_KARACHI},
    {"IO", "British Indian Ocean Territory", CALC_MWL},
    {"IQ", "Iraq", CALC_MWL},
    {"IR", "Iran, Islamic Republic of", CALC_MWL},
    {"IS", "Iceland", CALC_MWL},
    {"IT", "Italy", CALC_MWL},
    {"JE", "Jersey", CALC_MWL},
    {"JM", "Jamaica", CALC_MWL},
    {"JO", "Jordan", CALC_JORDAN},
    {"JP", "Japan", CALC_MWL},
    {"KE", "Kenya", CALC_MWL},
    {"KG", "Kyrgyzstan", CALC_MWL},
    {"KH", "Cambodia", CALC_MWL},
    {"KI", "Kiribati", CALC_MWL},
    {"KM", "Comoros", CALC_MWL},
    {"KN", "Saint Kitts and Nevis", CALC_MWL},
    {"KP", "Korea, Democratic People's Republic of", CALC_MWL},
    {"KR", "Korea, Republic of", CALC_MWL},
    {"KW", "Kuwait", CALC_KUWAIT},
    {"KY", "Cayman Islands", CALC_MWL},
    {"KZ", "Kazakhstan", CALC_MWL},
    {"LA", "Lao People's Democratic Republic", CALC_MWL},
    {"LB", "Lebanon", CALC_MWL},
    {"LC", "Saint Lucia", CALC_MWL},
    {"LI", "Liechtenstein", CALC_MWL},
    {"LK", "Sri Lanka", CALC_MWL},
    {"LR", "Liberia", CALC_MWL},
    {"LS", "Lesotho", CALC_MWL},
    {"LT", "Lithuania", CALC_MWL},
    {"LU", "Luxembourg", CALC_MWL},
    {"LV", "Latvia", CALC_MWL},
    {"LY", "Libya", CALC_EGYPT},
    {"MA", "Morocco", CALC_MOROCCO},
    {"MC", "Monaco", CALC_MWL},
    {"MD", "Moldova, Republic of", CALC_MWL},
    {"ME", "Montenegro", CALC_MWL},
    {"MF", "Saint Martin (French part)", CALC_MWL},
    {"MG", "Madagascar", CALC_MWL},
    {"MH", "Marshall Islands", CALC_MWL},
    {"MK", "North Macedonia", CALC_MWL},
    {"ML", "Mali", CALC_MWL},
    {"MM", "Myanmar", CALC_MWL},
    {"MN", "Mongolia", CALC_MWL},
    {"MO", "Macao", CALC_MWL},
    {"MP", "Northern Mariana Islands", CALC_MWL},
    {"MQ", "Martinique", CALC_MWL},
    {"MR", "Mauritania", CALC_MWL},
    {"MS", "Montserrat", CALC_MWL},
    {"MT", "Malta", CALC_MWL},
    {"MU", "Mauritius", CALC_MWL},
    {"MV", "Maldives", CALC_MWL},
    {"MW", "Malawi", CALC_MWL},
    {"MX", "Mexico", CALC_MWL},
    {"MY", "Malaysia", CALC_JAKIM},
    {"MZ", "Mozambique", CALC_MWL},
    {"NA", "Namibia", CALC_MWL},
    {"NC", "New Caledonia", CALC_MWL},
    {"NE", "Niger", CALC_MWL},
    {"NF", "Norfolk Island", CALC_MWL},
    {"NG", "Nigeria", CALC_MWL},
    {"NI", "Nicaragua", CALC_MWL},
    {"NL", "Netherlands", CALC_MWL},
    {"NO", "Norway", CALC_MWL},
    {"NP", "Nepal", CALC_MWL},
    {"NR", "Nauru", CALC_MWL},
    {"NU", "Niue", CALC_MWL},
    {"NZ", "New Zealand", CALC_MWL},
    {"OM", "Oman", CALC_GULF},
    {"PA", "Panama", CALC_MWL},
    {"PE", "Peru", CALC_MWL},
    {"PF", "French Polynesia", CALC_MWL},
    {"PG", "Papua New Guinea", CALC_MWL},
    {"PH", "Philippines", CALC_MWL},
    {"PK", "Pakistan", CALC_KARACHI},
    {"PL", "Poland", CALC_MWL},
    {"PM", "Saint Pierre and Miquelon", CALC_MWL},
    {"PN", "Pitcairn", CALC_MWL},
    {"PR", "Puerto Rico", CALC_MWL},
    {"PS", "Palestine, State of", CALC_JORDAN},
    {"PT", "Portugal", CALC_PORTUGAL},
    {"PW", "Palau", CALC_MWL},
    {"PY", "Paraguay", CALC_MWL},
    {"QA", "Qatar", CALC_QATAR},
    {"RE", "Réunion", CALC_MWL},
    {"RO", "Romania", CALC_MWL},
    {"RS", "Serbia", CALC_MWL},
    {"RU", "Russian Federation", CALC_RUSSIA},
    {"RW", "Rwanda", CALC_MWL},
    {"SA", "Saudi Arabia", CALC_MAKKAH},
    {"SB", "Solomon Islands", CALC_MWL},
    {"SC", "Seychelles", CALC_MWL},
    {"SD", "Sudan", CALC_EGYPT},
    {"SE", "Sweden", CALC_MWL},
    {"SG", "Singapore", CALC_SINGAPORE},
    {"SH", "Saint Helena, Ascension and Tristan da Cunha", CALC_MWL},
    {"SI", "Slovenia", CALC_MWL},
    {"SJ", "Svalbard and Jan Mayen", CALC_MWL},
    {"SK", "Slovakia", CALC_MWL},
    {"SL", "Sierra Leone", CALC_MWL},
    {"SM", "San Marino", CALC_MWL},
    {"SN", "Senegal", CALC_MWL},
    {"SO", "Somalia", CALC_MWL},
    {"SR", "Suriname", CALC_MWL},
    {"SS", "South Sudan", CALC_MWL},
    {"ST", "Sao Tome and Principe", CALC_MWL},
    {"SV", "El Salvador", CALC_MWL},
    {"SX", "Sint Maarten (Dutch part)", CALC_MWL},
    {"SY", "Syrian Arab Republic", CALC_MWL},
    {"SZ", "Eswatini", CALC_MWL},
    {"TC", "Turks and Caicos Islands", CALC_MWL},
    {"TD", "Chad", CALC_MWL},
    {"TF", "French Southern Territories", CALC_MWL},
    {"TG", "Togo", CALC_MWL},
    {"TH", "Thailand", CALC_MWL},
    {"TJ", "Tajikistan", CALC_MWL},
    {"TK", "Tokelau", CALC_MWL},
    {"TL", "Timor-Leste", CALC_MWL},
    {"TM", "Turkmenistan", CALC_MWL},
    {"TN", "Tunisia", CALC_TUNISIA},
    {"TO", "Tonga", CALC_MWL},
    {"TR", "Türkiye", CALC_TURKEY},
    {"TT", "Trinidad and Tobago", CALC_MWL},
    {"TV", "Tuvalu", CALC_MWL},
    {"TW", "Taiwan, Province of China", CALC_MWL},
    {"TZ", "Tanzania, United Republic of", CALC_MWL},
    {"UA", "Ukraine", CALC_MWL},
    {"UG", "Uganda", CALC_MWL},
    {"UM", "United States Minor Outlying Islands", CALC_MWL},
    {"US", "United States", CALC_ISNA},
    {"UY", "Uruguay", CALC_MWL},
    {"UZ", "Uzbekistan", CALC_MWL},
    {"VA", "Holy See (Vatican City State)", CALC_MWL},
    {"VC", "Saint Vincent and the Grenadines", CALC_MWL},
    {"VE", "Venezuela, Bolivarian Republic of", CALC_MWL},
    {"VG", "Virgin Islands, British", CALC_MWL},
    {"VI", "Virgin Islands, U.S.", CALC_MWL},
    {"VN", "Viet Nam", CALC_MWL},
    {"VU", "Vanuatu", CALC_MWL},
    {"WF", "Wallis and Futuna", CALC_MWL},
    {"WS", "Samoa", CALC_MWL},
    {"YE", "Yemen", CALC_MAKKAH},
    {"YT", "Mayotte", CALC_MWL},
    {"ZA", "South Africa", CALC_MWL},
    {"ZM", "Zambia", CALC_MWL},
    {"ZW", "Zimbabwe", CALC_MWL},
};

#define ISO_3166_1_ALPHA2_COUNT (sizeof(ISO_3166_1_ALPHA2) / sizeof(ISO_3166_1_ALPHA2[0]))

// bsearch comparator: `key` points to a 2-char uppercase NUL-terminated string,
// `elem` points to a Country. Compares against the entry's code.
static int country_code_cmp(const void *key, const void *elem) {
  const char *code = (const char *)key;
  const Country *entry = (const Country *)elem;
  return strcmp(code, entry->code);
}

bool country_is_valid_alpha2(const char *code) {
  if (!code)
    return false;

  // Must be exactly two ASCII letters.
  if (!isalpha((unsigned char)code[0]) || !isalpha((unsigned char)code[1]) || code[2] != '\0')
    return false;

  char key[3];
  key[0] = (char)toupper((unsigned char)code[0]);
  key[1] = (char)toupper((unsigned char)code[1]);
  key[2] = '\0';

  return bsearch(key, ISO_3166_1_ALPHA2, ISO_3166_1_ALPHA2_COUNT, sizeof(ISO_3166_1_ALPHA2[0]),
                 country_code_cmp) != NULL;
}

const Country *country_table(size_t *count) {
  if (count)
    *count = ISO_3166_1_ALPHA2_COUNT;
  return ISO_3166_1_ALPHA2;
}

CalcMethod country_default_method(const char *code) {
  if (!code || !isalpha((unsigned char)code[0]) || !isalpha((unsigned char)code[1]) ||
      code[2] != '\0')
    return CALC_MWL;

  char key[3];
  key[0] = (char)toupper((unsigned char)code[0]);
  key[1] = (char)toupper((unsigned char)code[1]);
  key[2] = '\0';

  const Country *hit = bsearch(key, ISO_3166_1_ALPHA2, ISO_3166_1_ALPHA2_COUNT,
                               sizeof(ISO_3166_1_ALPHA2[0]), country_code_cmp);
  return hit ? hit->method : CALC_MWL;
}
