#undef PTIME_TZPARSE_HEADERFUNC
#undef PTIME_TZPARSE_BODYFUNC
#undef PTIME_TZPARSE_TRANSTIME_TYPE
#undef PTIME_TZPARSE_LEAPSEC_TYPE
#undef PTIME_TZPARSE_LEAPSEC_SIZE
#undef PTIME_TZPARSE_NTOH_DYN

#ifdef PTIME_TZPARSE_V2
#  define PTIME_TZPARSE_HEADERFUNC     tzparse_headerV2
#  define PTIME_TZPARSE_BODYFUNC       tzparse_bodyV2
#  define PTIME_TZPARSE_TRANSTIME_TYPE ftz_transtimeV2
#  define PTIME_TZPARSE_LEAPSEC_TYPE   ftz_leapsecV2
#  define PTIME_TZPARSE_LEAPSEC_SIZE   ftz_leapsecV2_size
#  define PTIME_TZPARSE_NTOH_DYN(val)  ((int64_t)PTIME_BE64TOH(val))
#else
#  define PTIME_TZPARSE_HEADERFUNC     tzparse_headerV1
#  define PTIME_TZPARSE_BODYFUNC       tzparse_bodyV1
#  define PTIME_TZPARSE_TRANSTIME_TYPE ftz_transtimeV1
#  define PTIME_TZPARSE_LEAPSEC_TYPE   ftz_leapsecV1
#  define PTIME_TZPARSE_LEAPSEC_SIZE   ftz_leapsecV1_size
#  define PTIME_TZPARSE_NTOH_DYN(val)  ((int32_t)PTIME_BE32TOH(val))
#endif

#ifndef PTIME_TZPARSE_TRANSCMP
# define PTIME_TZPARSE_TRANSCMP
static int trans_cmp (const void* _a, const void* _b) {
    tztrans* a = (tztrans*) _a;
    tztrans* b = (tztrans*) _b;
    if (a->start < b->start) return -1;
    else if (a->start == b->start) return 0;
    else return 1;
}
#endif

static inline int PTIME_TZPARSE_HEADERFUNC (char** ptr, ftz_head& head, int* version) {
    memcpy(&head, *ptr, 44);
    *ptr += 44;
    
    if (strncmp(head.tzh_magic, FTZ_MAGIC, strlen(FTZ_MAGIC)-1) != 0) {
        //fprintf(stderr, "ptime: BAD FILE MAGIC\n", head.tzh_magic);
        return -1;
    }
    
    char tzh_version_str[2];
    tzh_version_str[0] = head.tzh_version[0];
    tzh_version_str[1] = '\0';
    *version = (int) strtol(tzh_version_str, NULL, 10);
    
    head.tzh_ttisgmtcnt = PTIME_BE32TOH(head.tzh_ttisgmtcnt);
    head.tzh_ttisstdcnt = PTIME_BE32TOH(head.tzh_ttisstdcnt);
    head.tzh_leapcnt    = PTIME_BE32TOH(head.tzh_leapcnt);
    head.tzh_timecnt    = PTIME_BE32TOH(head.tzh_timecnt);
    head.tzh_typecnt    = PTIME_BE32TOH(head.tzh_typecnt);
    head.tzh_charcnt    = PTIME_BE32TOH(head.tzh_charcnt);
    
    if (head.tzh_timecnt > FTZ_MAX_TIMES) {
        //fprintf(stderr, "ptime: tzh_timecnt %d is greater than max supported %d\n", head.tzh_timecnt, FTZ_MAX_TIMES);
        return -1;
    }
    
    if (head.tzh_typecnt > FTZ_MAX_TYPES) {
        //fprintf(stderr, "ptime: tzh_typecnt %d is greater than max supported %d\n", head.tzh_typecnt, FTZ_MAX_TYPES);
        return -1;
    }
    
    if (head.tzh_charcnt > FTZ_MAX_CHARS) {
        //fprintf(stderr, "ptime: tzh_charcnt %d is greater than max supported %d\n", head.tzh_charcnt, FTZ_MAX_CHARS);
        return -1;
    }
    
    if (head.tzh_leapcnt > FTZ_MAX_LEAPS) {
        //fprintf(stderr, "ptime: tzh_leapcnt %d is greater than max supported %d\n", head.tzh_leapcnt, FTZ_MAX_LEAPS);
        return -1;
    }

    int to_skip = head.tzh_timecnt*sizeof(PTIME_TZPARSE_TRANSTIME_TYPE) + // transition times
                  head.tzh_timecnt*sizeof(ftz_ilocaltype) +               // types of local time starting at above
                  head.tzh_typecnt*ftz_localtype_size +                   // local times
                  head.tzh_charcnt +                                      // abbrevs
                  head.tzh_leapcnt*PTIME_TZPARSE_LEAPSEC_SIZE +           // leap seconds
                  head.tzh_ttisstdcnt*sizeof(ftz_isstd) +
                  head.tzh_ttisgmtcnt*sizeof(ftz_isgmt);

    return to_skip;
}

