package de.hems.trafficsim;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

/**
 * Main user interface class, containing all necessary gui elements and their control flow.
 */
public class MainActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener {
    /**
     * default value of the number of vehicles on the track
     */
    public static final int defaultNoOfVehicles = 25;

    /**
     * default value of the length of the track
     */
    public static final int defaultTrackLength = 100;

    /**
     * default value of brake probability
     */
    public static final float defaultBrakeProb = 0.3f;

    /**
     * default value of the maximum velocity of the vehicles
     */
    public static final float defaultMaxVelocity = 5.0f;

    /**
     * default value of the delay between two simulation steps
     */
    public static final int defaultDelay = 0;

    /**
     * default value of the number of vehicles on the track
     */
    public static final int defaultHistoryLength = 50;

    /**
     * default value of the number of vehicles on the track
     */
    public static final int defaultFrameskip = 0;

    /**
     * the track to show in the activity
     */
    protected Track track;

    /**
     * the surface view on which the renderer draws the track history
     */
    protected TimeRecordView trackView;

    /**
     * the thread which runs the simulation and visualization
     */
    protected Worker worker;

    /**
     * the renderer instance drawing the track history
     */
    protected Renderer renderer;

    /**
     * the layout which keeps the surface view
     */
    protected LinearLayout viewStack;

    /**
     * Utility function to round a float to a given amount of digits.
     *
     * @param number number to round
     * @param digits amount of digits
     * @return rounded number
     */
    public static float round(float number, int digits) {
        float div = (float) Math.pow(10.0f, digits);
        return Math.round(number * div) / div;
    }

