/*
 * Copyright (C) 2022 - Florindo Smilari (spherIC)
 */
package ch.spherIC.recurvebowsight.ui.calcsight;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;

import java.text.DecimalFormat;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import ch.spherIC.recurvebowsight.R;
import ch.spherIC.recurvebowsight.components.SuspendableTextWatcher;
import ch.spherIC.recurvebowsight.databinding.FragmentCalcsightParametersBinding;
import ch.spherIC.recurvebowsight.logic.BallisticResults;
import ch.spherIC.recurvebowsight.logic.CalculationTask;
import ch.spherIC.recurvebowsight.logic.TrajectoryCalculation;
import ch.spherIC.recurvebowsight.logic.TrajectoryCalculator;
import ch.spherIC.recurvebowsight.model.IArcherySetup;
import ch.spherIC.recurvebowsight.utilities.NoFilterArrayAdapter;
import ch.spherIC.recurvebowsight.utilities.PlatformUtils;

public class CalculateSightParametersFragment extends Fragment implements TrajectoryCalculation {

    public static final DecimalFormat DF = new DecimalFormat("0.##");
    public static final DecimalFormat DF_1 = new DecimalFormat("0.00");
    public static final DecimalFormat DF_2 = new DecimalFormat("0.0##");
    private static final double CENTI = 0.01;
    private static final double MILLI = 0.001;
    private static final int MAX_TRAJ_POINTS = 200;
    private FragmentCalcsightParametersBinding binding;
    private CalculateSightParametersViewModel calculateSightViewModel;
    private ProgressDialog progressDialog;
    private SuspendableTextWatcher accuracyOptimizingTextWatcher;

    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        this.calculateSightViewModel = new ViewModelProvider(this).get(CalculateSightParametersViewModel.class);
        this.binding = FragmentCalcsightParametersBinding.inflate(inflater, container, false);
        View root = binding.getRoot();

