BetActivity.kt

/*
Copyright 2017 Hermann Krumrey <hermann@krumreyh.com>

This file is part of bundesliga-tippspiel-android.

bundesliga-tippspiel-android 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.

bundesliga-tippspiel-android 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 bundesliga-tippspiel-android.  If not, see <http://www.gnu.org/licenses/>.
*/

package net.namibsun.hktipp.activities

import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
import android.widget.PopupMenu
import android.widget.TextView
import net.namibsun.hktipp.R
import net.namibsun.hktipp.models.Bet
import net.namibsun.hktipp.models.Match
import net.namibsun.hktipp.models.MinimalBet
import net.namibsun.hktipp.views.BetView
import org.jetbrains.anko.doAsync
import java.io.IOException

/**
 * This activity allows a user to place bets, as well as view already placed bets
 */
class BetActivity : AuthorizedActivity() {

    /**
     * The Bet Views for the currently selected match day
     */
    private val betViews = mutableMapOf<Int, MutableList<BetView>>()

    /**
     * The Match Day to be displayed. -1 indicates that the current match day should be used
     */
    private var matchDay: Int = -1

    /**
     * Initializes the Activity. Sets the OnClickListeners for the buttons and starts
     * fetching bet and match data asynchronously.
     * Sets the username and apiKey instance variables
     * @param savedInstanceState: The Instance Information of the app.
     */
    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        this.setContentView(R.layout.bets)

        this.findViewById<View>(R.id.bets_submit_button).setOnClickListener { this.placeBets() }
        this.findViewById<View>(R.id.bets_prev_button).setOnClickListener {
            this.adjustMatchday(false)
        }
        this.findViewById<View>(R.id.bets_next_button).setOnClickListener {
            this.adjustMatchday(true)
        }

