package org.wikipedia.feed;

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

import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.feed.dataclient.FeedClient;
import org.wikipedia.feed.model.Card;
import org.wikipedia.feed.offline.OfflineCard;
import org.wikipedia.feed.progress.ProgressCard;
import org.wikipedia.settings.Prefs;
import org.wikipedia.util.ThrowableUtil;
import org.wikipedia.util.log.L;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public abstract class FeedCoordinatorBase {
    private static final int MAX_HIDDEN_CARDS = 100;

    public interface FeedUpdateListener {
        void insert(Card card, int pos);
        void swap(Card card, int pos);
        void remove(Card card, int pos);
    }

    @NonNull private Context context;
    @Nullable private WikiSite wiki;
    @Nullable private FeedUpdateListener updateListener;
    @NonNull private final List<Card> cards = new ArrayList<>();
    private int currentAge;
    private List<FeedClient> pendingClients = new ArrayList<>();
    private FeedClient.Callback callback = new ClientRequestCallback();
    private Card progressCard = new ProgressCard();

    private Set<String> hiddenCards = Collections.newSetFromMap(new LinkedHashMap<String, Boolean>() {
        @Override
        public boolean removeEldestEntry(Map.Entry<String, Boolean> eldest) {
            return size() > MAX_HIDDEN_CARDS;
        }
    });

    public FeedCoordinatorBase(@NonNull Context context) {
        this.context = context;
        hiddenCards.addAll(Prefs.getHiddenCards());
    }

    @NonNull
    public List<Card> getCards() {
        return cards;
    }

    public void setFeedUpdateListener(@Nullable FeedUpdateListener listener) {
        updateListener = listener;
    }

    public void reset() {
        wiki = null;
        currentAge = 0;
        for (FeedClient client : pendingClients) {
            client.cancel();
        }
        pendingClients.clear();
        cards.clear();
    }

    public void more(@NonNull WikiSite wiki) {
        this.wiki = wiki;

        if (cards.size() == 0) {
            insertCard(progressCard, 0);
        }

        if (cards.size() > 1) {
            currentAge++;
        }

        buildScript(currentAge, wiki);
        requestNextCard(wiki);
    }

    public boolean finished() {
        return pendingClients.isEmpty();
    }

    public int getAge() {
        return currentAge;
    }

    public int dismissCard(@NonNull Card card) {
        int position = cards.indexOf(card);
        addHiddenCard(card);
        removeCard(card, position);
        return position;
    }

    public void undoDismissCard(@NonNull Card card, int position) {
        unHideCard(card);
        insertCard(card, position);
    }

    void retryFromOffline(@NonNull WikiSite wiki) {
        Card card = cards.get(lastIndex());
        if (card instanceof OfflineCard) {
            swapCard(progressCard, lastIndex());
        }
        currentAge--;
        more(wiki);
    }

    protected abstract void buildScript(int age, WikiSite wiki);

    protected void addPendingClient(FeedClient client) {
        pendingClients.add(client);
    }

    private int lastIndex() {
        return cards.size() - 1;
    }

    private void requestNextCard(@NonNull WikiSite wiki) {
        if (pendingClients.isEmpty()) {
            removeProgressCard();
            return;
        }
        pendingClients.remove(0).request(context, wiki, currentAge, callback);
    }

    private void removeProgressCard() {
        int pos = cards.indexOf(progressCard);
        if (pos < 0) {
            return;
        }
        removeCard(progressCard, pos);
    }

    private class ClientRequestCallback implements FeedClient.Callback {
        @Override public void success(@NonNull List<? extends Card> cardList) {
            for (Card card : cardList) {
                if (!isCardHidden(card)) {
                    appendCard(card);
                }
            }
            //noinspection ConstantConditions
            requestNextCard(wiki);
        }

        @Override public void error(@NonNull Throwable caught) {
            if (ThrowableUtil.isOffline(caught)) {
                appendCard(new OfflineCard());
                removeProgressCard();
            } else {
                //noinspection ConstantConditions
                requestNextCard(wiki);
                L.w(caught);
            }
        }
    }

    private void appendCard(@NonNull Card card) {
        int progressPos = cards.indexOf(progressCard);
        insertCard(card, progressPos >= 0 ? progressPos : cards.size());
    }

    private void insertCard(@NonNull Card card, int position) {
        cards.add(position, card);
        if (updateListener != null) {
            updateListener.insert(card, position);
        }
    }

    private void swapCard(@NonNull Card card, int position) {
        cards.remove(cards.get(position));
        cards.add(position, card);
        if (updateListener != null) {
            updateListener.swap(card, position);
        }
    }

    private void removeCard(@NonNull Card card, int position) {
        cards.remove(card);
        if (updateListener != null) {
            updateListener.remove(card, position);
        }
    }

    private void addHiddenCard(@NonNull Card card) {
        hiddenCards.add(card.getHideKey());
        Prefs.setHiddenCards(hiddenCards);
    }

    private boolean isCardHidden(@NonNull Card card) {
        return hiddenCards.contains(card.getHideKey());
    }

    private void unHideCard(@NonNull Card card) {
        hiddenCards.remove(card.getHideKey());
        Prefs.setHiddenCards(hiddenCards);
    }
}

@@ -2991,60 +2991,22 @@
     
-Card card = cards.get(lastIndex());%0A        if (c
+if (lastC
 ard
+()
  ins
@@ -3091,30 +3091,8 @@
   %7D%0A
-        currentAge--;%0A
     
@@ -3272,32 +3272,118 @@
 client);%0A    %7D%0A%0A
+    @Nullable private Card lastCard() %7B%0A        return cards.get(lastIndex());%0A    %7D%0A%0A
     private int 
@@ -3863,32 +3863,309 @@
 d, pos);%0A    %7D%0A%0A
+    private void setOfflineState() %7B%0A        removeProgressCard();%0A        if (!(lastCard() instanceof OfflineCard)) %7B%0A            appendCard(new OfflineCard());%0A        %7D%0A        pendingClients.clear();%0A        if (currentAge %3E 0) %7B%0A            currentAge--;%0A        %7D%0A    %7D%0A%0A
     private clas
@@ -4683,73 +4683,23 @@
     
-appendCard(new OfflineCard());%0A                removeProgressCard
+setOfflineState
 ();%0A

Improve feed coordinator logic when offline.