content: /*
   * Copyright (C) 2020 ActiveJ LLC.
   *
   * Licensed 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.
   */
  
  package io.activej.http;
  
  import io.activej.bytebuf.ByteBuf;
  
  import static io.activej.bytebuf.ByteBufStrings.*;
  import static io.activej.http.HttpUtils.*;
  
  public final class HttpDate {
  	private static final int HOUR_SECONDS = 60 * 60;
  	private static final int DAY_SECONDS = 24 * HOUR_SECONDS;
  	private static final int YEAR_SECONDS = 365 * DAY_SECONDS;
  	private static final int FOUR_YEAR_SECONDS = 1461 * DAY_SECONDS;
  
  	private static final int[] DAYS_IN_MONTH = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  	private static final int[] DAYS_IN_MONTH_LEAP = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  
  	private static final byte[] GMT = encodeAscii("GMT");
  
  	private static final byte[][] DAYS_OF_WEEK = new byte[7][];
  
  	static {
  		DAYS_OF_WEEK[0] = encodeAscii("Sun");
  		DAYS_OF_WEEK[1] = encodeAscii("Mon");
  		DAYS_OF_WEEK[2] = encodeAscii("Tue");
  		DAYS_OF_WEEK[3] = encodeAscii("Wed");
  		DAYS_OF_WEEK[4] = encodeAscii("Thu");
  		DAYS_OF_WEEK[5] = encodeAscii("Fri");
  		DAYS_OF_WEEK[6] = encodeAscii("Sat");
  	}
  
  	private static final byte[][] MONTHS_IN_YEAR = new byte[12][];
  
  	static {
  		MONTHS_IN_YEAR[0] = encodeAscii("Jan");
  		MONTHS_IN_YEAR[1] = encodeAscii("Feb");
  		MONTHS_IN_YEAR[2] = encodeAscii("Mar");
  		MONTHS_IN_YEAR[3] = encodeAscii("Apr");
  		MONTHS_IN_YEAR[4] = encodeAscii("May");
  		MONTHS_IN_YEAR[5] = encodeAscii("Jun");
  		MONTHS_IN_YEAR[6] = encodeAscii("Jul");
  		MONTHS_IN_YEAR[7] = encodeAscii("Aug");
  		MONTHS_IN_YEAR[8] = encodeAscii("Sep");
  		MONTHS_IN_YEAR[9] = encodeAscii("Oct");
  		MONTHS_IN_YEAR[10] = encodeAscii("Nov");
  		MONTHS_IN_YEAR[11] = encodeAscii("Dec");
  	}
  
  	static long decode(byte[] bytes, int start) throws MalformedHttpException {
  		int day = trimAndDecodePositiveInt(bytes, start + 5, 2);
  
  		int month = -1;
  		for (int i = 0; i < MONTHS_IN_YEAR.length; i++) {
  			byte[] entry = MONTHS_IN_YEAR[i];
  			if (entry[0] == bytes[start + 8] &&
  				entry[1] == bytes[start + 9] &&
  				entry[2] == bytes[start + 10]) {
  				month = i;
  			}
  		}
  
  		int yearLength = '0' <= bytes[start + 12 + 2] && bytes[start + 12 + 2] <= '9' ? 4 : 2;
  
  		int year = (yearLength == 2 ? 2000 : 0) + trimAndDecodePositiveInt(bytes, start + 12, yearLength);
  		int hour = trimAndDecodePositiveInt(bytes, start + 13 + yearLength, 2);
  		int minutes = trimAndDecodePositiveInt(bytes, start + 16 + yearLength, 2);
  		int seconds = trimAndDecodePositiveInt(bytes, start + 19 + yearLength, 2);
  		boolean isLeapYear = isLeap(year);
  
  		int[] days = isLeapYear ? DAYS_IN_MONTH_LEAP : DAYS_IN_MONTH;
  
  		year = year - 1970;
  		int yearsLeft = year % 4;
  		long timestamp = 0;
  		int fy = (year - yearsLeft) / 4;
  		for (int i = 0; i < fy; i++) {
  			timestamp += FOUR_YEAR_SECONDS;
  		}
  
  		timestamp += yearsLeft * YEAR_SECONDS;
  		if (yearsLeft > 2) {
  			// 1972 was a leap year and this code is assumed to be either deprecated or fixed before year 2100
  			timestamp += DAY_SECONDS;
  		}
  
  		for (int i = 0; i < month; i++) {
  			timestamp += (long) DAY_SECONDS * days[i];
  		}
  
  		for (int i = 1; i < day; i++) {
  			timestamp += DAY_SECONDS;
  		}
  
  		timestamp += ((60L * hour + minutes) * 60) + seconds;
  
  		return timestamp;
  	}
  
  	static void render(long epochSeconds, ByteBuf buf) {
  		int pos = render(epochSeconds, buf.array(), buf.tail());
  		buf.tail(pos);
  	}
  
  	static int render(long epochSeconds, byte[] bytes, int pos) {
  		int fourYears = (int) (epochSeconds / FOUR_YEAR_SECONDS);
  		int year = fourYears * 4 + 1970;
  		int seconds = (int) (epochSeconds - fourYears * FOUR_YEAR_SECONDS);
  		boolean isLeapYear = false;
  
  		if (seconds >= YEAR_SECONDS) {
  			year++;
  			seconds -= YEAR_SECONDS;
  			if (seconds >= YEAR_SECONDS) {
  				year++;
  				seconds -= YEAR_SECONDS;
  				if (seconds >= (YEAR_SECONDS + DAY_SECONDS)) {
  					year++;
  					seconds -= YEAR_SECONDS + DAY_SECONDS;
  				} else {
  					isLeapYear = true;
  				}
  			}
  		}
  
  		int dayOfYear = seconds / DAY_SECONDS;
  		seconds -= dayOfYear * DAY_SECONDS;
  
  		int[] monthsDays = isLeapYear ? DAYS_IN_MONTH_LEAP : DAYS_IN_MONTH;
  		int month = 0;
  		int day = dayOfYear;
  		while (day >= monthsDays[month]) {
  			day -= monthsDays[month++];
  		}
  
  		int hours = seconds / (60 * 60);
  		seconds -= hours * (60 * 60);
  
  		int minutes = seconds / 60;
  		seconds -= minutes * 60;
  
  		int dayOfWeek = (int) ((epochSeconds / (24 * 60 * 60) + 4) % 7) + 1;
  
  		byte[] stringDay = DAYS_OF_WEEK[dayOfWeek - 1];
  		for (byte b : stringDay) {
  			bytes[pos++] = b;
  		}
  		bytes[pos++] = COMMA;
  		bytes[pos++] = SP;
  
  		if (day < 9) {
  			bytes[pos++] = ZERO;
  		}
  		day += 1;
  		pos += encodePositiveInt(bytes, pos, day);
  		bytes[pos++] = SP;
  
  		byte[] stringMonth = MONTHS_IN_YEAR[month];
  		for (byte b : stringMonth) {
  			bytes[pos++] = b;
  		}
  		bytes[pos++] = SP;
  
  		pos += encodePositiveInt(bytes, pos, year);
  		bytes[pos++] = SP;
  
  		if (hours < 10) {
  			bytes[pos++] = ZERO;
  		}
  		pos += encodePositiveInt(bytes, pos, hours);
  		bytes[pos++] = COLON;
  
  		if (minutes < 10) {
  			bytes[pos++] = ZERO;
  		}
  		pos += encodePositiveInt(bytes, pos, minutes);
  		bytes[pos++] = COLON;
  
  		if (seconds < 10) {
  			bytes[pos++] = ZERO;
  		}
  		pos += encodePositiveInt(bytes, pos, seconds);
  		bytes[pos++] = SP;
  
  		for (byte b : GMT) {
  			bytes[pos++] = b;
  		}
  
  		return pos;
  	}
  
  	private static boolean isLeap(int year) {
  		if (year % 4 == 0) {
  			if (year % 100 == 0) {
  				return year % 400 == 0;
  			} else {
  				return true;
  			}
  		}
  		return false;
  	}
  }
  