/*
 * Mirror - Yet another Sketch Mirror App for Android.
 * Copyright (C) 2016 Zhihu Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.zhihu.android.app.mirror.app;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.app.ActivityManager;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.util.DiffUtil;
import android.support.v7.util.ListUpdateCallback;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;

import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.trello.rxlifecycle.android.ActivityEvent;
import com.trello.rxlifecycle.components.support.RxAppCompatActivity;

import java.util.LinkedList;
import java.util.List;

import com.zhihu.android.app.mirror.R;
import com.zhihu.android.app.mirror.event.ArtboardSelectedEvent;
import com.zhihu.android.app.mirror.event.ViewSwitchedEvent;
import com.zhihu.android.app.mirror.event.MirrorFoundEvent;
import com.zhihu.android.app.mirror.event.MirrorLostEvent;
import com.zhihu.android.app.mirror.event.MirrorMessageEvent;
import com.zhihu.android.app.mirror.event.MirrorResolveFailedEvent;
import com.zhihu.android.app.mirror.event.MirrorResolveSuccessEvent;
import com.zhihu.android.app.mirror.event.WebSocketCloseEvent;
import com.zhihu.android.app.mirror.event.WebSocketFailureEvent;
import com.zhihu.android.app.mirror.event.WifiConnectedEvent;
import com.zhihu.android.app.mirror.event.WifiConnectingEvent;
import com.zhihu.android.app.mirror.event.WifiDisconnectedEvent;
import com.zhihu.android.app.mirror.model.Artboard;
import com.zhihu.android.app.mirror.model.Content;
import com.zhihu.android.app.mirror.model.Message;
import com.zhihu.android.app.mirror.model.MirrorInfo;
import com.zhihu.android.app.mirror.util.AnimUtils;
import com.zhihu.android.app.mirror.util.DisplayUtils;
import com.zhihu.android.app.mirror.util.MirrorUtils;
import com.zhihu.android.app.mirror.util.NetworkUtils;
import com.zhihu.android.app.mirror.util.RxBus;
import com.zhihu.android.app.mirror.widget.ArtboardLayout;
import com.zhihu.android.app.mirror.widget.ArtboardPagerView;
import com.zhihu.android.app.mirror.widget.ArtboardView;
import com.zhihu.android.app.mirror.widget.ConnectStatusLayout;
import com.zhihu.android.app.mirror.widget.adapter.ArtboardListAdapter;
import com.zhihu.android.app.mirror.widget.adapter.ArtboardPagerAdapter;
import com.zhihu.android.app.mirror.widget.adapter.MirrorListAdapter;
import okhttp3.OkHttpClient;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;

public class MainActivity extends RxAppCompatActivity implements ArtboardPagerView.ArtboardPagerViewDelegate,
        ArtboardView.ArtboardViewCallback {
    private static final int FLAG_MIRROR_LIST = ViewSwitchedEvent.FLAG_MIRROR_LIST;
    private static final int FLAG_ARTBOARD_LIST = ViewSwitchedEvent.FLAG_ARTBOARD_LIST;
    private static final int FLAG_ARTBOARD_PAGER = ViewSwitchedEvent.FLAG_ARTBOARD_PAGER;

    private FrameLayout mRootLayout;
    private LinearInterpolator mLinearInterpolator;
    private AccelerateInterpolator mAccelerateInterpolator;
    private String mCurrentManifestId;
    private int mCurrentFlag;

    private ConnectStatusLayout mConnectStatusLayout;
    private String mCurrentSSID;
    private MirrorInfo mCurrentMirror;

    private RecyclerView mMirrorListView;
    private MirrorListAdapter mMirrorListAdapter;
    private List<MirrorInfo> mMirrorInfoList;

    private RecyclerView mArtboardListView;
    private ArtboardListAdapter mArtboardListAdapter;
    private List<Artboard> mArtboardList;

    // share mArtboardList with mArtboardListView
    private ArtboardPagerView mArtboardPagerView;
    private ArtboardPagerAdapter mArtboardPagerAdapter;
    private boolean mIsDragDismiss;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTaskDescription(new ActivityManager.TaskDescription(
                getString(R.string.app_name),
                BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher),
                ContextCompat.getColor(this, R.color.black)));
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        mRootLayout = (FrameLayout) findViewById(R.id.root);
        mLinearInterpolator = new LinearInterpolator();
        mAccelerateInterpolator = new AccelerateInterpolator();
        mCurrentFlag = FLAG_MIRROR_LIST;

        setupFresco();
        setupMirrorListView();
        setupArtboardListView();
        setupArtboardPagerView();
        setupConnectStatusLayout();
        setupRxBus();

        Intent intent = new Intent(this, MirrorService.class);
        startService(intent);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Fresco.shutDown();

        Intent intent = new Intent(this, MirrorService.class);
        stopService(intent);
    }

    // =============================================================================================

    private void setupFresco() {
        OkHttpClient client = new OkHttpClient.Builder().build();
        ImagePipelineConfig config = OkHttpImagePipelineConfigFactory.newBuilder(this, client)
                .setResizeAndRotateEnabledForNetwork(true)
                .setDownsampleEnabled(true)
                .build();
        Fresco.initialize(this, config);
    }

    private void setupMirrorListView() {
        mMirrorInfoList = new LinkedList<>();
        mMirrorListAdapter = new MirrorListAdapter(this, mMirrorInfoList);
        mMirrorListView = (RecyclerView) getLayoutInflater().inflate(R.layout.layout_artboard_list,
                mRootLayout, false);
        mMirrorListView.setAdapter(mMirrorListAdapter);
        mMirrorListView.setLayoutManager(new LinearLayoutManager(this));

        mMirrorListView.setVisibility(View.VISIBLE);
        mRootLayout.addView(mMirrorListView);
    }

    private void setupArtboardListView() {
        mArtboardList = new LinkedList<>();
        mArtboardListAdapter = new ArtboardListAdapter(this, mArtboardList);
        mArtboardListView = (RecyclerView) getLayoutInflater().inflate(R.layout.layout_artboard_list,
                mRootLayout, false);
        mArtboardListView.setAdapter(mArtboardListAdapter);

        final int gridSpanCount = getResources().getInteger(R.integer.grid_span_count);
        GridLayoutManager layoutManager = new GridLayoutManager(this, gridSpanCount);
        layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return position <= 0 || position > mArtboardList.size() ? gridSpanCount : 1;
            }
        });
        mArtboardListView.setLayoutManager(layoutManager);

        mArtboardListView.setVisibility(View.INVISIBLE);
        mRootLayout.addView(mArtboardListView);
    }

    private void setupArtboardPagerView() {
        mArtboardPagerAdapter = new ArtboardPagerAdapter(mArtboardList);
        mArtboardPagerAdapter.setArtboardViewCallback(this);
        mArtboardPagerView = (ArtboardPagerView) getLayoutInflater().inflate(R.layout.layout_artboard_pager,
                mRootLayout, false);
        mArtboardPagerView.setAdapter(mArtboardPagerAdapter);
        mArtboardPagerView.setArtboardPagerViewDelegate(this);
        mArtboardPagerView.setOffscreenPageLimit(getResources().getInteger(R.integer.artboard_pager_limit));
        mArtboardPagerView.setPageMargin(getResources().getDimensionPixelSize(R.dimen.view_pager_margin));

        mArtboardPagerView.setVisibility(View.INVISIBLE);
        mRootLayout.addView(mArtboardPagerView);
    }

    @Override
    public boolean isArtboardPagerViewSwipeEnable() {
        ArtboardLayout layout = mArtboardPagerAdapter.getCurrentLayout();
        return layout != null && !layout.isScaling();
    }

    @Override
    public void onDrag(ArtboardView view, float dragDismissDistance, float dragTo) {
        dragTo = Math.abs(dragTo);
        if (dragTo > dragDismissDistance) {
            dragTo = dragDismissDistance;
        }

        int alpha = (int) (255.0F - 51.0F * dragTo / dragDismissDistance);
        mArtboardPagerView.getBackground().mutate().setAlpha(alpha);
    }

    @Override
    public void onDragDismiss(final ArtboardView view, boolean isDragDown) {
        mIsDragDismiss = true;
        switchToArtboardListView();
        view.animate().alpha(0.0F)
                .translationY(isDragDown ? -view.getHeight() : view.getHeight())
                .setDuration(AnimUtils.DURATION)
                .setInterpolator(new AccelerateInterpolator())
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animator) {
                        mIsDragDismiss = false;
                        mArtboardPagerView.getBackground().mutate().setAlpha(255);
                        view.setTranslationY(0.0F);
                        view.setAlpha(1.0F);
                    }
                })
                .start();
    }

    private void setupConnectStatusLayout() {
        mCurrentSSID = NetworkUtils.getCurrentSSID(this);
        mConnectStatusLayout = (ConnectStatusLayout) getLayoutInflater().inflate(R.layout.layout_connect_status,
                mRootLayout, false);
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.CONNECTING, mCurrentSSID);

        mConnectStatusLayout.setVisibility(View.VISIBLE);
        mRootLayout.addView(mConnectStatusLayout);
    }

    // =============================================================================================

    @Override
    public void onBackPressed() {
        if (mCurrentFlag == FLAG_ARTBOARD_PAGER) {
            switchToArtboardListView();
        } else if (mCurrentFlag == FLAG_ARTBOARD_LIST) {
            switchToMirrorListView();
        } else {
            super.onBackPressed();
        }
    }

    private void switchToMirrorListView() {
        if (mCurrentFlag == FLAG_ARTBOARD_LIST) {
            AnimUtils.showViewAlphaAnim(mArtboardListView, mLinearInterpolator, false);
        } else if (mCurrentFlag == FLAG_ARTBOARD_PAGER) {
            AnimUtils.showViewAlphaAnim(mArtboardPagerView, mLinearInterpolator, false);
            AnimUtils.showViewAlphaAnim(mArtboardListView, mLinearInterpolator, false);
            AnimUtils.showViewAlphaAnim(mConnectStatusLayout, mLinearInterpolator, true);
            DisplayUtils.clearImmersiveStickyMode(this);
        }

        mArtboardList.clear();
        mArtboardPagerAdapter.notifyDataSetChanged();
        mArtboardListAdapter.notifyDataSetChanged();

        mCurrentFlag = FLAG_MIRROR_LIST;
        RxBus.getInstance().post(new ViewSwitchedEvent(FLAG_MIRROR_LIST));
    }

    private void switchToArtboardListView() {
        if (mCurrentFlag == FLAG_MIRROR_LIST) {
            AnimUtils.showViewAlphaAnim(mArtboardListView, mLinearInterpolator, true);
        } else if (mCurrentFlag == FLAG_ARTBOARD_PAGER) {
            TimeInterpolator interpolator;
            if (mIsDragDismiss) {
                interpolator = mAccelerateInterpolator;
            } else {
                interpolator = mLinearInterpolator;
            }
            AnimUtils.showViewAlphaAnim(mArtboardPagerView, interpolator, false);
            AnimUtils.showViewAlphaAnim(mConnectStatusLayout, interpolator, true);
            DisplayUtils.clearImmersiveStickyMode(this);
        }

        mCurrentFlag = FLAG_ARTBOARD_LIST;
        RxBus.getInstance().post(new ViewSwitchedEvent(FLAG_ARTBOARD_LIST));
    }

    private void switchToArtboardPagerView() {
        if (mCurrentFlag != FLAG_ARTBOARD_LIST) {
            return;
        }

        AnimUtils.showViewAlphaAnim(mConnectStatusLayout, mLinearInterpolator, false);
        AnimUtils.showViewAlphaAnim(mArtboardPagerView, mLinearInterpolator, true);
        DisplayUtils.requestImmersiveStickyMode(this);

        mCurrentFlag = FLAG_ARTBOARD_PAGER;
        RxBus.getInstance().post(new ViewSwitchedEvent(FLAG_ARTBOARD_PAGER));
    }

    // =============================================================================================

    // when failed, always back to MirrorListView;
    // and have any better code style?
    private void setupRxBus() {
        RxBus.getInstance().toObservable(WifiConnectingEvent.class)
                .onBackpressureBuffer()
                .compose(this.<WifiConnectingEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<WifiConnectingEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(WifiConnectingEvent event) {
                        onWifiConnectingEvent();
                    }
                });

        RxBus.getInstance().toObservable(WifiConnectedEvent.class)
                .onBackpressureBuffer()
                .compose(this.<WifiConnectedEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<WifiConnectedEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(WifiConnectedEvent event) {
                        onWifiConnectedEvent();
                    }
                });

        RxBus.getInstance().toObservable(WifiDisconnectedEvent.class)
                .onBackpressureBuffer()
                .compose(this.<WifiDisconnectedEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<WifiDisconnectedEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(WifiDisconnectedEvent event) {
                        onWifiDisconnectedEvent();
                    }
                });

        RxBus.getInstance().toObservable(MirrorFoundEvent.class)
                .onBackpressureBuffer()
                .compose(this.<MirrorFoundEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<MirrorFoundEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(MirrorFoundEvent event) {
                        onMirrorFoundEvent(event);
                    }
                });

        RxBus.getInstance().toObservable(MirrorLostEvent.class)
                .onBackpressureBuffer()
                .compose(this.<MirrorLostEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<MirrorLostEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(MirrorLostEvent event) {
                        onMirrorLostEvent(event);
                    }
                });

        RxBus.getInstance().toObservable(MirrorResolveSuccessEvent.class)
                .onBackpressureBuffer()
                .compose(this.<MirrorResolveSuccessEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<MirrorResolveSuccessEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(MirrorResolveSuccessEvent event) {
                        onMirrorResolveSuccessEvent(event);
                    }
                });

        RxBus.getInstance().toObservable(MirrorResolveFailedEvent.class)
                .onBackpressureBuffer()
                .compose(this.<MirrorResolveFailedEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<MirrorResolveFailedEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(MirrorResolveFailedEvent event) {
                        onMirrorResolveFailedEvent(event);
                    }
                });

        RxBus.getInstance().toObservable(WebSocketCloseEvent.class)
                .onBackpressureBuffer()
                .compose(this.<WebSocketCloseEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<WebSocketCloseEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(WebSocketCloseEvent event) {
                        onWebSocketCloseEvent(event);
                    }
                });

        RxBus.getInstance().toObservable(WebSocketFailureEvent.class)
                .onBackpressureBuffer()
                .compose(this.<WebSocketFailureEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<WebSocketFailureEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(WebSocketFailureEvent event) {
                        onWebSocketFailureEvent(event);
                    }
                });

        RxBus.getInstance().toObservable(MirrorMessageEvent.class)
                .onBackpressureBuffer()
                .compose(this.<MirrorMessageEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<MirrorMessageEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(MirrorMessageEvent event) {
                        dispatchMirrorMessageEvent(event);
                    }
                });

        RxBus.getInstance().toObservable(ArtboardSelectedEvent.class)
                .onBackpressureBuffer()
                .compose(this.<ArtboardSelectedEvent>bindUntilEvent(ActivityEvent.DESTROY))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ArtboardSelectedEvent>() {
                    @Override
                    public void onCompleted() {
                        // DO NOTHING
                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(ArtboardSelectedEvent event) {
                        onArtboardSelectedEvent(event);
                    }
                });
    }

    private void onWifiConnectingEvent() {
        mCurrentSSID = NetworkUtils.getCurrentSSID(this);
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.CONNECTING, mCurrentSSID);
    }

    private void onWifiConnectedEvent() {
        mCurrentSSID = NetworkUtils.getCurrentSSID(this);
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.CONNECTED, mCurrentSSID);
    }

    private void onWifiDisconnectedEvent() {
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.DISCONNECTED, mCurrentSSID);
        switchToMirrorListView();
    }

    // just name equals, have a better way?
    private void onMirrorFoundEvent(MirrorFoundEvent event) {
        MirrorInfo mirrorInfo = event.getMirrorInfo();
        int position = -1;

        for (int i = 0 ; i < mMirrorInfoList.size(); i++) {
            MirrorInfo info = mMirrorInfoList.get(i);
            if (TextUtils.equals(info.getName(), mirrorInfo.getName())) {
                position = i;
                break;
            }
        }

        // add 1 for top placeholder
        if (position >= 0) {
            mMirrorInfoList.set(position, mirrorInfo);
            mMirrorListAdapter.notifyItemChanged(position + 1);
        } else {
            mMirrorInfoList.add(mirrorInfo);
            mMirrorListAdapter.notifyItemInserted(mMirrorInfoList.size());
        }
    }

    // just name equals, have a better way?
    private void onMirrorLostEvent(MirrorLostEvent event) {
        MirrorInfo mirrorInfo = event.getMirrorInfo();
        int position = -1;

        for (int i = 0 ; i < mMirrorInfoList.size(); i++) {
            MirrorInfo info = mMirrorInfoList.get(i);
            if (TextUtils.equals(info.getName(), mirrorInfo.getName())) {
                position = i;
                break;
            }
        }

        // add 1 for top placeholder
        if (position >= 0) {
            mMirrorInfoList.remove(position);
            mMirrorListAdapter.notifyItemRemoved(position + 1);
        }

        if (mCurrentMirror != null && TextUtils.equals(mCurrentMirror.getName(), mirrorInfo.getName())) {
            String currentSketch = MirrorUtils.getCurrentSketch(this, mCurrentMirror);
            mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.DISCONNECTED, currentSketch);
            switchToMirrorListView();
        }
    }

    private void onMirrorResolveSuccessEvent(MirrorResolveSuccessEvent event) {
        mCurrentMirror = event.getMirrorInfo();
        String currentSketch = MirrorUtils.getCurrentSketch(this, mCurrentMirror);
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.CONNECTING, currentSketch);
    }

    private void onMirrorResolveFailedEvent(MirrorResolveFailedEvent event) {
        mCurrentMirror = event.getMirrorInfo();
        String currentSketch = MirrorUtils.getCurrentSketch(this, mCurrentMirror);
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.DISCONNECTED, currentSketch);
        switchToMirrorListView();
    }

    private void onWebSocketCloseEvent(WebSocketCloseEvent event) {
        String currentSketch = MirrorUtils.getCurrentSketch(this, mCurrentMirror);
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.DISCONNECTED, currentSketch);
        switchToMirrorListView();
    }

    private void onWebSocketFailureEvent(WebSocketFailureEvent event) {
        String currentSketch = MirrorUtils.getCurrentSketch(this, mCurrentMirror);
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.DISCONNECTED, currentSketch);
        switchToMirrorListView();
    }

    private void dispatchMirrorMessageEvent(MirrorMessageEvent event) {
        Message message = event.getMessage();
        if (TextUtils.equals(message.getType(), MirrorUtils.TYPE_CONNECTED)) {
            onMirrorMessageConnected();
        } else if (TextUtils.equals(message.getType(), MirrorUtils.TYPE_DISCONNECTED)) {
            onMirrorMessageDisconnected();
        } else if (TextUtils.equals(message.getType(), MirrorUtils.TYPE_MANIFEST)) {
            onMirrorMessageManifest(message);
        } else if (TextUtils.equals(message.getType(), MirrorUtils.TYPE_ARTBOARD)) {
            onMirrorMessageArtboard(message);
        } else if (TextUtils.equals(message.getType(), MirrorUtils.TYPE_CURRENT_ARTBOARD)) {
            onMirrorMessageCurrentArtboard(message);
        }
    }

    private void onMirrorMessageConnected() {
        String currentSketch = MirrorUtils.getCurrentSketch(this, mCurrentMirror);
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.CONNECTED, currentSketch);
        switchToArtboardListView();
    }

    private void onMirrorMessageDisconnected() {
        String currentSketch = MirrorUtils.getCurrentSketch(this, mCurrentMirror);
        mConnectStatusLayout.setConnectStatus(ConnectStatusLayout.DISCONNECTED, currentSketch);
        switchToMirrorListView();
    }

    private void onMirrorMessageManifest(Message message) {
        Content content = message.getContent();

        // when id difference, means Sketch file changed
        if (TextUtils.isEmpty(mCurrentManifestId)) {
            mCurrentManifestId = content.getId();
        } else {
            if (!TextUtils.equals(content.getId(), mCurrentManifestId)) {
                mCurrentManifestId = content.getId();
                switchToArtboardListView();
            }
        }

        final List<Artboard> oldList = new LinkedList<>(mArtboardList);
        mArtboardList.clear();
        mArtboardList.addAll(MirrorUtils.getArtboardsFromContent(this, content));
        DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
            @Override
            public int getOldListSize() {
                return oldList.size();
            }

            @Override
            public int getNewListSize() {
                return mArtboardList.size();
            }

            @Override
            public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                Artboard old = oldList.get(oldItemPosition);
                Artboard nez = mArtboardList.get(newItemPosition);
                return TextUtils.equals(old.getId(), nez.getId());
            }

            @Override
            public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                Artboard old = oldList.get(oldItemPosition);
                Artboard nez = mArtboardList.get(newItemPosition);
                nez.setNeedUpdateInPager(old.isNeedUpdateInPager()); // when pager updated then false
                return !old.isNeedUpdateInList() && old.equals(nez);
            }
        });

        // add 1 for top placeholder
        result.dispatchUpdatesTo(new ListUpdateCallback() {
            @Override
            public void onInserted(int position, int count) {
                mArtboardListAdapter.notifyItemRangeInserted(position + 1, count);
            }

            @Override
            public void onRemoved(int position, int count) {
                mArtboardListAdapter.notifyItemRangeRemoved(position + 1, count);
            }

            @Override
            public void onMoved(int fromPosition, int toPosition) {
                mArtboardListAdapter.notifyItemMoved(fromPosition + 1, toPosition + 1);
            }

            @Override
            public void onChanged(int position, int count, Object payload) {
                mArtboardListAdapter.notifyItemRangeChanged(position + 1, count, payload);
            }
        });

        mArtboardPagerAdapter.notifyDataSetChanged();
    }

    // also update when next TYPE_MANIFEST arrive
    private void onMirrorMessageArtboard(Message message) {
        Content content = message.getContent();
        int position = -1;

        for (int i = 0; i < mArtboardList.size(); i++) {
            Artboard artboard = mArtboardList.get(i);
            if (TextUtils.equals(artboard.getId(), content.getIdentifier())) {
                artboard.setNeedUpdateInList(true);
                artboard.setNeedUpdateInPager(true);
                position = i;
                break;
            }
        }

        // add 1 for top placeholder
        if (position >= 0) {
            mArtboardListAdapter.notifyItemChanged(position + 1);
            mArtboardPagerAdapter.notifyDataSetChanged();
            mArtboardPagerView.setCurrentItem(position, false);
        }
    }

    // not much use
    private void onMirrorMessageCurrentArtboard(Message message) {
        String id = message.getContent().getIdentifier();
        int position = -1;

        for (int i = 0; i < mArtboardList.size(); i++) {
            Artboard artboard = mArtboardList.get(i);
            if (TextUtils.equals(artboard.getId(), id)) {
                position = i;
                break;
            }
        }

        if (position >= 0) {
            mArtboardPagerView.setCurrentItem(position, false);
        }
    }

    private void onArtboardSelectedEvent(ArtboardSelectedEvent event) {
        Artboard artboard = event.getArtboard();
        int position = mArtboardList.indexOf(artboard);
        if (position < 0) {
            return;
        }

        mArtboardPagerView.setCurrentItem(position, false);
        switchToArtboardPagerView();
    }
}