package edu.whs.spin2018.baoapp.views.spiel;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.IBinder;
import android.support.constraint.ConstraintLayout;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import edu.whs.spin2018.baoapp.R;
import edu.whs.spin2018.baoapp.model.spiel.MuldenVeraenderung;
import edu.whs.spin2018.baoapp.model.spiel.Position;
import edu.whs.spin2018.baoapp.model.spiel.Spielzug;
import edu.whs.spin2018.baoapp.model.spiel.spieler.SpielerIndex;
import edu.whs.spin2018.baoapp.presenter.BaoSpielPresenter;
import edu.whs.spin2018.baoapp.service.BaoService;
import edu.whs.spin2018.baoapp.service.Konstanten;
import edu.whs.spin2018.baoapp.model.remote.spielbrett.SpielbrettRemoteDataSource;
import edu.whs.spin2018.baoapp.model.remote.spielbrett.SpielbrettServiceInteractor;
import edu.whs.spin2018.baoapp.utilities.MuldenUtils;
import edu.whs.spin2018.baoapp.utilities.SharedPrefUtils;
import edu.whs.spin2018.baoapp.utilities.ViewUtils;
import edu.whs.spin2018.baoapp.views.OnlineActivity;

/**
 * Die Klasse BaoSpielActivity stellt das BaoSpiel auf dem Display dar und steuert die Interaktion
 * mit dem Spiel.
 */

public class BaoSpielActivity extends OnlineActivity implements BaoSpielContract.View {


    public static final int ANZAHL_ZEILEN = 4;
    public static final int MULDEN_TEILER = 5;
    public static final int DAUER_STEINE_AUS_HAND_AUFTEILEN_ANIMATION = 1000;
    public static final int DAUER_KLAUE_STEINE_AUS_MULDEN_ANIMATION = 1000;
    public static final int DAUER_BEWEGE_STEINE_ZUR_HAND_ANIMATION = 1000;
    private static final int ANZAHL_SPALTEN = 8;
    private static final String TAG = "SpielbrettTAG";
    private int steinBreite;

    /**
     * Viewvariablen
     */
    private TextView spielerEins;
    private TextView spielerZwei;
    private TextView spielerEinsTimerAnzeige;
    private TextView spielerZweiTimerAnzeige;
    private TextView aktuelleTimerAnzeige;
    private HandView spielerhand;
    private HandView gegnerhand;
    private HandView aktuelleHand;
    private MuldenView[][] muldenAnzeige;
    private ConstraintLayout mSpielbrett;
    /**
     * Variablen die den Ablauf mit Android erleichtern.
     */
    private Intent spielinformationen;
    private BaoSpielContract.Presenter presenter;

    private SpielBeendetDialog spielBeendetDialog;

