package cgeo.geocaching.settings;

import cgeo.geocaching.R;
import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.utils.functions.Action1;

import android.content.Context;
import android.content.res.TypedArray;
import android.preference.Preference;
import android.text.InputType;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;

import java.util.Locale;

public class SeekbarPreference extends Preference {

    private TextView valueView;
    protected int minProgress = 0;
    protected int maxProgress = 100;
    protected int startProgress = 0;
    protected final int defaultValue = 10;
    protected boolean hasDecimals = false;
    protected String label = "";
    protected Context context = null;

    public SeekbarPreference(final Context context) {
        this(context, null);
    }

    public SeekbarPreference(final Context context, final AttributeSet attrs) {
        this(context, attrs, android.R.attr.preferenceStyle);
    }

    public SeekbarPreference(final Context context, final AttributeSet attrs, final int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        setPersistent(true);
        setLayoutResource(R.layout.preference_seekbar);

        // analyze given parameters
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SeekbarPreference);
        minProgress = valueToProgress(a.getInt(R.styleable.SeekbarPreference_min, minProgress));
        maxProgress = valueToProgress(a.getInt(R.styleable.SeekbarPreference_max, maxProgress));
        final String temp = a.getString(R.styleable.SeekbarPreference_label);
        if (null != temp) {
            label = temp;
        }
        hasDecimals = a.getBoolean(R.styleable.SeekbarPreference_hasDecimals, useDecimals());
        a.recycle();

        init();
    }

    // naming convention:
    // progress     - the internal value of the android widget (0 to max) - always int
    // value        - actual value (may differ from "progress" if non linear or other boundaries) - always int
    // shownValue   - displayed value (may differ from "value", if conversions are involved) - in:float out:float or int converted to string

    protected int valueToProgressHelper(final int value) {
        return value;
    }

    protected int valueToProgress(final int value) {
        return valueToProgressHelper(value);
    }

    protected int progressToValue(final int progress) {
        return progress;
    }

    protected String valueToShownValue(final int value) {
        return useDecimals() ? String.format(Locale.US, "%.2f", (float) value) : String.valueOf(value);
    }

    protected int shownValueToValue(final float shownValue) {
        return Math.round(shownValue);
    }

    protected void init() {
        // init method gets called once by the constructor,
        // so override and put initialization stuff in there (if needed)
    }

    protected String getValueString(final int progress) {
        return valueToShownValue(progressToValue(progress)) + getUnitString();
    }

    /**
     * Get unit-label for progress value.
     * @return string for the unit label
     */
    protected String getUnitString() {
        return "";
    }

    /**
     * hasDecimals is parameter from SeekbarPreference, but can be overwritten.
     * So use useDecimals instead of member
     * @return hasDecimals
     */
    protected boolean useDecimals() {
        return hasDecimals;
    }

    private boolean atLeastMin(final SeekBar seekBar, final int progress) {
        if (progress < minProgress) {
            seekBar.setProgress(minProgress);
            return false;
        }
        return true;
    }

    protected void saveSetting(final int progress) {
        if (callChangeListener(progress)) {
            persistInt(progressToValue(progress));
            notifyChanged();
            // workaround for Android 7, as onCreateView() gets called unexpectedly after saveSetting(),
            // and the the old startValue would be used, leading to a jumping slider
            startProgress = progress;
        }
    }

    @Override
    protected void onSetInitialValue(final boolean restoreValue, final Object defaultValue) {
        final int defValue = null != defaultValue ? (Integer) defaultValue : this.defaultValue;
        startProgress = valueToProgress(restoreValue ? getPersistedInt(defValue) : defValue);
    }

    @Override
    protected Object onGetDefaultValue(final TypedArray a, final int index) {
        return valueToProgress(a.getInt(index, defaultValue));
    }

    @Override
    protected View onCreateView(final ViewGroup parent) {
        final View v = super.onCreateView(parent);

        // get views
        final SeekBar seekBar = v.findViewById(R.id.preference_seekbar);
        valueView = v.findViewById(R.id.preference_seekbar_value_view);

        // init seekbar
        seekBar.setMax(maxProgress);

        // set initial value
        final int threshold = startProgress;
        valueView.setText(getValueString(threshold));
        seekBar.setProgress(threshold);

        // set label (if given)
        if (null != label && !label.isEmpty()) {
            final TextView labelView = v.findViewById(R.id.preference_seekbar_label_view);
            labelView.setVisibility(View.VISIBLE);
            labelView.setText(label);
        }

        seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser) {
                if (fromUser && atLeastMin(seekBar, progress)) {
                    valueView.setText(getValueString(progress));
                }
            }
            @Override
            public void onStartTrackingTouch(final SeekBar seekBar) {
                // abstract method needs to be implemented, but is not used here
            }
            @Override
            public void onStopTrackingTouch(final SeekBar seekBar) {
                if (atLeastMin(seekBar, seekBar.getProgress())) {
                    saveSetting(seekBar.getProgress());
                }
            }
        });

        valueView.setOnClickListener(v2 -> {
            final String title = String.format(context.getString(R.string.number_input_title), valueToShownValue(progressToValue(minProgress)), valueToShownValue(progressToValue(maxProgress)));
            final String defaultValue = valueToShownValue(progressToValue(seekBar.getProgress()));
            int inputType = InputType.TYPE_CLASS_NUMBER;
            if (useDecimals()) {
                inputType |= InputType.TYPE_NUMBER_FLAG_DECIMAL;
            }
            final Action1<String> listener = input -> {
                int newValue;
                try {
                    newValue = valueToProgress(shownValueToValue(Float.parseFloat(input)));
                    if (newValue > maxProgress) {
                        newValue = maxProgress;
                        Toast.makeText(context, R.string.number_input_err_boundarymax, Toast.LENGTH_SHORT).show();
                    }
                    if (newValue < minProgress) {
                        newValue = minProgress;
                        Toast.makeText(context, R.string.number_input_err_boundarymin, Toast.LENGTH_SHORT).show();
                    }
                    seekBar.setProgress(newValue);
                    saveSetting(seekBar.getProgress());
                    valueView.setText(getValueString(newValue));
                } catch (NumberFormatException e) {
                    Toast.makeText(context, R.string.number_input_err_format, Toast.LENGTH_SHORT).show();
                }
            };

            Dialogs.input(context, title, inputType, defaultValue, getUnitString(), android.R.string.ok, listener);
        });

        return v;
    }
}

@@ -4500,36 +4500,48 @@
 tValue;%0A        
-star
+final int defaul
 tProgress = valu
@@ -4598,32 +4598,142 @@
 e) : defValue);%0A
+        startProgress = defaultProgress %3C minProgress ? minProgress : Math.min(defaultProgress, maxProgress);%0A
     %7D%0A%0A    @Over
@@ -4830,32 +4830,16 @@
  return 
-valueToProgress(
 a.getInt
@@ -4855,25 +4855,24 @@
 efaultValue)
-)
 ;%0A    %7D%0A%0A   

fix wrong default value for proximity-preferences: 'onGetDefaultValue' should return the default value, which is in this case the integer metric value (default value in the preferences.xml is defined as metric distance) and not the internal integer progress value. check for min/max on setting default-value