/*
 * Copyright (C) 2016 Simon Norberg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.simno.klingar.ui;

import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v4.media.session.PlaybackStateCompat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.Spinner;

import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.core.widget.ContentLoadingProgressBar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.bluelinelabs.conductor.Router;
import com.bluelinelabs.conductor.RouterTransaction;
import com.google.android.gms.cast.framework.CastButtonFactory;

import net.simno.klingar.KlingarApp;
import net.simno.klingar.R;
import net.simno.klingar.data.ServerManager;
import net.simno.klingar.data.model.Album;
import net.simno.klingar.data.model.Artist;
import net.simno.klingar.data.model.Library;
import net.simno.klingar.data.model.MediaType;
import net.simno.klingar.data.model.PlexItem;
import net.simno.klingar.data.model.Track;
import net.simno.klingar.data.repository.MusicRepository;
import net.simno.klingar.playback.MusicController;
import net.simno.klingar.playback.QueueManager;
import net.simno.klingar.ui.adapter.MusicAdapter;
import net.simno.klingar.ui.widget.DividerItemDecoration;
import net.simno.klingar.ui.widget.EndScrollListener;
import net.simno.klingar.util.Rx;

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

import javax.inject.Inject;

import butterknife.BindDrawable;
import butterknife.BindView;
import butterknife.OnClick;
import timber.log.Timber;

import static com.bluelinelabs.conductor.rxlifecycle2.ControllerEvent.DETACH;
import static net.simno.klingar.data.Key.PLEX_ITEM;
import static net.simno.klingar.util.Views.visible;

public class BrowserController extends BaseController implements
    MusicAdapter.OnPlexItemClickListener, EndScrollListener.EndListener,
    AdapterView.OnItemSelectedListener {

  private static final int PAGE_SIZE = 50;
  private final MusicAdapter adapter;
  @BindView(R.id.toolbar_libs_spinner) Spinner toolbarSpinner;
  @BindView(R.id.content_loading) ContentLoadingProgressBar contentLoading;
  @BindView(R.id.recycler_view) RecyclerView recyclerView;
  @BindView(R.id.miniplayer_container) FrameLayout miniplayerContainer;
  @BindDrawable(R.drawable.item_divider) Drawable itemDivider;
  @Inject ServerManager serverManager;
  @Inject MusicRepository musicRepository;
  @Inject QueueManager queueManager;
  @Inject MusicController musicController;
  @Inject Rx rx;
  private EndScrollListener endScrollListener;
  private List<Library> libs = Collections.emptyList();
  private Library currentLib;
  private MediaType mediaType;
  private int currentPage = -1;
  private boolean isLoading;
  private boolean serverRefreshed;

  public BrowserController(Bundle args) {
    super(args);
    adapter = new MusicAdapter(this);
  }

  @Override protected int getLayoutResource() {
    return R.layout.controller_browser;
  }

  @Override protected void injectDependencies() {
    if (getActivity() != null) {
      KlingarApp.get(getActivity()).component().inject(this);
    }
  }

  @NonNull @Override
  protected View onCreateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
    View view = super.onCreateView(inflater, container);

    PlexItem plexItem = getArgs().getParcelable(PLEX_ITEM);
    if (plexItem instanceof MediaType) {
      mediaType = (MediaType) plexItem;
    }

    ActionBar actionBar = null;
    if (getActivity() != null) {
      actionBar = ((KlingarActivity) getActivity()).getSupportActionBar();
    }
    if (actionBar != null) {
      setHasOptionsMenu(true);
      if (mediaType == null) {
        actionBar.setDisplayHomeAsUpEnabled(false);
        actionBar.setDisplayShowTitleEnabled(false);
      } else {
        actionBar.setDisplayHomeAsUpEnabled(true);
        actionBar.setDisplayShowTitleEnabled(true);
        actionBar.setTitle(mediaType.title());
      }
    }

    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    recyclerView.setHasFixedSize(true);
    recyclerView.addItemDecoration(new DividerItemDecoration(itemDivider));

    contentLoading.hide();

    return view;
  }

  @Override protected void onAttach(@NonNull View view) {
    super.onAttach(view);
    recyclerView.setAdapter(adapter);
    if (mediaType == null) {
      if (!serverRefreshed) {
        serverRefreshed = true;
        serverManager.refresh();
      }
      observeLibs();
    } else {
      startEndlessScrolling();
      if (currentPage < 0) {
        currentPage = 0;
        browseMediaType();
      }
    }
    observePlayback();
  }

  @Override protected void onDetach(@NonNull View view) {
    super.onDetach(view);
    recyclerView.clearOnScrollListeners();
    recyclerView.setAdapter(null);
  }

  @Override public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
    super.onCreateOptionsMenu(menu, inflater);
    inflater.inflate(R.menu.menu_main, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
        R.id.media_route_menu_item);
  }

  @Override public void endReached() {
    if (mediaType != null) {
      browseMediaType();
    }
  }

  @Override public void onPlexItemClicked(PlexItem plexItem) {
    if (plexItem instanceof MediaType) {
      goToMediaType((MediaType) plexItem);
    } else if (plexItem instanceof Artist) {
      goToDetails(plexItem);
    } else if (plexItem instanceof Album) {
      goToDetails(plexItem);
    } else if (plexItem instanceof Track) {
      playTrack((Track) plexItem);
    }
  }

  @OnClick(R.id.miniplayer_container) void onMiniplayerClicked() {
    getRouter().pushController(RouterTransaction.with(new PlayerController(null)));
  }

  private void observeLibs() {
    disposables.add(serverManager.libs()
        .compose(bindUntilEvent(DETACH))
        .compose(rx.flowableSchedulers())
        .subscribe(libs -> {
          BrowserController.this.libs = libs;

          ArrayList<String> libNames = new ArrayList<>();
          int currentPosition = 0;
          for (int i = 0; i < libs.size(); ++i) {
            Library lib = libs.get(i);
            libNames.add(lib.name());
            if (lib.equals(currentLib)) {
              currentPosition = i;
            }
          }

          if (getActivity() != null) {
            ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), R.layout.item_spinner,
                libNames);
            toolbarSpinner.setAdapter(adapter);
            toolbarSpinner.setSelection(currentPosition);
            toolbarSpinner.setOnItemSelectedListener(this);
            visible(toolbarSpinner);
          }
        }, Rx::onError));
  }

  private void browseLibrary(Library lib) {
    if (lib.equals(currentLib)) {
      return;
    }
    currentLib = lib;
    disposables.add(musicRepository.browseLibrary(lib)
        .compose(bindUntilEvent(DETACH))
        .compose(rx.singleSchedulers())
        .subscribe(adapter::set, Rx::onError));
  }

  private void browseMediaType() {
    if (isLoading) {
      return;
    }
    isLoading = true;
    disposables.add(musicRepository.browseMediaType(mediaType, PAGE_SIZE * currentPage)
        .compose(bindUntilEvent(DETACH))
        .compose(rx.singleSchedulers())
        .subscribe(items -> {
          if (items.isEmpty()) {
            stopEndlessScrolling();
          } else {
            adapter.addAll(items);
          }
          currentPage++; // Only increment page if current page was loaded successfully
          isLoading = false;
        }, Rx::onError));
  }

  private void startEndlessScrolling() {
    endScrollListener = new EndScrollListener((LinearLayoutManager)
        recyclerView.getLayoutManager(), this);
    recyclerView.addOnScrollListener(endScrollListener);
  }

  private void stopEndlessScrolling() {
    recyclerView.removeOnScrollListener(endScrollListener);
  }

  private void observePlayback() {
    disposables.add(musicController.state()
        .compose(bindUntilEvent(DETACH))
        .compose(rx.flowableSchedulers())
        .subscribe(state -> {
          switch (state) {
            case PlaybackStateCompat.STATE_ERROR:
            case PlaybackStateCompat.STATE_NONE:
            case PlaybackStateCompat.STATE_STOPPED:
              for (Router router : getChildRouters()) {
                removeChildRouter(router);
              }
              break;
            default:
              Router miniplayerRouter = getChildRouter(miniplayerContainer);
              if (!miniplayerRouter.hasRootController()) {
                miniplayerRouter.setRoot(RouterTransaction.with(new MiniPlayerController(null)));
              }
          }
        }, Rx::onError));
  }

  private void goToMediaType(MediaType mediaType) {
    Bundle args = new Bundle();
    args.putParcelable(PLEX_ITEM, mediaType);
    getRouter().pushController(RouterTransaction.with(new BrowserController(args)));
  }

  private void goToDetails(PlexItem plexItem) {
    Bundle args = new Bundle();
    args.putParcelable(PLEX_ITEM, plexItem);
    getRouter().pushController(RouterTransaction.with(new DetailController(args)));
  }

  private void playTrack(Track track) {
    Timber.d("playTrack %s", track);
    disposables.add(musicRepository.createPlayQueue(track)
        .compose(bindUntilEvent(DETACH))
        .compose(rx.singleSchedulers())
        .subscribe(pair -> {
          queueManager.setQueue(pair.first, pair.second);
          musicController.play();
        }, Rx::onError));
  }

  @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
    if (position < libs.size()) {
      browseLibrary(libs.get(position));
    }
  }

  @Override public void onNothingSelected(AdapterView<?> parent) {
  }
}