/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Adaptor.hh"
#include "Timezone.hh"
#include "wrap/gtest-wrapper.h"

#include <iostream>
#include <vector>

namespace orc {

  bool isLeap(int64_t year);

  TEST(TestTimezone, isLeap) {
    EXPECT_TRUE(isLeap(2000));
    EXPECT_FALSE(isLeap(2001));
    EXPECT_TRUE(isLeap(2004));
    EXPECT_FALSE(isLeap(2100));
    EXPECT_FALSE(isLeap(2200));
    EXPECT_FALSE(isLeap(2300));
    EXPECT_TRUE(isLeap(2400));
  }

  int64_t binarySearch(const std::vector<int64_t>& array, int64_t target);

  TEST(TestTimezone, testBinarySearch) {
    std::vector<int64_t> vect;
    EXPECT_EQ(-1, binarySearch(vect, 0));
    vect.push_back(0);
    EXPECT_EQ(-1, binarySearch(vect, -5));
    EXPECT_EQ(0, binarySearch(vect, 0));
    EXPECT_EQ(0, binarySearch(vect, 5));
    vect.push_back(2);
    EXPECT_EQ(-1, binarySearch(vect, -1));
    EXPECT_EQ(0, binarySearch(vect, 0));
    EXPECT_EQ(0, binarySearch(vect, 1));
    EXPECT_EQ(1, binarySearch(vect, 2));
    EXPECT_EQ(1, binarySearch(vect, 3));
  }

  /**
   * Parse a future rule string and return the parsed rule as a string.
   */
  std::string stringifyRule(const std::string& ruleString) {
    std::shared_ptr<FutureRule> rule = parseFutureRule(ruleString);
    std::stringstream buffer;
    rule->print(buffer);
    return buffer.str();
  }

  TEST(TestTimezone, parseFutureRule) {
    EXPECT_EQ("  Future rule: FOO0\n  standard FOO 0\n", stringifyRule("FOO0"));
    EXPECT_EQ("  Future rule: <FOO+->010:02\n  standard <FOO+-> -36120\n",
              stringifyRule("<FOO+->010:02"));
    // unclosed '<'
    EXPECT_THROW(stringifyRule("<FOO12"), TimezoneError);
    // empty name
    EXPECT_THROW(stringifyRule("+8"), TimezoneError);
    // missing offset
    EXPECT_THROW(stringifyRule("FOO"), TimezoneError);
    EXPECT_EQ("  Future rule: FOO-123:45:67\n  standard FOO 445567\n",
              stringifyRule("FOO-123:45:67"));
    EXPECT_EQ("  Future rule: FOO+8\n  standard FOO -28800\n", stringifyRule("FOO+8"));
    EXPECT_EQ(("  Future rule: FOO8BAR,J10,20\n  standard FOO -28800\n"
               "  dst BAR -25200 (dst)\n  start julian 10 at 2:0:0\n"
               "  end day 20 at 2:0:0\n"),
              stringifyRule("FOO8BAR,J10,20"));
    EXPECT_EQ(("  Future rule: FOO+8BAR-0:30,M3.1.0,M10.5.6\n"
               "  standard FOO -28800\n"
               "  dst BAR 1800 (dst)\n"
               "  start month 3 week 1 day 0 at 2:0:0\n"
               "  end month 10 week 5 day 6 at 2:0:0\n"),
              stringifyRule("FOO+8BAR-0:30,M3.1.0,M10.5.6"));
    EXPECT_EQ(("  Future rule: FOO10BAR1,3,4\n"
               "  standard FOO -36000\n"
               "  dst BAR -3600 (dst)\n"
               "  start day 3 at 2:0:0\n"
               "  end day 4 at 2:0:0\n"),
              stringifyRule("FOO10BAR1,3,4"));
    // missing transition times
    EXPECT_THROW(stringifyRule("FOO8BAR"), TimezoneError);
    // check left over text
    EXPECT_THROW(stringifyRule("FOO8BAR,10,20x"), TimezoneError);
    EXPECT_EQ(("  Future rule: FOO8BAR,3/3,4/4:30\n"
               "  standard FOO -28800\n"
               "  dst BAR -25200 (dst)\n"
               "  start day 3 at 3:0:0\n"
               "  end day 4 at 4:30:0\n"),
              stringifyRule("FOO8BAR,3/3,4/4:30"));
    EXPECT_EQ(("  Future rule: FOO-8BAR,J3/3,M4.5.6/00:04:0007\n"
               "  standard FOO 28800\n"
               "  dst BAR 32400 (dst)\n"
               "  start julian 3 at 3:0:0\n"
               "  end month 4 week 5 day 6 at 0:4:7\n"),
              stringifyRule("FOO-8BAR,J3/3,M4.5.6/00:04:0007"));
    // too many fields in offset
    EXPECT_THROW(stringifyRule("FOO8BAR,10,20/4:30:20:10"), TimezoneError);

    EXPECT_FALSE(parseFutureRule("")->isDefined());
    EXPECT_TRUE(parseFutureRule("FOO12")->isDefined());
  }

