package com.francesco.furlan.customizablecalendar.library.interactors;

import com.francesco.furlan.customizablecalendar.library.model.Calendar;
import com.francesco.furlan.customizablecalendar.library.model.CalendarFields;

import org.joda.time.DateTime;

import java.util.ArrayList;
import java.util.List;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;

/**
 * Created by francescofurlan on 30/06/17.
 */

public class AUCalendar {
    private static AUCalendar AUCalendarInstance;
    private Calendar calendar;
    private List<CalendarObjectChangeListener> onChangeListenerList = new ArrayList<>();

    public static AUCalendar getInstance() {
        if (AUCalendarInstance == null) {
            AUCalendarInstance = new AUCalendar();
        }
        return AUCalendarInstance;
    }

    public static AUCalendar getInstance(Calendar calendar) {
        getInstance();
        if (calendar != null) {
            AUCalendarInstance.setCalendar(calendar);
        }
        return AUCalendarInstance;
    }

    public DateTime getFirstMonth() {
        return calendar.getFirstMonth();
    }

    public Calendar getCalendar() {
        return calendar;
    }

    public void setCalendar(Calendar calendar) {
        this.calendar = calendar;
    }

    public Flowable<ChangeSet> observeChangesOnCalendar() {
        return Flowable.create((FlowableEmitter<ChangeSet> emitter) -> {
            CalendarObjectChangeListener objectChangeListener = emitter::onNext;
            addChangeListener(objectChangeListener);
            emitter.setCancellable(() -> removeChangeListener(objectChangeListener));
        }, BackpressureStrategy.BUFFER);
    }

    private void addChangeListener(CalendarObjectChangeListener objectChangeListener) {
        onChangeListenerList.add(objectChangeListener);
    }

    private void removeChangeListener(CalendarObjectChangeListener objectChangeListener) {
        onChangeListenerList.remove(objectChangeListener);
    }

    private void emitOnChange(ChangeSet changeSet) {
        for (CalendarObjectChangeListener onChangeListener : onChangeListenerList) {
            onChangeListener.onChange(changeSet);
        }
    }

    private void emitOnChange(String changedField) {
        CalendarChangeSet changeSet = new CalendarChangeSet();
        changeSet.addChangedField(changedField);
        emitOnChange(changeSet);
    }

    public DateTime getFirstSelectedDay() {
        return calendar.getFirstSelectedDay();
    }

    public void setFirstSelectedDay(DateTime firstSelectedDay) {
        calendar.setFirstSelectedDay(firstSelectedDay);
        emitOnChange(CalendarFields.FIRST_SELECTED_DAY);
    }

    public DateTime getLastSelectedDay() {
        return calendar.getLastSelectedDay();
    }

    public void setLastSelectedDay(DateTime lastSelectedDay) {
        calendar.setLastSelectedDay(lastSelectedDay);
        emitOnChange(CalendarFields.LAST_SELECTED_DAY);
    }

    public DateTime getCurrentMonth() {
        return calendar.getCurrentMonth();
    }

    public void setCurrentMonth(DateTime currentMonth) {
        calendar.setCurrentMonth(currentMonth);
        emitOnChange(CalendarFields.CURRENT_MONTH);
    }

    public List<DateTime> getMonths() {
        return calendar.getMonths();
    }

    public void setMonths(List<DateTime> months) {
        calendar.setMonths(months);
        emitOnChange(CalendarFields.MONTHS);
    }

    public boolean isMultipleSelectionEnabled() {
        return calendar.isMultipleSelectionEnabled();
    }

    public void setMultipleSelection(boolean multipleSelection) {
        calendar.setMultipleSelection(multipleSelection);
        emitOnChange(CalendarFields.MULTIPLE_SELECTION);
    }

    public int getFirstDayOfWeek() {
        return calendar.getFirstDayOfWeek();
    }

    public void setFirstDayOfWeek(int firstDayOfWeek) {
        calendar.setFirstDayOfWeek(firstDayOfWeek);
        emitOnChange(CalendarFields.FIRST_DAY_OF_WEEK);
    }

    public interface ChangeSet {
        List<String> getChangedFields();

        boolean isFieldChanged(String fieldName);

        String toString();
    }

    public interface CalendarObjectChangeListener {
        void onChange(ChangeSet changeSet);
    }

    private class CalendarChangeSet implements ChangeSet {
        private List<String> changedFields = new ArrayList<>();

        public void setChangedFiels(List<String> changedFields) {
            this.changedFields = changedFields;
        }

        public void addChangedField(String changedField) {
            this.changedFields.add(changedField);
        }

        @Override
        public List<String> getChangedFields() {
            return changedFields;
        }

        @Override
        public boolean isFieldChanged(String fieldName) {
            return changedFields.contains(fieldName);
        }

        @Override
        public String toString() {
            String fieldChanges = "";
            for (String fieldChanged : changedFields) {
                fieldChanges += " " + fieldChanged;
            }
            return fieldChanges;
        }
    }
}
@@ -2592,32 +2592,102 @@
 tSelectedDay) %7B%0A
+        if (calendar.getFirstSelectedDay() != firstSelectedDay) %7B%0A    
         calendar
@@ -2726,32 +2726,36 @@
 edDay);%0A        
+  
+  
 emitOnChange(Cal
@@ -2779,32 +2779,42 @@
 _SELECTED_DAY);%0A
+        %7D%0A
     %7D%0A%0A    publi
@@ -2955,32 +2955,100 @@
 tSelectedDay) %7B%0A
+        if (calendar.getLastSelectedDay() != lastSelectedDay) %7B%0A    
         calendar
@@ -3085,32 +3085,36 @@
 edDay);%0A        
+   
+ 
 emitOnChange(Cal
@@ -3141,24 +3141,34 @@
 ECTED_DAY);%0A
+        %7D%0A
     %7D%0A%0A    p
@@ -3305,24 +3305,86 @@
 entMonth) %7B%0A
+        if (calendar.getCurrentMonth() != currentMonth) %7B%0A    
         cale
@@ -3419,16 +3419,20 @@
 Month);%0A
+    
         
@@ -3475,16 +3475,26 @@
 MONTH);%0A
+        %7D%0A
     %7D%0A%0A 
@@ -3623,24 +3623,74 @@
 %3E months) %7B%0A
+        if (calendar.getMonths() != months) %7B%0A    
         cale
@@ -3713,16 +3713,20 @@
 onths);%0A
+    
         
@@ -3762,16 +3762,26 @@
 ONTHS);%0A
+        %7D%0A
     %7D%0A%0A 
@@ -3952,24 +3952,102 @@
 election) %7B%0A
+        if (calendar.isMultipleSelectionEnabled() != multipleSelection) %7B%0A    
         cale
@@ -4092,16 +4092,20 @@
 ction);%0A
+    
         
@@ -4153,16 +4153,26 @@
 CTION);%0A
+        %7D%0A
     %7D%0A%0A 
@@ -4311,24 +4311,90 @@
 ayOfWeek) %7B%0A
+        if (calendar.getFirstDayOfWeek() != firstDayOfWeek) %7B%0A    
         cale
@@ -4433,32 +4433,36 @@
 fWeek);%0A        
+    
 emitOnChange(Cal
@@ -4493,16 +4493,26 @@
 _WEEK);%0A
+        %7D%0A
     %7D%0A%0A 

change and notify calendar changes only if the value actually changes