// This file is part of Gopher2600.
//
// Gopher2600 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.
//
// Gopher2600 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 Gopher2600.  If not, see <https://www.gnu.org/licenses/>.

package debugger

import (
	"github.com/jetsetilly/gopher2600/debugger/govern"
	"github.com/jetsetilly/gopher2600/debugger/terminal"
	"github.com/jetsetilly/gopher2600/gui"
	"github.com/jetsetilly/gopher2600/hardware"
	"github.com/jetsetilly/gopher2600/userinput"
)

func (dbg *Debugger) forceROMSelector() error {
	dbg.forcedROMselection = make(chan bool, 1)
	err := dbg.gui.SetFeature(gui.ReqROMSelector)
	if err != nil {
		return err
	}

	return nil
}

func (dbg *Debugger) playLoop() error {
	// if forcedROMSelection is active (is not nil) then the event loop is
	// simplified for the duration (until it is set to nil)
	for dbg.forcedROMselection != nil {
		select {
		case sig := <-dbg.events.Signal:
			return dbg.events.SignalHandler(sig)
		case ev := <-dbg.events.PushedFunction:
			ev()
		case ev := <-dbg.events.PushedFunctionImmediate:
			ev()
			return nil
		case ev := <-dbg.events.UserInput:
			if _, ok := ev.(userinput.EventQuit); ok {
				return terminal.UserQuit
			}
		case <-dbg.forcedROMselection:
			dbg.forcedROMselection = nil
		}
	}

	// only check for end of measurement period every PerformanceBrake CPU
	// instructions
	performanceBrake := 0

	// update lastBank at the start of the play loop
	dbg.liveBankInfo = dbg.vcs.Mem.Cart.GetBank(dbg.vcs.CPU.PC.Address())

	// run and handle events
	return dbg.vcs.Run(func() (govern.State, error) {

		// update counters. because of the way LastResult works we need to make
		// sure we only use it in the event that the CPU RdyFlag is set
		//
		// if it isn't then we know that the CPU ticked once before returning
		// and allowing this function to run. meaning the the number of cycles
		// tell the counter to Step() is ColorClocksPerCPUCycle
		//
		// in all other cases the number of cycles to count is ColorClocksPerCPUCycle
		// multiplied by the number of cycles in the instruction
		if dbg.vcs.CPU.RdyFlg {
			dbg.counter.Step(dbg.vcs.CPU.LastResult.Cycles*hardware.ColorClocksPerCPUCycle, dbg.liveBankInfo)
		} else {
			dbg.counter.Step(hardware.ColorClocksPerCPUCycle, dbg.liveBankInfo)
		}

		// we must keep lastBank updated during the play loop
		dbg.liveBankInfo = dbg.vcs.Mem.Cart.GetBank(dbg.vcs.CPU.PC.Address())

		// record state. we do this before any of the conditions below that may
		// result in an early return from the function
		if dbg.state.Load().(govern.State) == govern.Running {
			dbg.Rewind.RecordState()
		}

		// run continueCheck() function is called every CPU instruction. for
		// some halt conditions this is too infrequent
		//
		// for this reason we should never find ourselves in the playLoop if
		// these halt conditions exist. see setMode() function
		dbg.halting.check()
		if dbg.halting.halt {
			// set debugging mode. halting messages will be preserved and
			// shown when entering debugging mode
			dbg.setMode(govern.ModeDebugger)
			return govern.Ending, nil
		}

		if dbg.Mode() != govern.ModePlay {
			return govern.Ending, nil
		}

		// return without checking interface unless we exceed the
		// PerformanceBrake value
		performanceBrake++
		if performanceBrake < hardware.PerformanceBrake {
			return dbg.State(), nil
		}
		performanceBrake = 0

		// how we wait for read events depends on whether the emulation is
		// paused or not. if emulation is paused then we halt until an event is
		// received. this reduces CPU usage when paused
		if dbg.state.Load().(govern.State) == govern.Paused {
			select {
			case <-dbg.readEventsPulse.C:
				err := dbg.readEventsHandler()
				if err != nil {
					return govern.Ending, err
				}
			}
		} else {
			select {
			case <-dbg.readEventsPulse.C:
				err := dbg.readEventsHandler()
				if err != nil {
					return govern.Ending, err
				}
			default:
			}
		}

		// resolve rewinding
		if dbg.rewindKeyboardAccumulation != 0 {
			amount := 0
			if dbg.rewindKeyboardAccumulation < 0 {
				if dbg.rewindKeyboardAccumulation > -100 {
					dbg.rewindKeyboardAccumulation--
				}
				amount = (dbg.rewindKeyboardAccumulation / 10) - 1
			} else {
				if dbg.rewindKeyboardAccumulation < 100 {
					dbg.rewindKeyboardAccumulation++
				}
				amount = (dbg.rewindKeyboardAccumulation / 10) + 1
			}
			dbg.RewindByAmount(amount)
		}

		return dbg.State(), nil
	})
}
