package com.alibaba.fastjson2.benchmark;

import com.alibaba.fastjson2.util.DateUtils;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class DateParse {
    static final String[] dates = {
            "2001-07-01 01:02:03", "2011-06-02 09:02:03",
            "2021-11-03 02:02:03", "2001-11-14 07:02:03",
            "2002-10-07 16:02:03", "2003-09-12 15:02:03",
            "2006-08-16 02:02:03", "2002-01-30 22:02:03",
            "2009-02-27 01:02:03", "2011-04-26 11:02:03",
            "2012-06-23 01:02:03", "2022-02-18 09:02:03"
    };

    static ThreadLocal<SimpleDateFormat> formatThreadLocal = ThreadLocal.withInitial(
            () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
    );
    static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault();
    static final ZoneId SHANGHAI_ZONE_ID = "Asia/Shanghai".equals(DEFAULT_ZONE_ID.getId()) ? DEFAULT_ZONE_ID : ZoneId.of("Asia/Shanghai");
    static final ZoneOffset SHANGHAI_ZONE_OFFSET = SHANGHAI_ZONE_ID
            .getRules()
            .getOffset(
                    LocalDateTime.of(
                            LocalDate.of(1992, 1, 1), LocalTime.MIN
                    )
            );

    @Benchmark
    public void simpleDateFormat(Blackhole bh) throws Exception {
        for (int i = 0; i < dates.length; i++) {
            String str = dates[i];
            SimpleDateFormat format = formatThreadLocal.get();
            bh.consume(
                    format.parse(str)
            );
        }
    }

    @Benchmark
    public void dateTimeFormatter(Blackhole bh) throws Exception {
        for (int i = 0; i < dates.length; i++) {
            String str = dates[i];
            LocalDateTime ldt = LocalDateTime.parse(str, formatter);
            ZoneOffset offset = DEFAULT_ZONE_ID.getRules().getOffset(ldt);
            long millis = ldt.toInstant(offset).toEpochMilli();
            bh.consume(new Date(millis));
        }
    }

    @Benchmark
    public void localDateTimeParse(Blackhole bh) throws Exception {
        for (int i = 0; i < dates.length; i++) {
            String str = dates[i];
            LocalDateTime ldt = LocalDateTime.parse(str, formatter);
            ZoneOffset offset;
            if (DEFAULT_ZONE_ID == SHANGHAI_ZONE_ID && ldt.getYear() >= 1992) {
                offset = SHANGHAI_ZONE_OFFSET;
            } else {
                offset = DEFAULT_ZONE_ID.getRules().getOffset(ldt);
            }
            long millis = ldt.toInstant(offset).toEpochMilli();
            bh.consume(new Date(millis));
        }
    }

    @Benchmark
    public void parseYYYYMMDDHHMMSS19(Blackhole bh) throws Exception {
        for (int i = 0; i < dates.length; i++) {
            String str = dates[i];
            bh.consume(parseYYYYMMDDHHMMSS19(str));
        }
    }

    public static Date parseYYYYMMDDHHMMSS19(String str) {
        if (str == null || str.length() != 19) {
            throw new DateTimeParseException("not support input ", str, 0);
        }

        char c0 = str.charAt(0);
        char c1 = str.charAt(1);
        char c2 = str.charAt(2);
        char c3 = str.charAt(3);
        char c4 = str.charAt(4);
        char c5 = str.charAt(5);
        char c6 = str.charAt(6);
        char c7 = str.charAt(7);
        char c8 = str.charAt(8);
        char c9 = str.charAt(9);
        char c10 = str.charAt(10);
        char c11 = str.charAt(11);
        char c12 = str.charAt(12);
        char c13 = str.charAt(13);
        char c14 = str.charAt(14);
        char c15 = str.charAt(15);
        char c16 = str.charAt(16);
        char c17 = str.charAt(17);
        char c18 = str.charAt(18);

        char y0, y1, y2, y3, m0, m1, d0, d1, h0, h1, i0, i1, s0, s1;
        if (c4 == '-' && c7 == '-' && c10 == ' ' && c13 == ':' && c16 == ':') {
            y0 = c0;
            y1 = c1;
            y2 = c2;
            y3 = c3;

            m0 = c5;
            m1 = c6;

            d0 = c8;
            d1 = c9;

            h0 = c11;
            h1 = c12;

            i0 = c14;
            i1 = c15;

            s0 = c17;
            s1 = c18;
        } else {
            throw new DateTimeParseException("not support input ", str, 0);
        }

        int year;
        if (y0 >= '0' && y0 <= '9'
                && y1 >= '0' && y1 <= '9'
                && y2 >= '0' && y2 <= '9'
                && y3 >= '0' && y3 <= '9'
        ) {
            year = (y0 - '0') * 1000 + (y1 - '0') * 100 + (y2 - '0') * 10 + (y3 - '0');
        } else {
            throw new DateTimeParseException("not support input ", str, 0);
        }

        int month;
        if (m0 >= '0' && m0 <= '9'
                && m1 >= '0' && m1 <= '9'
        ) {
            month = (m0 - '0') * 10 + (m1 - '0');
        } else {
            throw new DateTimeParseException("not support input ", str, 0);
        }

        int dom;
        if (d0 >= '0' && d0 <= '9'
                && d1 >= '0' && d1 <= '9'
        ) {
            dom = (d0 - '0') * 10 + (d1 - '0');
        } else {
            throw new DateTimeParseException("not support input ", str, 0);
        }

        int hour;
        if (h0 >= '0' && h0 <= '9'
                && h1 >= '0' && h1 <= '9'
        ) {
            hour = (h0 - '0') * 10 + (h1 - '0');
        } else {
            throw new DateTimeParseException("not support input ", str, 0);
        }

        int minute;
        if (i0 >= '0' && i0 <= '9'
                && i1 >= '0' && i1 <= '9'
        ) {
            minute = (i0 - '0') * 10 + (i1 - '0');
        } else {
            throw new DateTimeParseException("not support input ", str, 0);
        }

        int second;
        if (s0 >= '0' && s0 <= '9'
                && s1 >= '0' && s1 <= '9'
        ) {
            second = (s0 - '0') * 10 + (s1 - '0');
        } else {
            throw new DateTimeParseException("not support input ", str, 0);
        }

        long millis;
        if (DEFAULT_ZONE_ID.equals(SHANGHAI_ZONE_ID)) {
            long seconds = DateUtils.utcSeconds(year, month, dom, hour, minute, second);
            int zoneOffsetTotalSeconds = DateUtils.getShanghaiZoneOffsetTotalSeconds(seconds);
            seconds -= zoneOffsetTotalSeconds;
            millis = seconds * 1000L;
        } else {
            LocalDate localDate = LocalDate.of(year, month, dom);
            LocalTime localTime = LocalTime.of(hour, minute, second, 0);
            LocalDateTime ldt = LocalDateTime.of(localDate, localTime);
            ZoneOffset offset = DEFAULT_ZONE_ID.getRules().getOffset(ldt);
            millis = ldt.toEpochSecond(offset) * 1000;
        }

        return new Date(millis);
    }

    public static void main(String[] args) throws Exception {
        Options options = new OptionsBuilder()
                .include(DateParse.class.getName())
                .mode(Mode.Throughput)
                .timeUnit(TimeUnit.MILLISECONDS)
                .warmupIterations(3)
                .forks(1)
                .build();
        new Runner(options).run();
    }
}
