package com.utopiaindustries.util;

import com.utopiaindustries.model.Granularity;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

public class CTPDateTimeFormat {
    // https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html
    // generic format for date
    public static final String DATE_FORMAT = "M/d/yyyy";
    // generic format for datetime
    public static final String DATE_TIME_FORMAT = "M/d/yyyy HH:mm:ss";
    // format for bank statements
    public static final String BANK_STATEMENT_DATE_FORMAT = "d/M/yyyy";
    // mysql datetime format (mysql stores in this format)
    public static final String MYSQL_DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    // date time format for file names
    public static final String FILENAME_DATETIME_FORMAT = "yyyy-MM-dd--HH-mm-ss";
    // mysql date format (mysql stores in this format)
    public static final String MYSQL_DATE_FORMAT = "yyyy-MM-dd";
    // output format for date in templates
    public static final String TEMPLATE_DATE_FORMAT = "MMM dd, yyyy";
    // output format for datetime in templates
    public static final String TEMPLATE_DATE_TIME_FORMAT = "MMM dd, yyyy hh:mm a";
    // html5 input date format
    public static final String HTML5_DATE_INPUT_FORMAT = "yyyy-MM-dd";
    // html5 input datetime format
    public static final String HTML5_DATETIME_INPUT_FORMAT = "yyyy-MM-dd'T'HH:mm";
    public static final String HTML5_DATETIME_INPUT_FORMAT_WITH_SECONDS = "yyyy-MM-dd'T'HH:mm:ss";
    // html5 input time format
    public static final String HTML5_TIME_INPUT_FORMAT = "HH:mm";
    // html5 input time format
    public static final String HTML5_TIME_INPUT_FORMAT_ATTENDANCE = "hh:mm a";
    // email Date Format
    public static final String EMAIL_ATTENDANCE_DATE_FORMAT = "MMM dd";
    // probation email date format
    public static final String EMAIL_PROBATION_DATE_FORMAT = "dd/MM/yyyy";
    //month year date format
    public static final String MONTH_YEAR_DATE_FORMAT = "MMM-yyyy";
    // voc date format
    public static final String VOC_DATETIME_INPUT_FORMAT_WITH_SECONDS = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
    // reviews date format
    public static final String REVIEWS_DATETIME_INPUT_FORMAT_WITH_SECONDS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
    public static final String HTML5_MONTH_YEAR_FORMAT = "yyyy-MM";