  const std::string& getZoneFromRule(FutureRule* rule, const std::string& date) {
    tm timeStruct;
    if (strptime(date.c_str(), "%Y-%m-%d %H:%M:%S", &timeStruct) == nullptr) {
      throw TimezoneError("bad time " + date);
    }
    return rule->getVariant(timegm(&timeStruct)).name;
  }

  TEST(TestTimezone, useFutureRule) {
    std::shared_ptr<FutureRule> rule = parseFutureRule("FOO8BAR,M3.2.0,M11.1.0");
    // 1970
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-01-01 00:00:00"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-03-08 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-03-08 10:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-04-01 00:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-11-01 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-11-01 09:00:00"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-12-31 00:00:00"));

    // 2369
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "2369-01-01 00:00:00"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "2369-03-09 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "2369-03-09 10:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "2369-11-02 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "2369-11-02 09:00:00"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "2369-12-31 00:00:00"));

    // 2370
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "2370-01-01 00:00:00"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "2370-03-08 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "2370-03-08 10:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "2370-04-01 00:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "2370-11-01 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "2370-11-01 09:00:00"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "2370-12-31 00:00:00"));

    rule = parseFutureRule("FOO8BAR,J10,J360");
    // 1970
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-01-11 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-01-11 10:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-12-27 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-12-27 09:00:00"));
    // 1972
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1972-01-11 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1972-01-11 10:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1972-12-27 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1972-12-27 09:00:00"));

    rule = parseFutureRule("FOO8BAR,10,360");
