/* This Source Code Form is subject to the terms of the Mozilla Public
   License, v. 2.0. If a copy of the MPL was not distributed with this
   file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.library.bookmarks.edit

import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.Navigation
import com.jakewharton.rxbinding3.widget.textChanges
import com.uber.autodispose.AutoDispose
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.fragment_edit_bookmark.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.appservices.places.UrlParseFailed
import mozilla.components.concept.storage.BookmarkInfo
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getColorFromAttr
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.library.bookmarks.BookmarksSharedViewModel
import java.lang.IllegalArgumentException
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext

class EditBookmarkFragment : Fragment(), CoroutineScope {

    private lateinit var sharedViewModel: BookmarksSharedViewModel
    private lateinit var job: Job
    private lateinit var guidToEdit: String
    private var bookmarkNode: BookmarkNode? = null
    private var bookmarkParent: BookmarkNode? = null

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
        setHasOptionsMenu(true)
        sharedViewModel = activity?.run {
            ViewModelProviders.of(this).get(BookmarksSharedViewModel::class.java)
        }!!
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_edit_bookmark, container, false)
    }

    override fun onResume() {
        super.onResume()
        (activity as? AppCompatActivity)?.title = getString(R.string.edit_bookmark_fragment_title)
        (activity as? AppCompatActivity)?.supportActionBar?.show()

        guidToEdit = EditBookmarkFragmentArgs.fromBundle(arguments!!).guidToEdit
        launch(IO) {
            bookmarkNode = requireComponents.core.bookmarksStorage.getTree(guidToEdit)
            bookmarkParent = sharedViewModel.selectedFolder
                ?: bookmarkNode?.parentGuid?.let { requireComponents.core.bookmarksStorage.getTree(it) }

            launch(Main) {
                when (bookmarkNode?.type) {
                    BookmarkNodeType.FOLDER -> {
                        bookmark_url_edit.visibility = View.GONE
                        bookmark_url_label.visibility = View.GONE
                    }
                    BookmarkNodeType.ITEM -> {}
                    BookmarkNodeType.SEPARATOR -> {}
                    else -> throw IllegalArgumentException()
                }
                bookmark_name_edit.setText(bookmarkNode!!.title)
                bookmark_url_edit.setText(bookmarkNode!!.url)
            }

            bookmarkParent?.let { node ->
                launch(Main) {
                    bookmark_folder_selector.text = node.title
                    bookmark_folder_selector.setOnClickListener {
                        sharedViewModel.selectedFolder = null
                        Navigation.findNavController(requireActivity(), R.id.container).navigate(
                            EditBookmarkFragmentDirections
                                .actionBookmarkEditFragmentToBookmarkSelectFolderFragment(null)
                        )
                    }
                }
            }
        }

        updateBookmarkFromObservableInput()
    }

    override fun onPause() {
        updateBookmarkNode(Pair(bookmark_name_edit.text, bookmark_url_edit.text))
        super.onPause()
    }

    private fun updateBookmarkFromObservableInput() {
        Observable.combineLatest(
            bookmark_name_edit.textChanges().skipInitialValue(),
            bookmark_url_edit.textChanges().skipInitialValue(),
            BiFunction { name: CharSequence, url: CharSequence ->
                Pair(name, url)
            })
            .filter { it.first.isNotBlank() && it.second.isNotBlank() }
            .debounce(debouncePeriodInMs, TimeUnit.MILLISECONDS)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .`as`(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this@EditBookmarkFragment)))
            .subscribe {
                try {
                    bookmark_url_edit.error = null
                    updateBookmarkNode(it)
                } catch (e: UrlParseFailed) {
                    bookmark_url_edit.error = getString(R.string.bookmark_invalid_url_error)
                }
            }
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.bookmarks_edit, menu)
        menu.findItem(R.id.delete_bookmark_button).icon.colorFilter =
            PorterDuffColorFilter(R.attr.iconColor.getColorFromAttr(context!!), PorterDuff.Mode.SRC_IN)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.delete_bookmark_button -> {
                launch(IO) {
                    requireComponents.core.bookmarksStorage.deleteNode(guidToEdit)
                    launch(Main) {
                        Navigation.findNavController(requireActivity(), R.id.container).popBackStack()
                    }
                }
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }

    private fun updateBookmarkNode(pair: Pair<CharSequence, CharSequence>) {
        launch(IO) {
            requireComponents.core.bookmarksStorage.updateNode(
                guidToEdit,
                BookmarkInfo(
                    sharedViewModel.selectedFolder?.guid ?: bookmarkNode!!.parentGuid,
                    bookmarkNode!!.position,
                    pair.first.toString(),
                    if (bookmarkNode?.type == BookmarkNodeType.ITEM) pair.second.toString() else null
                )
            )
        }
    }

    companion object {
        private const val debouncePeriodInMs = 500L
    }
}
