package io.github.why168.view;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.util.Pair;
import android.text.InputFilter;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.TextView;

import com.google.android.flexbox.FlexboxLayout;
import com.jakewharton.rxbinding.widget.RxTextView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import io.github.why168.R;
import rx.Observable;
import rx.Single;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.observables.ConnectableObservable;
import rx.schedulers.Schedulers;
import rx.subscriptions.CompositeSubscription;


/**
 * 密码输入布局
 *
 * @author Edwin.Wu [email protected]
 * @version 2018/6/2 下午7:13 v0.9
 * @since JDK1.8
 */
public class PassWordInputLayout extends FrameLayout {

    private static final String BUNDLE__SUPER_STATE = "super_state";
    private static final String BUNDLE__CURRENT_CELL = "current_cell";
    private static final String BUNDLE__WORD_LIST = "word_list";
    private static final String BUNDLE__IS_HIDDEN = "is_hidden";
    private static final String BUNDLE__APPROVED_WORDS = "approved_words";

    private int currentCell = 0;
    private boolean isHidden = false;
    private ArrayList<String> wordList;
    private ArrayList<String> passphraseList;

    private CompositeSubscription subscriptions;
    private OnPassphraseFinishListener onPassphraseFinishedListener;
    private OnPassphraseUpdateListener onPassphraseUpdateListener;

    public interface OnPassphraseFinishListener {
        void onPassphraseFinished(final List<String> passphrase);
    }

    public interface OnPassphraseUpdateListener {
        void onPassphraseUpdate(final int approvedWords);
    }

    public PassWordInputLayout(Context context) {
        this(context, null);
    }