    /**
     * Constructor for MainActivity
     *
     * @param savedInstanceState Bundle with previously saved activity state, otherwise null
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.track = new Track(defaultNoOfVehicles, defaultTrackLength, defaultBrakeProb,
                defaultMaxVelocity, defaultDelay, defaultHistoryLength);
        this.viewStack = findViewById(R.id.trackViewStack);
        this.trackView = new TimeRecordView(this, track);

        viewStack.addView(this.trackView);
        this.renderer = new Renderer(track, this.trackView.getHolder());

        SeekBar trackLengthSeekBar = findViewById(R.id.trackLengthSeekBar);
        trackLengthSeekBar.setOnSeekBarChangeListener(this);
        trackLengthSeekBar.setProgress(defaultTrackLength);
        ((TextView)(findViewById(R.id.trackLengthTextView))).setText(String.valueOf(defaultTrackLength));

        SeekBar maxVelocitySeekBar = findViewById(R.id.maxVelocitySeekBar);
        maxVelocitySeekBar.setOnSeekBarChangeListener(this);
        maxVelocitySeekBar.setProgress((int)defaultMaxVelocity);
        ((TextView)(findViewById(R.id.maxVeloTextView))).setText(String.valueOf((int)defaultMaxVelocity));

        SeekBar noOfVehiclesSeekBar = findViewById(R.id.noOfVehiclesSeekBar);
        noOfVehiclesSeekBar.setOnSeekBarChangeListener(this);
        noOfVehiclesSeekBar.setProgress(defaultNoOfVehicles);
        ((TextView)(findViewById(R.id.noOfVehiclesTextView))).setText(String.valueOf(defaultNoOfVehicles));

        SeekBar brakeProbabilitySeekBar = findViewById(R.id.brakeProbabilitySeekBar);
        brakeProbabilitySeekBar.setOnSeekBarChangeListener(this);
        brakeProbabilitySeekBar.setProgress((int)(defaultBrakeProb*20));
        ((TextView)(findViewById(R.id.brakeProbTextView))).setText(String.valueOf(defaultBrakeProb));

        SeekBar delaySeekBar = findViewById(R.id.simDelaySeekBar);
        delaySeekBar.setOnSeekBarChangeListener(this);
        delaySeekBar.setProgress(defaultDelay);
        ((TextView) (findViewById(R.id.simDelayTextView))).setText(String.valueOf(defaultDelay));

        SeekBar frameskipSeekBar = findViewById(R.id.frameskipSeekBar);
        frameskipSeekBar.setOnSeekBarChangeListener(this);
        frameskipSeekBar.setProgress(defaultFrameskip);
        ((TextView) (findViewById(R.id.frameSkipTextView))).setText(String.valueOf(defaultFrameskip));

        this.updateStats();
    }

    /**
     * Updates the statistics view.
     */
    public void updateStats() {
        final Track trackRef = this.track;
        final TextView lastAvgView = findViewById(R.id.avgVeloLastView);
        final TextView overallAvgView = findViewById(R.id.avgVeloOverallView);
        final TextView delayedAvgView = findViewById(R.id.delayedAvgTextView);
        final TextView stepsView = findViewById(R.id.stepsTextView);
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                lastAvgView.setText(String.valueOf(round(trackRef.getLastAvg(), 2)));
                overallAvgView.setText(String.valueOf(round(trackRef.getOverallAvg(), 2)));
                stepsView.setText(String.valueOf(trackRef.getSteps()));
                delayedAvgView.setText(String.valueOf(round(trackRef.getDelayedAvg(), 2)));
            }
        });
    }

    /**
     * Handler function for clicks on the "Step" button.
     *
     * @param view the view the event is generated from
     */
    public void onStepButtonClick(View view) {
        this.track.timeElapse();
        this.updateStats();
        this.renderer.draw();
    }

    /**
     * Handler function for clicks on the "Play" button.
     *
     * @param view the view the event is generated from
     */
    public void onPlayButtonClick(View view) {
        Button playButton = findViewById(R.id.playButton);
        playButton.setEnabled(false);
        Button stepButton = findViewById(R.id.stepButton);
        stepButton.setEnabled(false);
        Button stopButton = findViewById(R.id.stopButton);
        stopButton.setEnabled(true);
        Button clearButton = findViewById(R.id.clearButton);
        clearButton.setEnabled(false);

        int frameskip = ((SeekBar) (findViewById(R.id.frameskipSeekBar))).getProgress();
        this.worker = new Worker(track, this, renderer, frameskip);
        this.worker.start();
    }

    /**
     * Handler function for clicks on the "Stop" button.
     *
     * @param view the view the event is generated from
     */
    public void onStopButtonClick(View view) {
        Button playButton = findViewById(R.id.playButton);
        playButton.setEnabled(true);
        Button stepButton = findViewById(R.id.stepButton);
        stepButton.setEnabled(true);
        Button stopButton = findViewById(R.id.stopButton);
        stopButton.setEnabled(false);
        Button clearButton = findViewById(R.id.clearButton);
        clearButton.setEnabled(true);

        this.stopWorker();
        this.worker = null;
    }

    /**
     * Stops the current worker thread.
     */
    protected void stopWorker() {
        this.worker.setStop(true);
        try {
            this.worker.join();
        } catch (InterruptedException ex) {
        }
        this.worker = null;
    }

    /**
     * Handler function for clicks on the "Stop" button.
     *
     * @param view the view the event is generated from
     */
    public void onClearButtonClick(View view) {
        if (this.worker != null) { // There was a simulation running
            this.stopWorker();
        }
        Button playButton = findViewById(R.id.playButton);
        playButton.setEnabled(true);
        Button stepButton = findViewById(R.id.stepButton);
        stepButton.setEnabled(true);
        Button stopButton = findViewById(R.id.stopButton);
        stopButton.setEnabled(false);
        this.updateTrack();
        this.updateStats();
        this.renderer.draw();

    }

    /**
     * Creates a new track with the current settings. This methods restarts the simulation it
     * it was running before.
     */
    protected void updateTrack() {
        int newTrackLength = ((SeekBar) (findViewById(R.id.trackLengthSeekBar))).getProgress();
        SeekBar noOfVehiclesSeekBar = findViewById(R.id.noOfVehiclesSeekBar);
        int newNoOfVehicles = noOfVehiclesSeekBar.getProgress();
        if (newTrackLength < newNoOfVehicles) { // Dont allow values greater than track length!
            newNoOfVehicles = newTrackLength;
            noOfVehiclesSeekBar.setProgress(newNoOfVehicles);

        }
        TextView noOfVehiclesTextView = findViewById(R.id.noOfVehiclesTextView);
        noOfVehiclesTextView.setText(String.valueOf(newNoOfVehicles));

        TextView trackLengthTextView = findViewById(R.id.trackLengthTextView);
        trackLengthTextView.setText(String.valueOf(newTrackLength));

        float newMaxVelocity = ((SeekBar) findViewById(R.id.maxVelocitySeekBar)).getProgress();
        SeekBar brakeProbabilitySeekBar = findViewById(R.id.brakeProbabilitySeekBar);
        float newBrakeProb = (float) brakeProbabilitySeekBar.getProgress()
                / (float)brakeProbabilitySeekBar.getMax();

        int newDelay = ((SeekBar)(findViewById(R.id.simDelaySeekBar))).getProgress();
        int newFrameskip = ((SeekBar)(findViewById(R.id.frameskipSeekBar))).getProgress();

        boolean wasRunning = this.worker != null;
        if (wasRunning) {
            this.worker.setStop(true);
            try {
                this.worker.join();
            } catch (InterruptedException ex) {
            }
        }

        this.track = new Track(newNoOfVehicles, newTrackLength, newBrakeProb, newMaxVelocity,
                newDelay, defaultHistoryLength);
        this.renderer.setTrack(this.track);

        if (wasRunning) {
            this.worker = new Worker(track, this, renderer, newFrameskip);
            this.worker.start();
        }
    }

    /**
     * Handler function for changes on the seek bars.
     *
     * @param seekBar  the seek bar changed
     * @param progress the new progress value of the seek bar
     * @param fromUser flag if the event is the result of an user action
     */
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        if (seekBar == findViewById(R.id.noOfVehiclesSeekBar)) {
            this.updateTrack();
        } else if (seekBar == findViewById(R.id.brakeProbabilitySeekBar)) {
            float newBrakeProb = (float) seekBar.getProgress() / (float) seekBar.getMax();
            this.track.setBrakeProb(newBrakeProb);
            TextView newBrakeProbTextView = findViewById(R.id.brakeProbTextView);
            newBrakeProbTextView.setText(String.valueOf((newBrakeProb)));
        } else if (seekBar == findViewById(R.id.maxVelocitySeekBar)) {
            this.track.setMaxVelocity(seekBar.getProgress());
            TextView tv = findViewById(R.id.maxVeloTextView);
            tv.setText(String.valueOf(progress));
        } else if (seekBar == findViewById(R.id.trackLengthSeekBar)) {
            this.updateTrack();
        } else if (seekBar == findViewById(R.id.simDelaySeekBar)) {
            TextView tv = findViewById(R.id.simDelayTextView);
            tv.setText(String.valueOf(seekBar.getProgress()));
            this.track.setWaitTime(seekBar.getProgress());
        } else if (seekBar == findViewById(R.id.frameskipSeekBar)) {
            TextView tv = findViewById(R.id.frameSkipTextView);
            tv.setText(String.valueOf(seekBar.getProgress()));
            if (this.worker != null) {
                this.worker.setFrameskip(seekBar.getProgress());
            }
        }
    }

    /**
     * Handler function for beginning touch events on a seek bar.
     *
     * @param seekBar the seek bar touched
     */
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }

    /**
     * Handler function for ending touch events on a seek bar.
     *
     * @param seekBar the seek bar touched
     */
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }
}