    /**
     * Führt die Click-Routine nach einem MuldenClick aus.
     */
    private View.OnClickListener listener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            PositionsButton p = (PositionsButton) view;
            Position position = p.gibPosition();
            presenter.onMuldenKlick(position);
        }
    };

    private ArrayList<AnimatorSet> zugAnimationen;
    private int aktuelleAnimationIndex;

    private Animator.AnimatorListener standardZugAnimationsListener = new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
            //nichts
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            aktuelleAnimationIndex++;
            starteNaechsteZugAnimation();
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            //nichts
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
            //nichts
        }
    };

    /**
     * @param savedInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_spielbrett);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        initView();
    }

    /**
     * Setzt die Spielernamen in die TextViews der Activity.
     */
    private void setzeSpielerNamen() {

        spielerEins = findViewById(edu.whs.spin2018.baoapp.R.id.spielername);
        spielerEinsTimerAnzeige = findViewById(R.id.spielerzeit);
        spielerZwei = findViewById(R.id.gegnername);
        spielerZweiTimerAnzeige = findViewById(R.id.gegnerzeit);

        ViewUtils.setzeSchatten(spielerEins);
        ViewUtils.setzeSchatten(spielerEinsTimerAnzeige);
        ViewUtils.setzeSchatten(spielerZwei);
        ViewUtils.setzeSchatten(spielerZweiTimerAnzeige);

        String username = SharedPrefUtils.gibBenutzername(this);
        spielerEins.setText(username);

        String nameSpielerZwei = spielinformationen.getStringExtra(Konstanten.EXTRA_GEGNER_NAME);
        spielerZwei.setText(nameSpielerZwei);

        if (spielinformationen.getStringExtra(Konstanten.EXTRA_SPIELTYP).equals("lokalesSpiel")) {
            spielerZwei.setRotation(180);
            spielerZweiTimerAnzeige.setRotation(180);
        }
    }

    /**
     * Positionsbuttons werden geladen und kriegen einen Listener. Zusätzlich werden Sie in das
     * 2D-Array @muldenAnzeige geladen.
     */
    private void initialisiereMulden() {
        SteinView steinView;
        PositionsButton aktuelleMulde;
        PositionAufBildschirm p;
        int spielerButton;
        aktuelleMulde = findViewById(R.id.mulde00);
        float muldenGroesse = aktuelleMulde.getWidth();
        float muldenRadius = aktuelleMulde.getPivotX();

        steinBreite = (int) muldenGroesse / MULDEN_TEILER;

        MuldenView aktuelleMuldenView;

        spielerhand = new HandView(findViewById(R.id.spielerhand), false);
        gegnerhand = new HandView(findViewById(R.id.gegnerhand), true);

        AnimatorSet fertigeAnimation = new AnimatorSet();
        ArrayList<Animator> anis = new ArrayList<>(ANZAHL_SPALTEN * ANZAHL_ZEILEN);

        for (int i = 0; i < ANZAHL_ZEILEN; i++) {
            for (int j = 0; j < ANZAHL_SPALTEN; j++) {
                spielerButton = getResources().getIdentifier("mulde" + i + j, "id", getPackageName());
                aktuelleMulde = findViewById(spielerButton);
                aktuelleMulde.setOnClickListener(listener);
                aktuelleMulde.setzePosition(new Position(i, j));

                //Position der Mulde auf dem Bildschirm beschaffen
                p = new PositionAufBildschirm(aktuelleMulde.getX(), aktuelleMulde.getY());

                //MuldenView Objekt zu dieser Mulde anlegen und zwei Steine hinzufuegen
                aktuelleMuldenView = new MuldenView(p, muldenRadius);


                for (int k = 0; k < 2; k++) {
                    steinView = erzeugeSteinView(steinBreite, aktuelleMuldenView.gibZufaelligePositionInMulde(steinBreite));
                    aktuelleMuldenView.fuegeSteinHinzu(steinView);
                    anis.add(ViewUtils.bewegeSteinZuPosition(steinView, steinView.gibPositionAufBildschirm()));
                }


                muldenAnzeige[i][j] = aktuelleMuldenView;
            }
        }
        fertigeAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
        fertigeAnimation.playTogether(anis);

        fertigeAnimation.setDuration(1);
        fertigeAnimation.start();


    }


    /**
     * Aktualisiert die Muldenanzeige in der Activity.
     *
     * @param spielzug der komplette Spielzug
     */
    public void aktualisiereMuldenAnzeige(Spielzug spielzug) {
        MuldenView aktuelleMuldenView;
        Position aktuellePosition;
        MuldenVeraenderung aktuelleVeraenderung;
        ArrayList<MuldenVeraenderung> veraenderungen = spielzug.gibVeraenderungen();
        ArrayList<AnimatorSet> fertigeAnimationen = new ArrayList<>();
        List<MuldenVeraenderung> veraenderungenTeilzug;
        int alteAnzahlSteineInMulde;
        int schrittweite;

        Spielzug aktuellerSpielzug = spielzug;

        aktuelleHand = spielzug.gibAusfuehrendenSpieler().istSpielerEins()
                ? spielerhand
                : gegnerhand;

        for (int i = 0; i < veraenderungen.size(); i++) {
            aktuelleVeraenderung = veraenderungen.get(i);
            aktuellePosition = aktuelleVeraenderung.gibMulde().gibPosition();
            aktuelleMuldenView = MuldenUtils.gibMuldenViewVonPosition(muldenAnzeige,
                    aktuellePosition);
            Log.i("STEIN_POS", "Stein Breite: " + steinBreite + "| Stein Pivot: " + aktuelleMuldenView.gibSteine().get(0).gibPivotX());
            Log.d("MULDEN_AKTION", aktuelleVeraenderung.gibMuldenAktion().name());
            switch (aktuelleVeraenderung.gibMuldenAktion()) {
                //Entfernt Steine
                case ENTFERNE_STEINE:
                    alteAnzahlSteineInMulde = aktuelleMuldenView.gibAnzahlSteine();
                    schrittweite = alteAnzahlSteineInMulde;

                    fertigeAnimationen.add(bewegeSteineZuHand(aktuelleMuldenView));

                    //i+1, da die aktuelle Mulde schon in der obigen Methode behandelt wurde.
                    //schrittweite + 1, da subList das Elements an der Stelle "schrittweite" nicht
                    //mitliefert
                    veraenderungenTeilzug = veraenderungen.subList(i + 1, i + schrittweite + 1);

                    fertigeAnimationen.add(teileSteineAusHandAuf(MuldenUtils
                            .gibMuldenViewsVonVereanderungen(muldenAnzeige, veraenderungenTeilzug)));

                    i += schrittweite - 1;
                    break;

                case KLAUE_STEINE:
                    fertigeAnimationen.add(klaueSteineAusMulde(aktuelleMuldenView,
                            MuldenUtils.gibMuldenViewVonPosition(muldenAnzeige,
                                    veraenderungen.get(i + 1).gibMulde().gibPosition())));
                    break;
            }
        }

        zugAnimationen = fertigeAnimationen;
        aktuelleAnimationIndex = 0;
        starteNaechsteZugAnimation();
    }

    /**
     * Startet nach Ablauf der letzen Teilanimation die naechste Animation, solange noch eine
     * vorhanden ist.
     */
    private void starteNaechsteZugAnimation() {
        runOnUiThread(() -> {
            if (aktuelleAnimationIndex < zugAnimationen.size()) {
                zugAnimationen.get(aktuelleAnimationIndex).start();
            } else {
                presenter.onSpielzugAnzeigeAbgeschlossen();
            }
        });

    }

    /**
     * Erstellt die Animation um Steine aus einer Mulde zu klauen und in eine andere Mulde zu
     * bewegen.
     *
     * @param aktuelleMuldenView - Mulde, aus der die Steine geklaut werden sollen.
     * @param zielMulde          - Mulde, in die die Steine gelegt werden sollen.
     * @return - Teilanimation um die Steine aus einer Mulde zu klauen.
     */
    private AnimatorSet klaueSteineAusMulde(MuldenView aktuelleMuldenView, MuldenView zielMulde) {
        ArrayList<Animator> animators = new ArrayList<>(aktuelleMuldenView.gibAnzahlSteine());
        AnimatorSet animatorSet = new AnimatorSet();

        for (SteinView stein : aktuelleMuldenView.gibSteine()) {
            animators.add(ViewUtils.bewegeSteinZuPosition(stein,
                    zielMulde.gibZufaelligePositionInMulde(steinBreite)));
        }
        zielMulde.fuegeSteineHinzu(aktuelleMuldenView.gibSteine());
        aktuelleMuldenView.entferneSteine();

        animatorSet.setDuration(DAUER_KLAUE_STEINE_AUS_MULDEN_ANIMATION);
        animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
        animatorSet.addListener(standardZugAnimationsListener);
        animatorSet.playTogether(animators);
        return animatorSet;
    }

    /**
     * Teilt die Steine, die sich in der Hand befinden auf die Zielmulden auf.
     *
     * @param zielMulden - Mulden, auf die die Steine aufgeteilt werden sollen
     * @return Animatoren um die Verteilung darzustellen
     */
    private AnimatorSet teileSteineAusHandAuf(ArrayList<MuldenView> zielMulden) {
        ArrayList<Animator> animators = new ArrayList<>(aktuelleHand.gibAnzahlSteineInHand());
        AnimatorSet animatorSet = new AnimatorSet();

        //Steine aus Hand holen und aus Hand entfernen
        ArrayList<SteinView> steineAusHand = aktuelleHand.gibSteineInHand();

        MuldenView ziel;
        for (int i = 0; i < aktuelleHand.gibAnzahlSteineInHand(); i++) {

            //Zielmulde holen und Stein hinzufuegen
            ziel = zielMulden.get(i);
            ziel.fuegeSteinHinzu(steineAusHand.get(steineAusHand.size() - 1 - i));

            animators.add(ViewUtils.bewegeSteinZuPosition(steineAusHand.get(steineAusHand.size() - 1 - i), ziel.gibZufaelligePositionInMulde(steinBreite)));
        }
        aktuelleHand.entferneSteine();


        animatorSet.playSequentially(animators);
        animatorSet.addListener(standardZugAnimationsListener);
        animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
        animatorSet.setDuration(DAUER_STEINE_AUS_HAND_AUFTEILEN_ANIMATION);
        return animatorSet;
    }

    /**
     * Nimmt die Steine aus einer Mulde und bewegt diese zur Hand.
     *
     * @param aktuelleMulde - Mulde, aus der die Steine in die Hand verschoben werden sollen.
     * @return Teilanimation um die Steine aus einer Mulde in die Hand zu verschieben
     */
    private AnimatorSet bewegeSteineZuHand(MuldenView aktuelleMulde) {
        ArrayList<SteinView> steine = aktuelleMulde.gibSteine();
        ArrayList<Animator> animators = new ArrayList<>(steine.size());
        AnimatorSet animatorSet = new AnimatorSet();
        PositionAufBildschirm positionInHand;

        for (SteinView stein : steine) {
            positionInHand = aktuelleHand.gibNaechstePositionInHand(steinBreite);
            animators.add(ViewUtils.bewegeSteinZuPosition(stein, positionInHand));
        }
        aktuelleHand.setzeSteineInHand(steine);

        animatorSet.setDuration(DAUER_BEWEGE_STEINE_ZUR_HAND_ANIMATION);
        animatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
        animatorSet.playTogether(animators);
        animatorSet.addListener(standardZugAnimationsListener);
        return animatorSet;
    }

    /**
     * Erzeugt eine neue SteinView.
     *
     * @param breite - Breite der SteinView
     * @param pos    - Position an der die SteinView angezeigt werden soll
     * @return neue SteinView
     */
    private SteinView erzeugeSteinView(int breite, PositionAufBildschirm pos) {

        Drawable hintergrund = getDrawable(R.drawable.stein_mini_20px);

        ImageView steinBild = new ImageView(this);

        steinBild.setMinimumHeight(breite);
        steinBild.setMinimumWidth(breite);

        steinBild.setBackground(hintergrund);
        steinBild.setElevation(10f);
        mSpielbrett.addView(steinBild);

        return new SteinView(steinBild, pos, steinBreite);
    }

    /**
     * Formatiert Millisekunden in einen String im Format mm:ss
     *
     * @param millisekunden anzahl der Millisekunden
     * @return einen String im Format mm:ss
     */
    private String millisInFormat(long millisekunden) {
        int minutes = (int) millisekunden / 60000;
        int seconds = (int) millisekunden % 60000 / 1000;
        String timeLeft;
        timeLeft = "" + minutes + ":";
        if (seconds < 10) {
            timeLeft = timeLeft + "0" + seconds;
        } else {
            timeLeft = timeLeft + seconds;
        }
        return timeLeft;
    }

    /**
     * Zeigt einen Dialog an, der den Gewinner mitteilt und womit sich das Spiel beenden lässt.
     *
     * @param gewinnername Name des Gewinners
     */
    @Override
    public void zeigeGewinnerDialog(String gewinnername) {
        if (spielBeendetDialog == null) {
            spielBeendetDialog = SpielBeendetDialog.newInstance(gewinnername);
            spielBeendetDialog.setzeSpielBeendetListener(this::finish);
            spielBeendetDialog.show(getSupportFragmentManager(), "istSpielBeendet");
        }
    }

    /**
     * Zeigt einen Dialog an, der aufgerufen wird, wenn der Spieler das Spiel verlassen möchte.
     */
    @Override
    public void zeigeSpielVerlassenDialog() {
        SpielVerlassenDialog spielVerlassen = SpielVerlassenDialog.newInstance();
        spielVerlassen.setzeSpielVerlassenListener(this::finish);
        spielVerlassen.show(getSupportFragmentManager(), "spielVerlassen?");
    }


    @Override
    public void zeigeAktuellenSpieler(SpielerIndex spielerIndex) {

        if (spielerIndex.equals(SpielerIndex.SPIELER_ZWEI)) {
            spielerEins.clearAnimation();
            spielerZwei.startAnimation(AnimationUtils.loadAnimation(this, R.anim.scale_text));
        } else {
            spielerZwei.clearAnimation();
            spielerEins.startAnimation(AnimationUtils.loadAnimation(this, R.anim.scale_text));
        }
    }

    @Override
    public void initialisiereTimerAnzeigen(long millisekunden) {
        spielerEinsTimerAnzeige = findViewById(R.id.spielerzeit);
        spielerZweiTimerAnzeige = findViewById(R.id.gegnerzeit);
        if (millisekunden > 0) {
            spielerEinsTimerAnzeige.setText(millisInFormat(millisekunden));
            spielerZweiTimerAnzeige.setText(millisInFormat(millisekunden));
        }
    }

    @Override
    public void zeigeUebrigeZeit(long uebrigeZeit) {
        aktuelleTimerAnzeige.setText(millisInFormat(uebrigeZeit));
    }

    @Override
    public void setzeAktuelleTimerAnzeige(SpielerIndex spielerIndex) {
        switch (spielerIndex) {
            case SPIELER_EINS:
                aktuelleTimerAnzeige = spielerEinsTimerAnzeige;
                break;
            case SPIELER_ZWEI:
                aktuelleTimerAnzeige = spielerZweiTimerAnzeige;
        }
    }

    @Override
    public void onStart() {
        super.onStart();
    }

    @Override
    protected void onStop() {
        super.onStop();
        presenter.stop();
    }

    @Override
    public void onBackPressed() {
        zeigeSpielVerlassenDialog();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();
        new BaoSpielPresenter(this, getIntent());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

    }

    @Override
    public void initView() {
        spielinformationen = getIntent();
        setzeSpielerNamen();
        spielinformationen.putExtra(Konstanten.EXTRA_SPIELER_NAME, SharedPrefUtils.gibBenutzername(this));
        spielinformationen.putExtra(Konstanten.EXTRA_UUID, SharedPrefUtils.gibUUID(this));
        mSpielbrett = findViewById(R.id.spielbrett);
        mSpielbrett.post(this::initialisiereMulden);
        muldenAnzeige = new MuldenView[ANZAHL_ZEILEN][ANZAHL_SPALTEN];
    }

    @Override
    public void setzePresenter(BaoSpielContract.Presenter presenter) {
        this.presenter = presenter;
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        BaoService baoServiceInterface = ((BaoService.LocalBinder) service).getService();
        SpielbrettRemoteDataSource serviceInteractor = new SpielbrettServiceInteractor(baoServiceInterface);
        presenter.setzeServiceInteractor(serviceInteractor);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
}