        this.binding.fabCalculate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                accuracyOptimizingTextWatcher.suspend();
                updateModelFromUI();
                accuracyOptimizingTextWatcher.resume();
                calculate();
            }
        });

        loadCalculationAccuracies(root);
        loadCalculationTimeIntervals(root);
        addValueChangeListeners();
        addObservers();

        return root;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (getParentFragment() != null && getParentFragment().getArguments() != null && getParentFragment().getArguments().containsKey("setup")) {
            IArcherySetup archerySetup = getParentFragment().getArguments().getParcelable("setup");
            this.calculateSightViewModel.setSelectedArcherySetup(archerySetup);
            loadDefaultValues();

            Bundle bundle = new Bundle();
            bundle.putParcelable("selectedArcherySetup", archerySetup);
            getParentFragmentManager().setFragmentResult("selectedArcherySetup", bundle);
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }

    @Override
    public void updateTrajectoryTxtFld(Integer step, Double[] flightCurveRow, Double startAngle, Double[] maxHCurve) {
    }

    @Override
    public void onCalculationDone(BallisticResults ballisticResults) {
        Bundle bundle = new Bundle();
        bundle.putParcelable("ballisticResults", ballisticResults);
        getParentFragmentManager().setFragmentResult("ballisticResults", bundle);

        Bundle startAngleBundle = new Bundle();
        startAngleBundle.putDouble("startAngle", ballisticResults.getStartAngle());
        startAngleBundle.putDouble("shootingDistance", this.calculateSightViewModel.getShootingDistance().getValue());
        getParentFragmentManager().setFragmentResult("startAngle", startAngleBundle);

        if (this.progressDialog != null) {
            this.progressDialog.dismiss();
        }

        ((CalculateSightFragment) getParentFragment()).scrollToTab(2);
    }

    private void addObservers() {
        this.calculateSightViewModel.getArrowDiameter().observe(getViewLifecycleOwner(), arrowDiameter -> this.binding.arrowDiameterTxt.setText(arrowDiameter != null ? DF.format(arrowDiameter) : ""));
        this.calculateSightViewModel.getArrowCw().observe(getViewLifecycleOwner(), arrowCw -> this.binding.arrowCwTxt.setText(arrowCw != null ? DF.format(arrowCw) : ""));
        this.calculateSightViewModel.getArrowWeight().observe(getViewLifecycleOwner(), arrowWeight -> this.binding.arrowWeightTxt.setText(arrowWeight != null ? DF.format(arrowWeight) : ""));
        this.calculateSightViewModel.getBowPullout().observe(getViewLifecycleOwner(), bowPullout -> this.binding.bowPulloutTxt.setText(bowPullout != null ? DF.format(bowPullout) : ""));
        this.calculateSightViewModel.getArrowV0().observe(getViewLifecycleOwner(), arrowV0 -> this.binding.arrowV0Txt.setText(arrowV0 != null ? DF.format(arrowV0) : ""));
        this.calculateSightViewModel.getHNA().observe(getViewLifecycleOwner(), hNA -> this.binding.hNATxt.setText(hNA != null ? DF.format(hNA) : ""));
        this.calculateSightViewModel.getNockRaising().observe(getViewLifecycleOwner(), nockRaising -> this.binding.nockRaisingTxt.setText(nockRaising != null ? DF.format(nockRaising) : ""));
        this.calculateSightViewModel.getBraceHeight().observe(getViewLifecycleOwner(), braceHeight -> this.binding.braceHeightTxt.setText(braceHeight != null ? DF.format(braceHeight) : ""));
        this.calculateSightViewModel.getAirTemperature().observe(getViewLifecycleOwner(), airTemp -> this.binding.airTemperatureTxt.setText(airTemp != null ? DF.format(airTemp) : ""));
        this.calculateSightViewModel.getRelativeAirHumidity().observe(getViewLifecycleOwner(), relAirHumidity -> this.binding.airRelativeHumidityTxt.setText(relAirHumidity != null ? DF.format(relAirHumidity) : ""));
        this.calculateSightViewModel.getShootingDistance().observe(getViewLifecycleOwner(), shootingDistance -> this.binding.shootingDistanzTxt.setText(shootingDistance != null ? DF.format(shootingDistance) : ""));
        this.calculateSightViewModel.getArrowNockHeight().observe(getViewLifecycleOwner(), arrowNockHeight -> this.binding.arrowNockHeightTxt.setText(arrowNockHeight != null ? DF_1.format(arrowNockHeight) : ""));
        this.calculateSightViewModel.getTargetCenterHeight().observe(getViewLifecycleOwner(), targetCenterHeight -> this.binding.targetCenterHeightTxt.setText(targetCenterHeight != null ? DF_1.format(targetCenterHeight) : ""));
        this.calculateSightViewModel.getDeltaTime().observe(getViewLifecycleOwner(), deltaTime -> this.binding.deltaTimeTxt.setText(deltaTime != null ? DF_2.format(deltaTime) : ""));
        this.calculateSightViewModel.getCalcPrecision().observe(getViewLifecycleOwner(), calcPrecision -> this.binding.calcPrecisionTxt.setText(calcPrecision != null ? DF.format(calcPrecision) : ""));
    }

    private void updateModelFromUI() {
        this.calculateSightViewModel.setArrowDiameter(PlatformUtils.parseDouble(this.binding.arrowDiameterTxt.getText().toString()));
        this.calculateSightViewModel.setArrowCw(PlatformUtils.parseDouble(this.binding.arrowCwTxt.getText().toString()));
        this.calculateSightViewModel.setArrowWeight(PlatformUtils.parseDouble(this.binding.arrowWeightTxt.getText().toString()));
        this.calculateSightViewModel.setArrowV0(PlatformUtils.parseDouble(this.binding.arrowV0Txt.getText().toString()));
        this.calculateSightViewModel.setBowPullout(PlatformUtils.parseDouble(this.binding.bowPulloutTxt.getText().toString()));
        this.calculateSightViewModel.setHNA(PlatformUtils.parseDouble(this.binding.hNATxt.getText().toString()));
        this.calculateSightViewModel.setNockRaising(PlatformUtils.parseDouble(this.binding.nockRaisingTxt.getText().toString()));
        this.calculateSightViewModel.setBraceHeight(PlatformUtils.parseDouble(this.binding.braceHeightTxt.getText().toString()));
        this.calculateSightViewModel.setAirTemperature(PlatformUtils.parseDouble(this.binding.airTemperatureTxt.getText().toString()));
        this.calculateSightViewModel.setRelativeAirHumidity(PlatformUtils.parseDouble(this.binding.airRelativeHumidityTxt.getText().toString()));
        this.calculateSightViewModel.setShootingDistance(PlatformUtils.parseDouble(this.binding.shootingDistanzTxt.getText().toString()));
        this.calculateSightViewModel.setArrowNockHeight(PlatformUtils.parseDouble(this.binding.arrowNockHeightTxt.getText().toString()));
        this.calculateSightViewModel.setTargetCenterHeight(PlatformUtils.parseDouble(this.binding.targetCenterHeightTxt.getText().toString()));
        this.calculateSightViewModel.setDeltaTime(PlatformUtils.parseDouble(this.binding.deltaTimeTxt.getText().toString()));
        this.calculateSightViewModel.setCalcPrecision(PlatformUtils.parseDouble(this.binding.calcPrecisionTxt.getText().toString()));
    }

    private void loadCalculationAccuracies(View root) {
        String[] accuracies = root.getResources().getStringArray(R.array.CalculationAccuracies);
        ArrayAdapter<String> adapter = new NoFilterArrayAdapter<String>(root.getContext(), android.R.layout.simple_dropdown_item_1line, accuracies);
        AutoCompleteTextView textView = root.findViewById(R.id.calcPrecisionTxt);
        textView.setAdapter(adapter);
    }

    private void loadCalculationTimeIntervals(View root) {
        String[] timeDeltas = root.getResources().getStringArray(R.array.CalculationTimeIntervals);
        ArrayAdapter<String> adapter = new NoFilterArrayAdapter<String>(root.getContext(), android.R.layout.simple_dropdown_item_1line, timeDeltas);
        AutoCompleteTextView textView = root.findViewById(R.id.deltaTimeTxt);
        textView.setAdapter(adapter);
    }

    /**
     * Startet die Berechnung wenn alle notwendigen Felder gefüllt sind.
     */
    private void calculate() {
        if (allParamFieldsValid()) {
            Context context = this.binding.getRoot().getContext();
            InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(this.binding.getRoot().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);

            this.progressDialog = ProgressDialog.show(binding.getRoot().getContext(), null, getResources().getString(R.string.caption_Calculating),
                                                      true, false);

            CalculationTask calcTask = new CalculationTask();
            TrajectoryCalculator calculator = new TrajectoryCalculator(Double.valueOf(this.binding.arrowDiameterTxt.getText().toString().trim()) * MILLI,
                                                                       Double.valueOf(this.binding.arrowCwTxt.getText().toString().trim()),
                                                                       Double.valueOf(this.binding.arrowWeightTxt.getText().toString().trim()) * MILLI,
                                                                       Double.valueOf(this.binding.arrowV0Txt.getText().toString().trim()),
                                                                       Double.valueOf(this.binding.shootingDistanzTxt.getText().toString().trim()),
                                                                       Double.valueOf(this.binding.targetCenterHeightTxt.getText().toString().trim())
                                                                               - Double.valueOf(this.binding.arrowNockHeightTxt.getText().toString().trim()),
                                                                       Double.valueOf(this.binding.airTemperatureTxt.getText().toString().trim()),
                                                                       Double.valueOf(this.binding.airRelativeHumidityTxt.getText().toString().trim()) * CENTI,
                                                                       Double.valueOf(this.binding.deltaTimeTxt.getText().toString().trim()),
                                                                       Double.valueOf(this.binding.calcPrecisionTxt.getText().toString().trim()) * CENTI,
                                                                       this);
            calculator.setStartAngle(0d);
            calcTask.execute(calculator);

        } else {
            Toast toast = Toast.makeText(binding.getRoot().getContext(), getResources().getString(R.string.caption_FillAllMandatoryFields), Toast.LENGTH_LONG);
            toast.setGravity(Gravity.CENTER, 0, 0);
            toast.show();
        }
    }

    private boolean allParamFieldsValid() {
        return !this.binding.arrowDiameterTxt.getText().toString().isEmpty()
                && !this.binding.arrowCwTxt.getText().toString().isEmpty()
                && !this.binding.arrowWeightTxt.getText().toString().isEmpty()
                && !this.binding.bowPulloutTxt.getText().toString().isEmpty()
                && !this.binding.arrowV0Txt.getText().toString().isEmpty()
                && !this.binding.hNATxt.getText().toString().isEmpty()
                && !this.binding.nockRaisingTxt.getText().toString().isEmpty()
                && !this.binding.braceHeightTxt.getText().toString().isEmpty()
                && !this.binding.airTemperatureTxt.getText().toString().isEmpty()
                && !this.binding.airRelativeHumidityTxt.getText().toString().isEmpty() && !this.binding.airRelativeHumidityTxt.getText().toString().equals("0")
                && !this.binding.shootingDistanzTxt.getText().toString().isEmpty() && !this.binding.shootingDistanzTxt.getText().toString().equals("0")
                && !this.binding.arrowNockHeightTxt.getText().toString().isEmpty()
                && !this.binding.targetCenterHeightTxt.getText().toString().isEmpty()
                && !this.binding.deltaTimeTxt.getText().toString().isEmpty()
                && !this.binding.calcPrecisionTxt.getText().toString().isEmpty();
    }

    private void addValueChangeListeners() {
        this.accuracyOptimizingTextWatcher = new SuspendableTextWatcher() {
            @Override
            protected void onBeforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            protected void onTextHasChanged(CharSequence s, int start, int before, int count) {
            }

            @Override
            protected void onAfterTextChanged(Editable s) {
                if (!binding.arrowV0Txt.getText().toString().isEmpty() && !binding.shootingDistanzTxt.getText().toString().isEmpty()) {
                    calculateAndSetPerformantAccuracy(Double.valueOf(binding.arrowV0Txt.getText().toString()),
                                                      Double.valueOf(binding.shootingDistanzTxt.getText().toString()));
                }
            }
        };

        this.binding.arrowV0Txt.addTextChangedListener(this.accuracyOptimizingTextWatcher);
        this.binding.shootingDistanzTxt.addTextChangedListener(this.accuracyOptimizingTextWatcher);
    }

    private void calculateAndSetPerformantAccuracy(final Double v0, final Double shootingDistance) {
        String[] calcAccuracies = getResources().getStringArray(R.array.CalculationTimeIntervals);
        String calcAccuracyToSet = calcAccuracies[calcAccuracies.length - 1];
        Double approxFlightTime = shootingDistance / v0;
        Double approxIntervalTime = approxFlightTime / MAX_TRAJ_POINTS;

        for (int i = 0; i < calcAccuracies.length - 1; i++) {
            Double accLow = Double.valueOf(calcAccuracies[i]);
            Double accHigh = Double.valueOf(calcAccuracies[i + 1]);
            if (accHigh < approxIntervalTime && approxIntervalTime <= accLow) {
                calcAccuracyToSet = calcAccuracies[i];
                break;
            }
        }

        this.binding.deltaTimeTxt.setText(calcAccuracyToSet);
    }

    private void loadDefaultValues() {
        SharedPreferences sharedPreferences = this.binding.getRoot().getContext().getSharedPreferences(PlatformUtils.DEFAULT_SETTINGS_PREF, Context.MODE_PRIVATE);
        if (sharedPreferences != null) {
            this.calculateSightViewModel.setAirTemperature((double) sharedPreferences.getFloat(PlatformUtils.DEFAULT_SETTING_AIRTEMP, 0.0f));
            this.calculateSightViewModel.setRelativeAirHumidity((double) sharedPreferences.getFloat(PlatformUtils.DEFAULT_SETTING_RELAIRHUMIDITY, 0.0f));
            this.calculateSightViewModel.setShootingDistance((double) sharedPreferences.getFloat(PlatformUtils.DEFAULT_SETTING_SHOOTINGDIST, 0.0f));
        }
    }
}