#ifdef HAS_PRE_1970
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1969-01-11 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1969-01-11 10:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1969-12-27 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1969-12-27 09:00:00"));
#endif
    // 1970
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-01-11 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-01-11 10:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-12-27 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-12-27 09:00:00"));
    // 1972
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1972-01-11 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1972-01-11 10:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1972-12-26 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1972-12-26 09:00:00"));

    // test a southern hemisphere timezone
    rule = parseFutureRule("FOO8BAR,360,10");
    // 1970
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-01-11 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-01-11 09:00:00"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-12-27 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-12-27 10:00:00"));
    // 1972
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1972-01-11 08:59:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1972-01-11 09:00:00"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1972-12-26 09:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1972-12-26 10:00:00"));

    rule = parseFutureRule("FOO8BAR,J10/3,J360/3:30");
    // 1970
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-01-11 10:59:59"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-01-11 11:00:00"));
    EXPECT_EQ("BAR", getZoneFromRule(rule.get(), "1970-12-27 10:29:59"));
    EXPECT_EQ("FOO", getZoneFromRule(rule.get(), "1970-12-27 10:30:00"));
  }

  unsigned char decodeBase64Char(unsigned char ch) {
    switch (ch >> 4) {
      case 4:
      case 5:
        return static_cast<unsigned char>(ch - 'A');
      case 6:
      case 7:
        return static_cast<unsigned char>(ch - 'a' + 26);
      case 3:
        return static_cast<unsigned char>(ch - '0' + 52);
      case 2:
        return static_cast<unsigned char>(ch == '+' ? 62 : 63);
      default:
        return 255;
    }
  }

  std::vector<unsigned char> decodeBase64(const std::string& input) {
    std::vector<unsigned char> result;
    result.reserve(3 * (input.size() / 4));
    uint32_t accumulator = 0;
    uint32_t bits = 0;
    for (uint32_t c = 0; c < input.size() && input[c] != '='; ++c) {
      accumulator = (accumulator << 6) | decodeBase64Char(static_cast<unsigned char>(input[c]));
      bits += 6;
      if (bits >= 8) {
        bits -= 8;
        result.push_back(static_cast<unsigned char>(accumulator >> bits));
        accumulator &= ~(0xffffffff << bits);
      }
    }
    return result;
  }

  static const char* LA_VER1 =
      ("VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAC5AAAABAAAABCepkig"
       "n7sVkKCGKqChmveQy4kaoNIj9HDSYSYQ1v50INiArZDa/tGg28CQENzes6DdqayQ3r6Vo"
       "N+JjpDgnneg4WlwkOJ+WaDjSVKQ5F47oOUpNJDmR1gg5xJREOgnOiDo8jMQ6gccIOrSFR"
       "Dr5v4g7LH3EO3G4CDukdkQ76/8oPBxuxDxj96g8n/BkPNvwKD0X6OQ9U+ioPY/hZD3L4S"
       "g+CiiEPkPZqD6CIQQ+viDIPvoZhD82GUg/chIEP64RyD/qCoQAJgpIAGIDBACeAsgA3Eo"
       "kARhJ6AFUQqQBkEJoAcw7JAHjUOgCRDOkAmtvyAK8LCQC+CvoAzZzRANwJGgDrmvEA+pr"
       "iAQmZEQEYmQIBJ5cxATaXIgFFlVEBVJVCAWOTcQFyk2IBgiU5AZCRggGgI1kBryNKAb4h"
       "eQHNIWoB3B+ZAesfigH6HbkCB2KyAhgb2QIlYNICNq2hAkNe8gJUq8ECYV0SAnKp4QJ/7"
       "toCkKgBAp3s+gKupiECu+saAs036QLZ6ToC6zYJAvfnWgMJNCkDFnkiAycySQM0d0IDRT"
       "BpA1J1YgNjLokDcHOCA4HAUQOOcaIDn75xA6xvwgO9vJEDywGKA9u6sQPo/6oD+bjRBAb"
       "9ygQYSpkEJPvqBDZIuQRC+goEVEbZBF89MgRy2KEEfTtSBJDWwQSbOXIErtThBLnLOgTN"
       "ZqkE18laBOtkyQT1x3oFCWLpBRPFmgUnYQkFMcO6BUVfKQVPwdoFY11JBW5TogWB7xEFj"
       "FHCBZ/tMQWqT+IFvetRBchOAgXb6XEF5kwiBfnnkQYE3eoGGHlZBiLcCgY2d3kGQNoqBl"
       "R1mQZe2EoGcnO5BnzWagaQcdkGmtSKBq5v+Qa5ZlIGzQHBBtdkcgbq/+EG9WKSBwj+AQc"
       "TYLIHJvwhBzFe0gdE+kEHT/CaB2OMCQdt7roHgYopB4vs2gefiEkHqer6B72GaQfH6RoH"
       "24SJB+XnOgf5gqkAABAAECAwEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQA"
       "BAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA"
       "AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABA"
       "AEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB//+dkAEA//+PgA"
       "AE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAAB");

  static const char* LA_VER2 =
      ("VFppZjIAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAC6AAAABQAAABSAAAAAnqZIoJ"
       "+7FZCghiqgoZr3kMuJGqDSI/Rw0mEmENb+dCDYgK2Q2v7RoNvAkBDc3rOg3amskN6+laD"
       "fiY6Q4J53oOFpcJDiflmg40lSkOReO6DlKTSQ5kdYIOcSURDoJzog6PIzEOoHHCDq0hUQ"
       "6+b+IOyx9xDtxuAg7pHZEO+v/KDwcbsQ8Y/eoPJ/wZDzb8Cg9F+jkPVPoqD2P4WQ9y+Eo"
       "PgoohD5D2ag+giEEPr4gyD76GYQ/NhlIP3ISBD+uEcg/6gqEACYKSABiAwQAngLIANxKJ"
       "AEYSegBVEKkAZBCaAHMOyQB41DoAkQzpAJrb8gCvCwkAvgr6AM2c0QDcCRoA65rxAPqa4"
       "gEJmREBGJkCASeXMQE2lyIBRZVRAVSVQgFjk3EBcpNiAYIlOQGQkYIBoCNZAa8jSgG+IX"
       "kBzSFqAdwfmQHrH4oB+h25AgdisgIYG9kCJWDSAjatoQJDXvICVKvBAmFdEgJyqeECf+7"
       "aApCoAQKd7PoCrqYhArvrGgLNN+kC2ek6Aus2CQL351oDCTQpAxZ5IgMnMkkDNHdCA0Uw"
       "aQNSdWIDYy6JA3BzggOBwFEDjnGiA5++cQOsb8IDvbyRA8sBigPburED6P+qA/m40QQG/"
       "coEGEqZBCT76gQ2SLkEQvoKBFRG2QRfPTIEctihBH07UgSQ1sEEmzlyBK7U4QS5yzoEzW"
       "apBNfJWgTrZMkE9cd6BQli6QUTxZoFJ2EJBTHDugVFXykFT8HaBWNdSQVuU6IFge8RBYx"
       "RwgWf7TEFqk/iBb3rUQXITgIF2+lxBeZMIgX555EGBN3qBhh5WQYi3AoGNnd5BkDaKgZU"
       "dZkGXthKBnJzuQZ81moGkHHZBprUigaub/kGuWZSBs0BwQbXZHIG6v/hBvVikgcI/gEHE"
       "2CyByb8IQcxXtIHRPpBB0/wmgdjjAkHbe66B4GKKQeL7NoHn4hJB6nq+ge9hmkHx+kaB9"
       "uEiQfl5zoH+YKpACAQIBAgMEAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECA"
       "QIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAg"
       "ECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQI"
       "BAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQL//5EmAAD//52Q"
       "AQT//4+AAAj//52QAQz//52QARBMTVQAUERUAFBTVABQV1QAUFBUAAAAAAABAAAAAAFUW"
       "mlmMgAAAAAAAAAAAAAAAAAAAAAAAAUAAAAFAAAAAAAAALsAAAAFAAAAFPgAAAAAAAAA//"
       "///14EGsD/////nqZIoP////+fuxWQ/////6CGKqD/////oZr3kP/////LiRqg/////9I"
       "j9HD/////0mEmEP/////W/nQg/////9iArZD/////2v7RoP/////bwJAQ/////9zes6D/"
       "////3amskP/////evpWg/////9+JjpD/////4J53oP/////haXCQ/////+J+WaD/////4"
       "0lSkP/////kXjug/////+UpNJD/////5kdYIP/////nElEQ/////+gnOiD/////6PIzEP"
       "/////qBxwg/////+rSFRD/////6+b+IP/////ssfcQ/////+3G4CD/////7pHZEP/////"
       "vr/yg//////BxuxD/////8Y/eoP/////yf8GQ//////NvwKD/////9F+jkP/////1T6Kg"
       "//////Y/hZD/////9y+EoP/////4KKIQ//////kPZqD/////+giEEP/////6+IMg/////"
       "/voZhD//////NhlIP/////9yEgQ//////64RyD//////6gqEAAAAAAAmCkgAAAAAAGIDB"
       "AAAAAAAngLIAAAAAADcSiQAAAAAARhJ6AAAAAABVEKkAAAAAAGQQmgAAAAAAcw7JAAAAA"
       "AB41DoAAAAAAJEM6QAAAAAAmtvyAAAAAACvCwkAAAAAAL4K+gAAAAAAzZzRAAAAAADcCR"
       "oAAAAAAOua8QAAAAAA+priAAAAAAEJmREAAAAAARiZAgAAAAABJ5cxAAAAAAE2lyIAAAA"
       "AAUWVUQAAAAABVJVCAAAAAAFjk3EAAAAAAXKTYgAAAAABgiU5AAAAAAGQkYIAAAAAAaAj"
       "WQAAAAABryNKAAAAAAG+IXkAAAAAAc0hagAAAAAB3B+ZAAAAAAHrH4oAAAAAAfoduQAAA"
       "AACB2KyAAAAAAIYG9kAAAAAAiVg0gAAAAACNq2hAAAAAAJDXvIAAAAAAlSrwQAAAAACYV"
       "0SAAAAAAJyqeEAAAAAAn/u2gAAAAACkKgBAAAAAAKd7PoAAAAAAq6mIQAAAAACu+saAAA"
       "AAALNN+kAAAAAAtnpOgAAAAAC6zYJAAAAAAL351oAAAAAAwk0KQAAAAADFnkiAAAAAAMn"
       "MkkAAAAAAzR3QgAAAAADRTBpAAAAAANSdWIAAAAAA2MuiQAAAAADcHOCAAAAAAOBwFEAA"
       "AAAA45xogAAAAADn75xAAAAAAOsb8IAAAAAA728kQAAAAADywGKAAAAAAPburEAAAAAA+"
       "j/qgAAAAAD+bjRAAAAAAQG/coAAAAABBhKmQAAAAAEJPvqAAAAAAQ2SLkAAAAABEL6CgA"
       "AAAAEVEbZAAAAAARfPTIAAAAABHLYoQAAAAAEfTtSAAAAAASQ1sEAAAAABJs5cgAAAAAE"
       "rtThAAAAAAS5yzoAAAAABM1mqQAAAAAE18laAAAAAATrZMkAAAAABPXHegAAAAAFCWLpA"
       "AAAAAUTxZoAAAAABSdhCQAAAAAFMcO6AAAAAAVFXykAAAAABU/B2gAAAAAFY11JAAAAAA"
       "VuU6IAAAAABYHvEQAAAAAFjFHCAAAAAAWf7TEAAAAABapP4gAAAAAFvetRAAAAAAXITgI"
       "AAAAABdvpcQAAAAAF5kwiAAAAAAX555EAAAAABgTd6gAAAAAGGHlZAAAAAAYi3AoAAAAA"
       "BjZ3eQAAAAAGQNoqAAAAAAZUdZkAAAAABl7YSgAAAAAGcnO5AAAAAAZ81moAAAAABpBx2"
       "QAAAAAGmtSKAAAAAAaub/kAAAAABrlmUgAAAAAGzQHBAAAAAAbXZHIAAAAABur/4QAAAA"
       "AG9WKSAAAAAAcI/gEAAAAABxNgsgAAAAAHJvwhAAAAAAcxXtIAAAAAB0T6QQAAAAAHT/C"
       "aAAAAAAdjjAkAAAAAB23uugAAAAAHgYopAAAAAAeL7NoAAAAAB5+ISQAAAAAHqer6AAAA"
       "AAe9hmkAAAAAB8fpGgAAAAAH24SJAAAAAAfl5zoAAAAAB/mCqQAAIBAgECAwQCAQIBAgE"
       "CAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIB"
       "AgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECA"
       "QIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAgECAQIBAg"
       "ECAQIBAgECAQIBAgECAQIBAv//kSYAAP//nZABBP//j4AACP//nZABDP//nZABEExNVAB"
       "QRFQAUFNUAFBXVABQUFQAAAAAAAEAAAAAAQpQU1Q4UERULE0zLjIuMCxNMTEuMS4wCg==");

  const std::string& getVariantFromZone(const Timezone& zone, const std::string& date) {
    tm timeStruct;
    if (strptime(date.c_str(), "%Y-%m-%d %H:%M:%S", &timeStruct) == nullptr) {
      throw TimezoneError("bad time " + date);
    }
    return zone.getVariant(timegm(&timeStruct)).name;
  }

  TEST(TestTimezone, testParser) {
    std::unique_ptr<Timezone> la = getTimezone("America/Los_Angeles", decodeBase64(LA_VER1));
    EXPECT_EQ(1, la->getVersion());
    EXPECT_EQ("PST", getVariantFromZone(*la, "1974-01-06 09:59:59"));
    EXPECT_EQ("PDT", getVariantFromZone(*la, "1974-01-06 10:00:00"));
    // v1 won't have information past 2038
    EXPECT_EQ("PST", getVariantFromZone(*la, "2100-03-14 09:59:59"));
    EXPECT_EQ("PST", getVariantFromZone(*la, "2100-03-14 10:00:00"));

    la = getTimezone("America/Los_Angeles", decodeBase64(LA_VER2));
    EXPECT_EQ(2, la->getVersion());
    EXPECT_EQ("PST", getVariantFromZone(*la, "1974-01-06 09:59:59"));
    EXPECT_EQ("PDT", getVariantFromZone(*la, "1974-01-06 10:00:00"));
    EXPECT_EQ("PST", getVariantFromZone(*la, "2100-03-14 09:59:59"));
    EXPECT_EQ("PDT", getVariantFromZone(*la, "2100-03-14 10:00:00"));
  }

  TEST(TestTimezone, testZoneCache) {
    const Timezone* la1 = &getTimezoneByName("America/Los_Angeles");
    const Timezone* ny1 = &getTimezoneByName("America/New_York");
    const Timezone* la2 = &getTimezoneByName("America/Los_Angeles");
    const Timezone* ny2 = &getTimezoneByName("America/New_York");
    EXPECT_EQ(la1, la2);
    EXPECT_EQ(ny1, ny2);
    EXPECT_EQ("PST", getVariantFromZone(*la1, "1974-01-06 09:59:59"));
    EXPECT_EQ("PDT", getVariantFromZone(*la1, "1974-01-06 10:00:00"));
    EXPECT_EQ("PDT", getVariantFromZone(*la1, "1974-10-27 08:59:59"));
    EXPECT_EQ("PST", getVariantFromZone(*la1, "1974-10-27 09:00:00"));
    EXPECT_EQ("EST", getVariantFromZone(*ny1, "1974-01-06 06:59:59"));
    EXPECT_EQ("EDT", getVariantFromZone(*ny1, "1974-01-06 07:00:00"));
    EXPECT_EQ("EDT", getVariantFromZone(*ny1, "1974-10-27 05:59:59"));
    EXPECT_EQ("EST", getVariantFromZone(*ny1, "1974-10-27 06:00:00"));
  }

  TEST(TestTimezone, testGMTv1) {
    const char GMT[] =
        ("VFppZgAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAA"
         "AAAAAAAAAAAQAAAAQAAAAAAABHTVQAAAA=");
    std::unique_ptr<Timezone> gmt = getTimezone("GMT", decodeBase64(GMT));
    EXPECT_EQ(1, gmt->getVersion());
    EXPECT_EQ("GMT", getVariantFromZone(*gmt, "1974-01-06 09:59:59"));
    EXPECT_EQ("GMT", getVariantFromZone(*gmt, "2015-06-06 12:34:56"));
  }

  TEST(TestTimezone, testConvertFromUtc) {
    const Timezone* la = &getTimezoneByName("America/Los_Angeles");
    const Timezone* ny = &getTimezoneByName("America/New_York");
    const Timezone* gmt = &getTimezoneByName("GMT");
    const Timezone* sh = &getTimezoneByName("Asia/Shanghai");
    // 2023-05-29 22:20:00 UTC
    EXPECT_EQ(1685398800 + 7 * 3600, la->convertFromUTC(1685398800));  // it's PDT
    EXPECT_EQ(1685398800 + 4 * 3600, ny->convertFromUTC(1685398800));  // it's EDT
    EXPECT_EQ(1685398800, gmt->convertFromUTC(1685398800));
    EXPECT_EQ(1685398800 - 8 * 3600, sh->convertFromUTC(1685398800));  // no DST in China

    // DST starts in Los Angeles March 12, 2:00 am
    // 2023-03-12 03:00:00 UTC
    EXPECT_EQ(1678590000 + 7 * 3600, la->convertFromUTC(1678590000));
    // 2023-03-12 01:59:59 UTC
    EXPECT_EQ(1678590000 + 7 * 3600 - 1, la->convertFromUTC(1678586399));

    // DST starts in New York March 12, 2:00 am
    // 2023-03-12 03:00:00 UTC
    EXPECT_EQ(1678590000 + 4 * 3600, ny->convertFromUTC(1678590000));
    // 2023-03-12 01:59:59 UTC
    EXPECT_EQ(1678590000 + 4 * 3600 - 1, ny->convertFromUTC(1678586399));

    // no DST in China
    EXPECT_EQ(1678590000 - 8 * 3600, sh->convertFromUTC(1678590000));
    EXPECT_EQ(1678586399 - 8 * 3600, sh->convertFromUTC(1678586399));

    // DST ends in New York November 5, 2:00 am
    // 2023-11-05 06:00:00 UTC
    EXPECT_EQ(1699164000 + 5 * 3600, ny->convertFromUTC(1699164000));
    // DST ends in Los Angeles November 5, 2:00 am
    EXPECT_EQ(1699164000 + 8 * 3600, la->convertFromUTC(1699164000));
  }
}  // namespace orc
