package com.turlir.abakgists.allgists;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import com.turlir.abakgists.allgists.combination.InlineLoading;
import com.turlir.abakgists.allgists.combination.ListCombination;
import com.turlir.abakgists.allgists.combination.ListManipulator;
import com.turlir.abakgists.allgists.combination.Refresh;
import com.turlir.abakgists.allgists.combination.Start;
import com.turlir.abakgists.model.GistModel;

import java.util.List;

import io.reactivex.disposables.Disposable;
import io.reactivex.observers.ResourceSingleObserver;
import timber.log.Timber;

class GistLoader {

    private final GistListInteractor mInteractor;
    private final ListManipulator<GistModel> mCallback;

    @NonNull
    private ListCombination<GistModel> mState;
    private Disposable mDatabaseConnection;
    @Nullable
    private GistModel mLast = null;
    private boolean isEnded;

    GistLoader(GistListInteractor interactor, ListManipulator<GistModel> callback) {
        mInteractor = interactor;
        mCallback = callback;

        mState = new Start(mCallback);
    }

    void firstPage() {
        if (!canLoad()) return;
        mState = new Start(mCallback);
        mState.perform();

        mDatabaseConnection = mInteractor.firstPage()
                .subscribe(gistModels -> {
                    final Range range = mInteractor.range;
                    if (gistModels.size() == 0 && !isEnded) {
                        LoadablePage page = range.page();
                        server(page);
                    }
                    if (gistModels.size() > 0) {
                        changeState(mState.content(gistModels));
                        mLast = gistModels.get(gistModels.size() - 1);
                    }
                    isEnded = range.count() != gistModels.size();

                }, t -> {
                    changeState(mState.error(t));
                });
    }

    void nextPage() {
        if (!canNext()) {
            Timber.d("loading next page is not allowed (is loading or last page reached)");
            return;
        }
        mDatabaseConnection.dispose();
        mDatabaseConnection = mInteractor.nextPage()
                .subscribe(nextItems -> {
                    setNextItems(nextItems);
                }, t -> {
                    changeState(mState.error(t));
                });
    }

    private void setNextItems(List<GistModel> nextItems) {
        boolean lessThan = mInteractor.range.count() > nextItems.size();
        GistModel nowLast = nextItems.get(nextItems.size() - 1);
        boolean lastNotChanged = mLast != null && nowLast.isDifferent(mLast);
        mLast = nowLast;
        if (!canLoad()) { // loading in process
            Timber.v("updating list when loading the next page");
            mState.content(nextItems).perform(); // side effect without state change
            mState.perform(); // repeat loading
        } else {
            Timber.v("updating list direct");
            changeState(mState.content(nextItems)); // perform
        }
        if (lessThan && lastNotChanged && !isEnded && canNext()) {
            Range already = mInteractor.range.cut(nextItems.size());
            Range required = mInteractor.range.diff(already);
            LoadablePage page = required.page();
            Timber.d("download required %d th page in %d items", page.number, page.size);
            server(page);
            changeState(mState.doLoad());
        }
    }

    void prevPage() {
        if (!canPrevious()) {
            Timber.d("loading previous page is not allowed (is loading or first page reached)");
            return;
        }
        mDatabaseConnection.dispose();
        mDatabaseConnection = mInteractor.prevPage()
                .doOnNext(nextItems -> {
                    isEnded = false;
                    mLast = nextItems.get(nextItems.size() - 1);
                })
                .subscribe(nextItems -> {
                    changeState(mState.content(nextItems));
                }, t -> {
                    changeState(mState.error(t));
                });
    }

    int size() {
        return mInteractor.range.prev().absStop;
    }

    boolean isDifferent(GistModel now) {
        return mLast != null && now.isDifferent(mLast);
    }

    void stop() {
        if (mDatabaseConnection != null) {
            mDatabaseConnection.dispose();
        }
    }

    private void server(LoadablePage page) {
        mInteractor.server(page)
                .doOnSuccess(i -> {
                    changeState(mState.doIntermediate());
                })
                .subscribe(new ResourceSingleObserver<Integer>() {
                    @Override
                    public void onSuccess(Integer count) {
                        isEnded = count < page.size;
                        if (isEnded) {
                            Timber.d("gists list ended");
                        }
                        dispose();
                    }
                    @Override
                    public void onError(Throwable e) {
                        changeState(mState.error(e));
                        dispose();
                    }
                });
    }

    private void changeState(ListCombination<GistModel> now) {
        Timber.v("StateChange leave %s, enter %s", mState.getClass().getSimpleName(), now.getClass().getSimpleName());
        mState = now;
        mState.perform();
    }

    private boolean canLoad() {
        return !(mState instanceof InlineLoading) && !(mState instanceof Refresh);
    }

    public boolean canNext() {
        return canLoad() && mInteractor.range.hasNext() && !isEnded;
    }

    public boolean canPrevious() {
        return canLoad() && mInteractor.range.hasPrevious();
    }
}

@@ -2291,19 +2291,18 @@
         
-set
+on
 NextItem
@@ -2439,11 +2439,10 @@
 oid 
-set
+on
 Next
@@ -2480,249 +2480,8 @@
 ) %7B%0A
-        boolean lessThan = mInteractor.range.count() %3E nextItems.size();%0A        GistModel nowLast = nextItems.get(nextItems.size() - 1);%0A        boolean lastNotChanged = mLast != null && nowLast.isDifferent(mLast);%0A        mLast = nowLast;%0A
     
@@ -2535,33 +2535,33 @@
          Timber.
-v
+d
 (%22updating list 
@@ -2723,16 +2723,93 @@
 loading%0A
+            mLast = nextItems.get(nextItems.size() - 1);%0A            return;%0A
         
@@ -2836,17 +2836,17 @@
  Timber.
-v
+d
 (%22updati
@@ -2928,32 +2928,33 @@
 rform%0A        %7D%0A
+%0A
         if (less
@@ -2950,49 +2950,279 @@
    i
-f (lessThan && lastNotChanged && !isEnded
+nt nowSize = nextItems.size();%0A        boolean lessThan = nowSize %3C mInteractor.range.count();%0A        GistModel lastItem = nextItems.get(nowSize - 1);%0A        boolean eqLast = mLast == null %7C%7C !lastItem.isDifferent(mLast);%0A        mLast = lastItem;%0A%0A        if (lessThan
  && 
@@ -3222,32 +3222,42 @@
 han && canNext()
+ && eqLast
 ) %7B%0A            
@@ -3294,32 +3294,42 @@
 nge.cut(
-nextItems.size()
+mInteractor.range.addition
 );%0A     
@@ -5430,22 +5430,24 @@
 ber.
-v(%22S
+d(%22s
 tate
-C
+ c
 hange
+:
  lea

fix paging algorithm