static inline bool PTIME_TZPARSE_BODYFUNC (char* ptr, ftz_head& head, tz* zone) {
    PTIME_TZPARSE_TRANSTIME_TYPE* transitions = (PTIME_TZPARSE_TRANSTIME_TYPE*) ptr;
    ptr += head.tzh_timecnt * sizeof(PTIME_TZPARSE_TRANSTIME_TYPE);

    ftz_ilocaltype* ilocaltypes = (ftz_ilocaltype*) ptr;
    ptr += head.tzh_timecnt * sizeof(ftz_ilocaltype);

    ftz_localtype localtypes[FTZ_MAX_TYPES];
    for (int i = 0; i < head.tzh_typecnt; i++) {
        memcpy(&localtypes[i], ptr, ftz_localtype_size);
        localtypes[i].offset = PTIME_BE32TOH(localtypes[i].offset);
        ptr += ftz_localtype_size;
    }

    char* abbrevs = ptr;
    ptr += head.tzh_charcnt * sizeof(char);

    zone->leaps_cnt = head.tzh_leapcnt;
    //zone->leaps = zone->leaps_cnt > 0 ? (tzleap*) malloc(zone->leaps_cnt * sizeof(tzleap)) : NULL;
    zone->leaps = zone->leaps_cnt > 0 ? new tzleap[zone->leaps_cnt] : NULL;
    for (int i = 0; i < head.tzh_leapcnt; i++) {
        PTIME_TZPARSE_LEAPSEC_TYPE leapsec;
        bzero(&leapsec, sizeof(leapsec));
        memcpy(&leapsec, ptr, PTIME_TZPARSE_LEAPSEC_SIZE);
        zone->leaps[i].time       = (ptime_t) PTIME_TZPARSE_NTOH_DYN(leapsec.time);
        zone->leaps[i].correction = PTIME_BE32TOH(leapsec.correction);
        ptr += PTIME_TZPARSE_LEAPSEC_SIZE;
    }

    ftz_isstd* isstds = (ftz_isstd*) ptr;
    ptr += head.tzh_ttisstdcnt * sizeof(ftz_isstd);

    ftz_isgmt* isgmts = (ftz_isgmt*) ptr;
    ptr += head.tzh_ttisgmtcnt * sizeof(ftz_isgmt);
    
    // find past localtype - first localtype if it's not used in transitions, otherwise it's first std time localtype
    int past_lt_index = 0;
    for (int i = 0; i < head.tzh_timecnt; ++i) {
        if (ilocaltypes[i] != 0) continue;
        past_lt_index = -1;
        break;
    }
    if (past_lt_index < 0) for (int i = 0; i < head.tzh_typecnt; ++i) {
        if (localtypes[i].isdst) continue;
        past_lt_index = i;
        break;
    }
    if (past_lt_index < 0) past_lt_index = 0;
    
    zone->trans_cnt = head.tzh_timecnt + 1 + zone->leaps_cnt; // +1 for 'past'
    size_t trans_size = zone->trans_cnt * sizeof(tztrans);
    //zone->trans = (tztrans*) malloc(trans_size);
    zone->trans = new tztrans[trans_size];
    bzero(zone->trans, trans_size);
    
    zone->trans[0].start       = EPOCH_NEGINF;
    zone->trans[0].local_start = EPOCH_NEGINF;
    zone->trans[0].local_lower = EPOCH_NEGINF;
    zone->trans[0].local_upper = EPOCH_NEGINF;
    zone->trans[0].offset      = localtypes[past_lt_index].offset;
    zone->trans[0].gmt_offset  = localtypes[past_lt_index].offset;
    zone->trans[0].delta       = 0;
    zone->trans[0].isdst       = localtypes[past_lt_index].isdst;
    zone->trans[0].leap_corr   = 0;
    zone->trans[0].leap_delta  = 0;
    zone->trans[0].leap_end    = EPOCH_NEGINF;
    zone->trans[0].leap_lend   = EPOCH_NEGINF;
    char* past_abbrev          = abbrevs + localtypes[past_lt_index].abbrev_offset;
    if (strlen(past_abbrev) > ZONE_ABBR_MAX) {
        //fprintf(stderr, "ptime: past abbrev is too long (%d), max is %d\n", strlen(past_abbrev), ZONE_ABBR_MAX);
        tzfree(zone);
        return false;
    }
    strcpy(zone->trans[0].abbrev, past_abbrev);

    for (int i = 0; i < head.tzh_timecnt; ++i) {
        ftz_localtype localtype = localtypes[ilocaltypes[i]];
        tztrans* this_trans     = &zone->trans[i+1];
        this_trans->start       = (ptime_t) PTIME_TZPARSE_NTOH_DYN(transitions[i]);
        this_trans->gmt_offset  = localtype.offset;
        this_trans->isdst       = localtype.isdst;
        char* abbrev            = abbrevs + localtype.abbrev_offset;
        if (strlen(abbrev) > ZONE_ABBR_MAX) {
            //fprintf(stderr, "ptime: locatype's #%d abbrev is too long (%d), max is %d\n", ilocaltypes[i], strlen(abbrev), ZONE_ABBR_MAX);
            tzfree(zone);
            return false;
        }
        strcpy(this_trans->abbrev, abbrev);
    }
    
    for (int i = 0; i < zone->leaps_cnt; i++) {
        tztrans* this_trans = &zone->trans[head.tzh_timecnt+i+1];
        this_trans->start     = zone->leaps[i].time;
        this_trans->leap_corr = zone->leaps[i].correction;
    }
    
    qsort(zone->trans, zone->trans_cnt, sizeof(tztrans), trans_cmp);
    
    for (int i = 1; i < zone->trans_cnt; ++i) {
        tztrans* this_trans = &zone->trans[i];
        tztrans* prev_trans = &zone->trans[i-1];
        
        if (this_trans->leap_corr != 0) {
            this_trans->leap_delta = this_trans->leap_corr - prev_trans->leap_corr;
            this_trans->gmt_offset = prev_trans->gmt_offset;
            this_trans->isdst      = prev_trans->isdst;
            strcpy(this_trans->abbrev, prev_trans->abbrev);
        } else {
            this_trans->leap_delta = 0;
            this_trans->leap_corr  = prev_trans->leap_corr;
        }
        
        this_trans->offset      = this_trans->gmt_offset - this_trans->leap_corr;
        this_trans->delta       = this_trans->offset - prev_trans->offset;
        this_trans->local_start = this_trans->start + this_trans->offset;
        prev_trans->local_end   = this_trans->start + prev_trans->offset;
        this_trans->local_lower = this_trans->local_start;
        this_trans->local_upper = std::max(this_trans->local_start, prev_trans->local_end);
        this_trans->leap_end    = this_trans->start + this_trans->leap_delta;
        this_trans->leap_lend   = this_trans->local_start + 2*this_trans->leap_delta;
    }

    char* posixstr = ptr+1; // ptr+1 because POSIX rule begins and ends with '\n'
    char* posixend = strchr(posixstr, '\n');
    
#ifdef PTIME_TZPARSE_V2
    if (posixend == NULL) {
        //fprintf(stderr, "ptime: cannot locate terminating newline character for posix string\n");
        tzfree(zone);
        return false;
    }
    *posixend = '\0';
#else
    posixend = posixstr;
#endif

    zone->ltrans = zone->trans[zone->trans_cnt-1];

    if (posixend - posixstr == 0) { // no posix string, using last transition
        zone->future.hasdst           = 0;
        zone->future.outer.gmt_offset = zone->ltrans.gmt_offset;
        zone->future.outer.isdst      = zone->ltrans.isdst;
        strcpy(zone->future.outer.abbrev, zone->ltrans.abbrev);
    }
    else if (!tzparse_rule(posixstr, &zone->future)) {
        //fprintf(stderr, "ptime: tzparse_rule failed\n");
        tzfree(zone);
        return false;
    }

    zone->future.outer.offset = zone->future.outer.gmt_offset - zone->ltrans.leap_corr;
    if (zone->future.hasdst) {
        zone->future.inner.offset = zone->future.inner.gmt_offset - zone->ltrans.leap_corr;
        zone->future.delta        = zone->future.inner.offset - zone->future.outer.offset;
        zone->future.max_offset   = std::max(zone->future.outer.offset, zone->future.inner.offset);
    }
    
    return true;
}
