/**
 * ProjectLaogai
 *
 * Copyright 2019-2020  <seil0@mosad.xyz>
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 */

package org.mosad.seil0.projectlaogai.fragments

import android.graphics.Rect
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
import org.mosad.seil0.projectlaogai.databinding.FragmentGradesBinding
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
import kotlin.system.measureTimeMillis

/**
 * The grades fragment class
 * contains all needed parts to display and the grades screen
 */
class GradesFragment : Fragment() {

    private lateinit var parser: QISPOSParser
    private lateinit var binding: FragmentGradesBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = FragmentGradesBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.refreshLayoutGrades.setProgressBackgroundColorSchemeColor(Preferences.themeSecondary)
        initActions()

        parser = QISPOSParser(context!!)// init the parser

        if (checkCredentials()) {
            GlobalScope.launch(Dispatchers.Default) {
                // if the cache is older than 24hr, update blocking
                val currentTime = System.currentTimeMillis() / 1000
                withContext(Dispatchers.Main) {
                    if ((currentTime - Preferences.gradesCacheTime) > 86400 && checkQisposStatus()) {
                        binding.refreshLayoutGrades.isRefreshing = true
                        CacheController.updateGrades(context!!).join()
                    }
                }

                addGrades()
            }
        }
    }

    /**
     * initialize the actions
     */
    private fun initActions() = GlobalScope.launch(Dispatchers.Default) {
        binding.refreshLayoutGrades.setOnRefreshListener {
            binding.linLayoutGrades.removeAllViews() // clear layout
            // TODO add loading textView

            GlobalScope.launch(Dispatchers.Default) {
                CacheController.updateGrades(context!!).join()
                addGrades()
            }
        }
    }

    /**
     * check if the credentials are set, if not show a login dialog
     */
    private fun checkCredentials(): Boolean {
        val credentials = EncryptedPreferences.readCredentials(context!!)
        var credentialsPresent = false

        // if there is no password set, show the login dialog
        if (credentials.first.isEmpty() || credentials.second.isEmpty()) {
            LoginDialog(this.context!!)
                .positiveButton {
                    EncryptedPreferences.saveCredentials(email, password, context)
                    addGrades()
                }
                .negativeButton {
                    binding.txtViewLoading.text = resources.getString(R.string.credentials_missing)
                }
                .show {
                    email = EncryptedPreferences.email
                    password = ""
                }
        } else {
            credentialsPresent = true
        }

        return credentialsPresent
    }

    /**
     * check if qispos is available, if not show an error
     */
    private fun checkQisposStatus(): Boolean {
        val statusCode = parser.checkQISPOSStatus()

        // show error if the status code is not 200
        if (statusCode != 200) {
            val infoText = resources.getString(when(statusCode) {
                503 -> R.string.qispos_unavailable
                else -> R.string.qispos_generic_error
            })

            val img = ResourcesCompat.getDrawable(resources, R.drawable.ic_error_outline_black_24dp, null)?.apply {
                bounds = Rect(0, 0, 75, 75)
            }

            binding.txtViewLoading.apply {
                text = infoText
                setCompoundDrawables(null, null, null, img)
            }
        }

        return statusCode == 200
    }

    /**
     * add the grades to the layout, async
     * TODO this is slow as hell
     */
    private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
        val addGradesTime = measureTimeMillis {
            val grades = CacheController(context!!).readGrades()

            withContext(Dispatchers.Main) {
                binding.linLayoutGrades.removeAllViews() // clear layout
            }

            // TODO this loop takes 3/4 of the time
            // for each semester add a new card
            grades.forEach { semester ->
                val usedSubjects = ArrayList<String>()
                val semesterCard = DayCardView(context!!)
                semesterCard.setDayHeading(semester.key)

                // for each subject add a new linLayout
                semester.value.forEachIndexed { index, subject ->
                    if (usedSubjects.contains(subject.name)) {
                        return@forEachIndexed
                    }

                    // get the first sub subjects
                    val subSubject = semester.value.firstOrNull {
                        it.name.contains(subject.name) && it.name != subject.name
                    }

                    // if sub subject is not null, add it to used subjects
                    subSubject?.let {
                        usedSubjects.add(it.name)
                    }

                    val subjectLayout = GradeLinearLayout(context).set {
                        subjectName = subject.name
                        grade = subject.grade
                        subSubjectName = subSubject?.name.toString()
                        subGrade = subSubject?.grade.toString()
                    }

                    // disable sub-subject if not set
                    if (subSubject == null)
                        subjectLayout.disableSubSubject()

                    // disable divider if last element
                    if (index == semester.value.lastIndex || semester.value.indexOf(subSubject) == semester.value.lastIndex)
                        subjectLayout.disableDivider()

                    semesterCard.getLinLayoutDay().addView(subjectLayout)
                }

                // without context we can't access the view
                withContext(Dispatchers.Main) {
                    binding.linLayoutGrades.addView(semesterCard)
                }
            }

            val txtViewLegal = TextView(context).apply {
                text = resources.getString(R.string.without_guarantee)
                textAlignment = View.TEXT_ALIGNMENT_CENTER
            }

            // stop refreshing and show legal warning
            withContext(Dispatchers.Main) {
                binding.linLayoutGrades.addView(txtViewLegal)
                binding.refreshLayoutGrades.isRefreshing = false
            }

        }
        println("added grades in $addGradesTime ms")
    }

}