    public PassWordInputLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PassWordInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        inflate(getContext(), R.layout.view_passphrase_input, this);
        this.subscriptions = new CompositeSubscription();
    }


    // 设置为传递短语完成监听器
    public PassWordInputLayout setOnPassphraseFinishListener(final OnPassphraseFinishListener listener) {
        this.onPassphraseFinishedListener = listener;
        return this;
    }

    // 设置通过短语更新监听器。
    public PassWordInputLayout setOnPassphraseUpdateListener(final OnPassphraseUpdateListener listener) {
        this.onPassphraseUpdateListener = listener;
        return this;
    }

    // 设置单词
    public PassWordInputLayout setWordList(final ArrayList<String> wordList) {
        this.wordList = wordList;
        initView();
        return this;
    }

    private void initView() {
        initPassphraseList();

        reAddWordsToInputViews();

        initClickListeners();

        initCellListeners();

        hidePassphrase(this.isHidden);

        initFocus();
    }

    private void initPassphraseList() {
        if (this.passphraseList != null) return;
        this.passphraseList = new ArrayList<>(12);
    }

    // 将单词添加到输入视图中。
    private void reAddWordsToInputViews() {
        if (this.passphraseList.size() == 0) return;

        final FlexboxLayout wrapper = findViewById(R.id.wrapper);

        for (int i = 0; i < this.passphraseList.size(); i++) {
            final SuggestionInputLayout inputView = (SuggestionInputLayout) wrapper.getChildAt(i);
            final String word = this.passphraseList.get(i);

            if (word != null) {
                inputView.showTagView(word);
            } else {
                inputView.showInputView(null);
            }
            inputView.setVisibility(VISIBLE);
        }

        checkIfPassphraseIsApproved();
    }

    // 点击事件
    private void initClickListeners() {
        findViewById(R.id.hidePassphrase).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // 隐藏助记词
                PassWordInputLayout.this.isHidden = !PassWordInputLayout.this.isHidden;
                hidePassphrase(PassWordInputLayout.this.isHidden);
            }
        });

        findViewById(R.id.wrapper).setOnClickListener(__ -> handleWrapperClicked());

        findViewById(R.id.wrapper).setOnLongClickListener(__ -> handleWrapperLongClicked());
    }

    // 隐藏密码
    private void hidePassphrase(final boolean isHidden) {
        final FlexboxLayout wrapper = findViewById(R.id.wrapper);
        for (int i = 0; i < wrapper.getChildCount(); i++) {
            final SuggestionInputLayout suggestionInputLayout = (SuggestionInputLayout) wrapper.getChildAt(i);
            if (isHidden) {
                suggestionInputLayout.getTagView().hideText();
            } else {
                suggestionInputLayout.getTagView().showText();
            }
        }
    }

    // 转到可用的最后一个单元格,除非当前单元格为0。
    private void handleWrapperClicked() {
        final int lastContainedIndex = getLastWordContainedCellIndex();
        final int nextToLastContainedIndex = lastContainedIndex >= 11 ? this.passphraseList.size() - 1 : lastContainedIndex + 1;
        final int indexToGoTo = this.currentCell == 0 ? this.currentCell : nextToLastContainedIndex;
        final SuggestionInputLayout inputView = getChild(indexToGoTo);
        goToCell(inputView);
    }

    // 长按粘贴12个单词
    private boolean handleWrapperLongClicked() {
        final ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
        ClipData clipData = null;

        if (clipboard != null) {
            clipData = clipboard.getPrimaryClip();
        }

        if (clipData == null || clipData.getItemCount() == 0) return false;

        final ClipData.Item clipItem = clipData.getItemAt(0);

        if (clipItem == null || clipItem.getText() == null) return false;

        final List<String> wordList = Arrays.asList(clipItem.getText().toString().split(" "));

        if (wordList.size() > 12) {
            showErrorMessage(getContext().getString(R.string.paste_passphrase_error));
            return false;
        }

        pastePassphrase(wordList);
        return true;
    }

    private int getLastWordContainedCellIndex() {
        for (int i = this.passphraseList.size() - 1; i >= 0; i--) {
            final String word = this.passphraseList.get(i);
            if (word != null) return i;
        }
        return 0;
    }

    //
    private void initCellListeners() {
        final FlexboxLayout wrapper = findViewById(R.id.wrapper);

        for (int i = 0; i < wrapper.getChildCount(); i++) {
            final SuggestionInputLayout inputView = (SuggestionInputLayout) wrapper.getChildAt(i);

            inputView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    goToCell(inputView);
                }
            });

            inputView.getWordView().setOnFocusChangeListener(new OnFocusChangeListener() {
                @Override
                public void onFocusChange(View view, boolean hasFocus) {

                    final SuggestionInputLayout inputView = (SuggestionInputLayout) view.getParent();
                    if (hasFocus) {
                        PassWordInputLayout.this.currentCell = getIndexOfView(inputView);
                        final EditText wordView = inputView.getWordView();


                        // 添加支持速度侦听器
                        wordView.setOnKeyListener(new OnKeyListener() {
                            @Override
                            public boolean onKey(View v, int keyCode, KeyEvent event) {
                                final boolean isFirstCell = PassWordInputLayout.this.currentCell == 0;
                                final boolean isBackspace = keyCode == KeyEvent.KEYCODE_DEL;
                                final boolean isViewEmpty = wordView.getText().length() == 0;
                                final boolean isDownAction = event.getAction() == KeyEvent.ACTION_DOWN;
                                if (!isFirstCell && isBackspace && isViewEmpty && isDownAction) {
                                    goToPreviousCell();
                                    return true;
                                }
                                return false;
                            }
                        });


                        // 添加输入点击监听器
                        wordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
                            @Override
                            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

                                if (actionId == EditorInfo.IME_ACTION_DONE) {
                                    final SuggestionInputLayout inputView = getChild(PassWordInputLayout.this.currentCell);
                                    inputView.clearFocus();
                                    return true;
                                }
                                return false;
                            }
                        });


                        // 添加文本侦听器
                        addTextListeners(wordView);


                    } else {
                        clearView(inputView);
                        inputView.setSuggestionAsWord();
                        approveWord(inputView);
                    }
                }
            });
        }

    }

    // 初始化的焦点
    private void initFocus() {
        final SuggestionInputLayout cell = getChild(this.currentCell);
        goToCell(cell);
    }

    private void goToPreviousCell() {
        final SuggestionInputLayout currentView = getChild(this.currentCell);
        //Hide the current cell if empty
        currentView.setVisibility(currentView.getWordView().length() == 0 ? GONE : VISIBLE);
        final SuggestionInputLayout previousView = getChild(this.currentCell - 1);
        goToCell(previousView);
    }

    private void addTextListeners(final EditText et) {
        final ConnectableObservable<String> textSource = RxTextView
                .textChanges(et)
                .skip(1)
                .map(CharSequence::toString)
                .publish();

        addSpaceHandler(et);

        final Subscription suggestionSub = textSource.filter(input -> input.length() > 0)
                .flatMap(this::getWordSuggestion)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(this::handleWordSuggestion, Throwable::printStackTrace);

        final Subscription uiSub = textSource
                .subscribe(this::updateUi, Throwable::printStackTrace);

        final Subscription connectSub = textSource.connect();
        this.subscriptions.addAll(suggestionSub, uiSub, connectSub);
    }

    private void updateUi(final String input) {
        hideErrorMessage();
        clearSuggestion(input);
        clearHint(input);
    }

    private void addSpaceHandler(EditText et) {
        final InputFilter filter = (source, start, end, dest, dstart, dend) -> {
            for (int i = start; i < end; i++) {
                if (Character.isWhitespace(source.charAt(i))) {
                    tryAddSuggestion();
                    return "";
                }
            }
            return null;
        };
        et.setFilters(new InputFilter[]{filter});
    }

    private void tryAddSuggestion() {
        if (this.currentCell > 11) return;
        goToNextCell();
    }

    private void goToNextCell() {
        final SuggestionInputLayout currentView = getChild(this.currentCell);
        final String inputValueNoSpaces = currentView.getWordView().getText().toString().replace(" ", "");
        if (inputValueNoSpaces.length() == 0)
            return; //If the current cell is empty, don't do anything

        final SuggestionInputLayout nextView = getChild(this.currentCell + 1);
        if (nextView != null) {
            goToCell(nextView);
        } else {
            currentView.getWordView().clearFocus();
        }
    }

    private void goToCell(final SuggestionInputLayout cell) {
        if (cell == null) return;
        final String word = cell.getWord();
        setWordInCell(cell, word);
        cell.setVisibility(VISIBLE);
        setCursorAtEnd(cell.getWordView());
        cell.getWordView().requestFocus();

        ((InputMethodManager) getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE))
                .showSoftInput(cell.getWordView(), InputMethodManager.SHOW_IMPLICIT);
        hideErrorMessage();
    }

    private void setWordInCell(final SuggestionInputLayout cell, final String word) {
        // If word is null(word isn't approved), don't clear the text view
        if (word == null) return;
        cell.showInputView(word);
    }

    private Observable<String> getWordSuggestion(final String startOfWord) {
        return Observable.fromCallable(() -> {
            if (startOfWord.length() == 0) return null;
            return findStringSuggestion(this.wordList, startOfWord);
        });
    }

    private void handleWordSuggestion(final String suggestion) {
        if (suggestion == null) {
            handleNoSuggestion();
            return;
        }

        final SuggestionInputLayout inputView = getChild(this.currentCell);
        inputView.setWordSuggestion(suggestion);
    }

    private void hideErrorMessage() {
        final TextView hidePassphrase = findViewById(R.id.hidePassphrase);
        final TextView errorView = findViewById(R.id.errorView);
        hidePassphrase.setVisibility(View.VISIBLE);
        errorView.setVisibility(View.GONE);
    }

    private void clearSuggestion(final String string) {
        if (string.length() > 0) return;
        final SuggestionInputLayout inputView = getChild(this.currentCell);
        inputView.clearSuggestion();
    }

    private void clearHint(final String input) {
        final TextView hint = findViewById(R.id.hint);
        if (this.passphraseList.size() == 0 && input.length() == 0) {
            hint.setVisibility(VISIBLE);
        } else {
            hint.setVisibility(GONE);
        }
    }

    private void clearView(final SuggestionInputLayout inputView) {
        clearSubscriptions();
        removeBackspaceListener(inputView.getWordView());
        removeEnterClickedListener(inputView.getWordView());
        clearSpacesInView(inputView.getWordView());
    }

    private void removeBackspaceListener(final EditText et) {
        et.setOnKeyListener(null);
    }

    private void removeEnterClickedListener(final EditText et) {
        et.setOnEditorActionListener(null);
    }

    private void setCursorAtEnd(final EditText cell) {
        final String cellString = cell.getText().toString();
        if (cellString.length() == 0) return;
        cell.setSelection(cellString.length());
    }

    private void handleNoSuggestion() {
        final SuggestionInputLayout currentCell = getChild(this.currentCell);
        final String passphraseInput = currentCell.getWordView().getText().toString();
        final String errorMessage = getContext().getString(R.string.no_suggestion_found, passphraseInput);
        showErrorMessage(errorMessage);
        currentCell.clearSuggestion();
    }

    private void showErrorMessage(final String string) {
        final TextView hidePassphrase = findViewById(R.id.hidePassphrase);
        final TextView errorView = findViewById(R.id.errorView);
        hidePassphrase.setVisibility(View.GONE);
        errorView.setVisibility(View.VISIBLE);
        errorView.setText(string);
    }


    private void clearSpacesInView(final EditText et) {
        final String currentText = et.getText().toString();
        if (currentText.length() == 0) return;
        final String noSpacesText = currentText.replaceAll(" ", "");
        et.setText(noSpacesText);
    }

    private void approveWord(final SuggestionInputLayout view) {
        final String word = view.getWord();
        final int cellIndex = getIndexOfView(view);

        if (word == null) {
            addWordToView(null, cellIndex);
            return;
        }

        final Subscription sub = findMatch(word)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(matchResult -> addWordToView(matchResult, cellIndex),
                        Throwable::printStackTrace);

        this.subscriptions.add(sub);
    }

    private int getIndexOfView(final SuggestionInputLayout view) {
        final FlexboxLayout wrapper = findViewById(R.id.wrapper);
        for (int i = 0; i < wrapper.getChildCount(); i++) {
            final View inputView = wrapper.getChildAt(i);
            if (inputView.getId() == view.getId()) {
                return i;
            }
        }
        return 0;
    }

    private Single<String> findMatch(final String wordToFind) {
        return Single
                .just(wordToFind)
                .flatMap(word -> Single.fromCallable(() -> findMatch(this.wordList, word)));
    }

    private void addWordToView(final String matchResult, final int cellIndex) {
        final SuggestionInputLayout inputView = getChild(cellIndex);
        if (matchResult != null) {
            inputView.showTagView(matchResult);
        }

        addWordToPassphraseList(matchResult, cellIndex);
        checkIfPassphraseIsApproved();
    }

    private SuggestionInputLayout getChild(final int index) {
        final FlexboxLayout wrapper = findViewById(R.id.wrapper);
        return (SuggestionInputLayout) wrapper.getChildAt(index);
    }

    private void addWordToPassphraseList(final String word, final int cellIndex) {
        if (cellIndex < this.passphraseList.size()) {
            this.passphraseList.set(cellIndex, word);
        } else {
            this.passphraseList.add(cellIndex, word);
        }
    }

    private void checkIfPassphraseIsApproved() {
        if (isPassphraseApproved()) {
            this.onPassphraseFinishedListener.onPassphraseFinished(this.passphraseList);
        } else {
            this.onPassphraseUpdateListener.onPassphraseUpdate(getNumberApproveWords());
        }
    }

    private boolean isPassphraseApproved() {
        return this.passphraseList.size() == 12 && !this.passphraseList.contains(null);
    }

    private int getNumberApproveWords() {
        int numberOfApprovedWords = 0;
        for (final String word : this.passphraseList) {
            if (word != null) {
                numberOfApprovedWords++;
            }
        }
        return numberOfApprovedWords;
    }

    @Override
    public Parcelable onSaveInstanceState() {
        final Bundle bundle = new Bundle();
        bundle.putInt(BUNDLE__CURRENT_CELL, this.currentCell);
        bundle.putStringArrayList(BUNDLE__APPROVED_WORDS, this.passphraseList);
        bundle.putStringArrayList(BUNDLE__WORD_LIST, this.wordList);
        bundle.putBoolean(BUNDLE__IS_HIDDEN, this.isHidden);
        bundle.putParcelable(BUNDLE__SUPER_STATE, super.onSaveInstanceState());
        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            final Bundle bundle = (Bundle) state;
            this.currentCell = bundle.getInt(BUNDLE__CURRENT_CELL);
            this.passphraseList = bundle.getStringArrayList(BUNDLE__APPROVED_WORDS);
            this.wordList = bundle.getStringArrayList(BUNDLE__WORD_LIST);
            this.isHidden = bundle.getBoolean(BUNDLE__IS_HIDDEN);
            state = bundle.getParcelable(BUNDLE__SUPER_STATE);
        }
        super.onRestoreInstanceState(state);
        initView();
    }

    public List<String> getApprovedWordList() {
        return this.passphraseList;
    }

    public void pastePassphrase(final List<String> wordList) {
        initPassphraseList();
        this.passphraseList.clear();

        final Subscription sub = validatePastedPassphrase(wordList)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::addValidatedPassphraseToView, Throwable::printStackTrace);

        this.subscriptions.add(sub);
    }

    private Single<List<Pair<Boolean, String>>> validatePastedPassphrase(final List<String> passphrase) {
        return Single.fromCallable(() -> {
            final List<Pair<Boolean, String>> validatedPassphrase = new ArrayList<>();
            for (final String word : passphrase) {
                final String result = findMatch(this.wordList, word);
                if (result != null) {
                    validatedPassphrase.add(new Pair<>(true, word));
                } else {
                    validatedPassphrase.add(new Pair<>(false, word));
                }
            }
            return validatedPassphrase;
        });
    }

    private void addValidatedPassphraseToView(final List<Pair<Boolean, String>> validatedPassphrase) {
        if (validatedPassphrase.size() == 0) return;
        final FlexboxLayout wrapper = findViewById(R.id.wrapper);
        for (int i = 0; i < validatedPassphrase.size(); i++) {
            final SuggestionInputLayout inputView = (SuggestionInputLayout) wrapper.getChildAt(i);
            final String word = validatedPassphrase.get(i).second;
            final boolean isApproved = validatedPassphrase.get(i).first;
            if (isApproved) {
                inputView.showTagView(word);
                addWordToPassphraseList(word, i);
            } else {
                inputView.showInputView(word);
                addWordToPassphraseList(null, i);
            }
            inputView.setVisibility(VISIBLE);
        }

        checkIfPassphraseIsApproved();
        this.currentCell = validatedPassphrase.size() - 1;
        findViewById(R.id.hint).setVisibility(GONE);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        clearSubscriptions();
    }

    private void clearSubscriptions() {
        this.subscriptions.clear();
    }


    public static <T extends Comparable<T>> T findMatch(final List<T> searchList, final T itemToFind) {
        final int searchResult = Collections.binarySearch(searchList, itemToFind);
        if (Math.abs(searchResult) >= searchList.size() || searchResult < 0) return null;
        return searchList.get(searchResult);
    }

    public static String findStringSuggestion(final List<String> searchList, final String itemToFind) {
        final int searchResult = Collections.binarySearch(searchList, itemToFind);
        final int absoluteIndex = Math.abs(searchResult);

        //If the insertion point is >= searchList.size(), compare it to the last item
        if (absoluteIndex >= searchList.size()) {
            final String suggestion = searchList.get(searchList.size() - 1);
            return suggestion.startsWith(itemToFind) ? suggestion : null;
        }

        final int index = searchResult < 0 ? absoluteIndex - 1 : searchResult;
        final String suggestion = searchList.get(index);
        return suggestion != null && suggestion.startsWith(itemToFind) ? suggestion : null;
    }
}