        val menuButton = this.findViewById<View>(R.id.menu_button)
        menuButton.setOnClickListener {
            val popup = PopupMenu(this@BetActivity, menuButton)
            popup.menuInflater.inflate(R.menu.main_menu, popup.menu)
            popup.setOnMenuItemClickListener {
                if (it.itemId == R.id.leaderboard_menu_option) {
                    this@BetActivity.startActivity(LeaderboardActivity::class.java, false)
                } else if (it.itemId == R.id.logout_menu_option) {
                    Log.i("BetActivity", "Logging out.")
                    this.logout()
                }
                true
            }
            popup.show()
        }
        this.updateData()
    }

    /**
     * Starts the loading animation
     */
    override fun startLoadingAnimation() {

        this.findViewById<Button>(R.id.bets_submit_button).isEnabled = false
        this.findViewById<View>(R.id.bets_progress).visibility = View.VISIBLE

        this.findViewById<Button>(R.id.bets_next_button).setOnClickListener { }
        this.findViewById<Button>(R.id.bets_prev_button).setOnClickListener { }

        Log.d("BetActivity", "Clearing old views")
        this.betViews[this.matchDay] = mutableListOf()
        this.renderBetViews()
    }

    /**
     * Stops the loading animation
     */
    override fun stopLoadingAnimation() {

        this.findViewById<Button>(R.id.bets_submit_button).isEnabled = true
        this.findViewById<View>(R.id.bets_progress).visibility = View.VISIBLE

        this.findViewById<Button>(R.id.bets_prev_button).setOnClickListener {
            this.adjustMatchday(false)
        }
        this.findViewById<Button>(R.id.bets_next_button).setOnClickListener {
            this.adjustMatchday(true)
        }
    }

    /**
     * Switches the match day when either the `next` or `previous` buttons are pressed
     * @param incrementing: Specifies if the matchday is getting incremented or decremented
     */
    private fun adjustMatchday(incrementing: Boolean) {

        Log.i("BetActivity", "Switching matchday")

        // Only switch matchday if in valid range
        if ((this.matchDay in 1..33 && incrementing) || (this.matchDay in 2..34 && !incrementing)) {

            if (incrementing) {
                this.matchDay++
            } else {
                this.matchDay--
            }

            this.findViewById<Button>(R.id.bets_prev_button).isEnabled = this.matchDay != 1
            this.findViewById<Button>(R.id.bets_next_button).isEnabled = this.matchDay != 34
            this.updateData()
        }
    }

    /**
     * Updates the data fetched from the API. First, the previous bets are saved, provided they
     * exist at all. Then, the previous data is cleared and a loading animation is shown. The data
     * is fetched asynchronously, then displayed. If fetching the data failed, the user will be
     * logged out after being displayed an error message
     */
    private fun updateData() {

        Log.i("BetActivity", "Updating Data")

        this.startLoadingAnimation()

        this.doAsync {

            try {

                val matchQuery = Match.query(this@BetActivity.apiConnection)
                matchQuery.addFilter("matchday", this@BetActivity.matchDay)
                val matches = matchQuery.query()

                val betQuery = Bet.query(this@BetActivity.apiConnection)
                betQuery.addFilter("matchday", this@BetActivity.matchDay)
                betQuery.addFilter("user_id", this@BetActivity.apiConnection.user.id)
                val bets = betQuery.query()

                Log.i("BetActivity", "Data successfully fetched")

                this@BetActivity.matchDay = matches[0].matchday
                this@BetActivity.betViews[this@BetActivity.matchDay] = mutableListOf()

                this@BetActivity.runOnUiThread {
                    this@BetActivity.initializeBetViews(matches, bets)
                    this@BetActivity.renderBetViews()
                    this@BetActivity.stopLoadingAnimation()
                }
            } catch (e: IOException) { // If failed to fetch data, log out
                this@BetActivity.runOnUiThread {

                    this@BetActivity.showErrorDialog(
                            R.string.bets_fetching_error_title,
                            R.string.bets_fetching_error_body
                    )
                    this@BetActivity.logout()
                }
            }
        }
    }

    /**
     * Initializes the bet views generated by the retrieved data in [updateData].
     * @param matches: The match data retrieved from the API
     * @param bets: The bets data retrieved from the API
     */
    private fun initializeBetViews(matches: List<Match>, bets: List<Bet>) {

        Log.i("BetActivity", "Initializing bet views")

        for (match in matches) {

            var betData: Bet? = null

            // Check for existing bet data
            for (bet in bets) {
                if (bet.match.id == match.id) {
                    betData = bet
                    Log.d("BetActivity", "Bet Data Found for match ${match.id}")
                }
            }

            val betView = BetView(this@BetActivity, match, betData)
            this.betViews[this.matchDay]!!.add(betView)
        }
    }

    /**
     * Removes all current bets from the activity, then adds all of the BetViews
     * found in the [betViews] variable for the current matchday.
     * Also changes the matchday title to the current matchday
     */
    private fun renderBetViews() {

        Log.d("BetActivity", "Rendering Views")

        val title = this.findViewById<TextView>(R.id.bets_title)
        val list = this.findViewById<LinearLayout>(R.id.bets_list)
        val bets = this.betViews[this.matchDay]

        if (this.matchDay != -1) {
            title.text = this.resources.getString(R.string.bets_matchday_title, this.matchDay)
        }

        if (bets != null) {
            list.removeAllViews()
            for (view in bets) {
                list.addView(view)
            }
        }
    }

    /**
     * Places the currently entered bets
     */
    private fun placeBets() {

        Log.i("BetActivity", "Placing Bets")

        val minimalBets = mutableListOf<MinimalBet>()
        for (betView in this.betViews[this.matchDay]!!) {
            val minimalBet = betView.getMinimalBet()
            if (minimalBet != null) {
                minimalBets.add(minimalBet)
            }
            Log.d("BetActivity", "Placing bet $minimalBet")
        }

        this.startLoadingAnimation()

        this.doAsync {
            Bet.place(this@BetActivity.apiConnection, minimalBets)
            this@BetActivity.runOnUiThread {
                this@BetActivity.updateData()
            }
        }
    }
}