    /**
     * parse date with format
     */
    public static LocalDate getParsedDate( String date, String format ) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern( format );
        return LocalDate.parse( date, formatter );
    }

    /**
     * parse date with format
     */
    public static LocalDateTime getParsedDateTime( String date, String format ) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern( format );
        return LocalDateTime.parse( date, formatter );
    }

    /**
     * format localdatetime into a given format string
     */
    public static String getFormattedDateTimeString( LocalDateTime dateTime, String dateTimeFormat ) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern( dateTimeFormat );
        return dateTime.format( formatter );
    }

    /**
     * format localdate into a given format string
     */
    public static String getFormattedDateString( LocalDate dateTime, String dateTimeFormat ) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern( dateTimeFormat );
        return dateTime.format( formatter );
    }


    /**
     * format string/localdate to mysql date format string
     */
    public static String getMySQLFormattedDateString( String date, String currentFormat ) {
        String formattedDate = null;
        if ( ! StringUtils.isNullOrEmpty( date ) ) {
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern( currentFormat );
            LocalDate localDate = LocalDate.parse( date, dtf );
            formattedDate = localDate.format( DateTimeFormatter.ofPattern( CTPDateTimeFormat.MYSQL_DATE_FORMAT ) );
        }
        return formattedDate;
    }

    /**
     * get mysql formatted date time string
     */
    public static String getMySQLFormattedDateTimeString( LocalDateTime dateTime ) {
        String formattedDate = null;
        if ( dateTime != null ) {
            formattedDate = dateTime.format( DateTimeFormatter.ofPattern( CTPDateTimeFormat.MYSQL_DATETIME_FORMAT ) );
        }
        return formattedDate;
    }

    /**
     * format string/localdate to mysql date format string
     */
    public static String getMySQLFormattedDateString( String date ) {
        return getMySQLFormattedDateString( date, CTPDateTimeFormat.MYSQL_DATE_FORMAT );
    }

    public static String getMySQLFormattedDateString( LocalDate date ) {
        String formattedDate = null;
        if ( date != null ) {
            formattedDate = date.format( DateTimeFormatter.ofPattern( CTPDateTimeFormat.MYSQL_DATE_FORMAT ) );
        }
        return formattedDate;
    }

    public static String getDateFormat( LocalDate localDate, String format ){
        String formattedDate = null;
        if ( localDate != null ) {
            formattedDate = localDate.format( DateTimeFormatter.ofPattern( format ) );
        }
        return formattedDate;
    }

    public static String getTimeFormat( LocalTime localTime, String format ) {
        String formattedTime = null;
        if ( localTime != null ) {
            formattedTime = localTime.format( DateTimeFormatter.ofPattern( format ) );
        }
        return formattedTime;
    }

    public static String[] getMonths() {
        return new String[] {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October",
                "November", "December"};
    }

    /**
     * get fiscal year
     */
    public static String getFiscalYearForDate( LocalDate date ) {
        String year = date.format( DateTimeFormatter.ofPattern( "yy" ) );
        if ( date.getMonthValue() <= 6 ) {
            int previousYear = Integer.parseInt( year ) - 1;
            return previousYear + year;
        } else {
            int nextYear = Integer.parseInt( year ) + 1;
            return year + nextYear;
        }
    }

    /**
     * get number of days in month of the year
     */
    public static int getNumberOfDaysInMonthYear( LocalDate date ) {
        YearMonth yearMonth = YearMonth.of( date.getYear(), date.getMonthValue() );
        return yearMonth.lengthOfMonth();
    }

    /**
     * get date range
     */
    public static List<LocalDate> getDateRange( LocalDate startDate, LocalDate endDate ) {
        long daysBetween = ChronoUnit.DAYS.between( startDate, endDate );
        List<LocalDate> dateRange = new ArrayList<>();
        for ( int i = 0; i <= daysBetween; i++ ) {
            dateRange.add( startDate.plusDays( i ) );
        }
        return dateRange;
    }

    /**
     * get date range by granularity
     */
    public static List<LocalDate> getDateRangeByGranularity( LocalDate startDate, LocalDate endDate, String granularity ) {
        if ( granularity.equalsIgnoreCase( Granularity.DAILY.name() ) ) {
            return getDateRange( startDate, endDate );
        } else if ( granularity.equalsIgnoreCase( Granularity.WEEKLY.name() ) ) {
            long daysBetween = ChronoUnit.DAYS.between( startDate, endDate );
            List<LocalDate> dateRange = new ArrayList<>();
            for ( int i = 0; i < daysBetween; i++ ) {
                LocalDate date = startDate.plusDays( i ).with( WeekFields.of( Locale.getDefault() ).getFirstDayOfWeek() );
                if ( ! dateRange.contains( date ) ) {
                    dateRange.add( date );
                }
            }
            return dateRange;
        } else if ( granularity.equalsIgnoreCase( Granularity.MONTHLY.name() ) ) {
            long daysBetween = ChronoUnit.DAYS.between( startDate, endDate );
            List<LocalDate> dateRange = new ArrayList<>();
            for ( int i = 0; i < daysBetween; i++ ) {
                LocalDate date = startDate.plusDays( i ).withDayOfMonth( 1 );
                if ( ! dateRange.contains( date ) ) {
                    dateRange.add( date );
                }
            }
            return dateRange;
        } else if ( granularity.equalsIgnoreCase( Granularity.YEARLY.name() ) ) {
            long daysBetween = ChronoUnit.DAYS.between( startDate, endDate );
            List<LocalDate> dateRange = new ArrayList<>();
            for ( int i = 0; i < daysBetween; i++ ) {
                LocalDate date = startDate.plusDays( i ).withDayOfYear( 1 );
                if ( ! dateRange.contains( date ) ) {
                    dateRange.add( date );
                }
            }
            return dateRange;
        }
        return new ArrayList<>();
    }

    /**
     * get date wrt granularity
     */
    public static LocalDate getDateByGranularity( LocalDate date, String granularity ) {
        if ( granularity.equalsIgnoreCase( Granularity.DAILY.name() ) ) {
            return date;
        } else if ( granularity.equalsIgnoreCase( Granularity.WEEKLY.name() ) ) {
            return date.with( WeekFields.of( Locale.getDefault() ).getFirstDayOfWeek() );
        } else if ( granularity.equalsIgnoreCase( Granularity.MONTHLY.name() ) ) {
            return date.withDayOfMonth( 1 );
        } else if ( granularity.equalsIgnoreCase( Granularity.YEARLY.name() ) ) {
            return date.withDayOfYear( 1 );
        }
        return date;
    }

    public static List<LocalDate> getDateRangeMonthly( LocalDate startDate, LocalDate endDate ) {
        long daysBetween = ChronoUnit.DAYS.between( startDate, endDate );
        List<LocalDate> dateRange = new ArrayList<>();
        for ( int i = 0; i <= daysBetween; i++ ) {
            LocalDate date = startDate.plusDays( i ).withDayOfMonth( 1 );
            if ( ! dateRange.contains( date ) ) {
                dateRange.add( date );
            }
        }
        return dateRange;
    }

    /**
     * convert date to localdate
     */
    public static LocalDate convertDateToLocalDate( Date date ) {
        return date.toInstant()
                .atZone( ZoneId.systemDefault() )
                .toLocalDate();
    }

    /**
     * convert local date to date
     */
    public static Date convertLocalDateToDate( LocalDate date ) {
        return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }

    /**
     * convert local date list to date list
     */
    public static List<Date> convertListOfLocalDateToListOfDate(List<LocalDate> localDateList) {
        return localDateList.stream()
                .map(CTPDateTimeFormat::convertLocalDateToDate)
                .collect(Collectors.toList());
    }

    /**
     *
     * @param date
     * @param startDate
     * @param endDate
     * @comment check date between range
     * @return
     */
    public static boolean isDateInRange(LocalDate date, LocalDate startDate, LocalDate endDate) {
        return !date.isBefore(startDate) && !date.isAfter(endDate);
    }

}

