package org.solovyev.android.calculator.converter;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import org.solovyev.android.calculator.App;
import org.solovyev.android.calculator.AppComponent;
import org.solovyev.android.calculator.AppModule;
import org.solovyev.android.calculator.BaseDialogFragment;
import org.solovyev.android.calculator.Clipboard;
import org.solovyev.android.calculator.Editor;
import org.solovyev.android.calculator.Keyboard;
import org.solovyev.android.calculator.Named;
import org.solovyev.android.calculator.R;
import org.solovyev.android.calculator.keyboard.FloatingKeyboard;
import org.solovyev.android.calculator.keyboard.FloatingKeyboardWindow;
import org.solovyev.android.calculator.keyboard.FloatingNumberKeyboard;
import org.solovyev.android.calculator.math.MathUtils;
import org.solovyev.android.calculator.text.NaturalComparator;
import org.solovyev.android.calculator.view.EditTextCompat;

import javax.annotation.Nonnull;
import javax.inject.Inject;

import butterknife.Bind;
import butterknife.ButterKnife;

import static org.solovyev.android.calculator.UiPreferences.Converter.lastDimension;
import static org.solovyev.android.calculator.UiPreferences.Converter.lastUnitsFrom;
import static org.solovyev.android.calculator.UiPreferences.Converter.lastUnitsTo;

public class ConverterFragment extends BaseDialogFragment
        implements AdapterView.OnItemSelectedListener, View.OnFocusChangeListener, TextView.OnEditorActionListener, View.OnClickListener, TextWatcher {

    private static final int NUMBER_INPUT_TYPE = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED;
    private static final String STATE_SELECTION_FROM = "selection.from";
    private static final String STATE_SELECTION_TO = "selection.to";
    private static final String EXTRA_VALUE = "value";
    public static final int NONE = -1;

    @NonNull
    private final FloatingKeyboardWindow keyboardWindow = new FloatingKeyboardWindow(null);
    @Inject
    Typeface typeface;
    @Inject
    Clipboard clipboard;
    @Inject
    Keyboard keyboard;
    @javax.inject.Named(AppModule.PREFS_UI)
    @Inject
    SharedPreferences uiPreferences;
    @Inject
    Editor editor;
    @Bind(R.id.converter_dimensions_spinner)
    Spinner dimensionsSpinner;
    @Bind(R.id.converter_spinner_from)
    Spinner spinnerFrom;
    @Bind(R.id.converter_label_from)
    TextInputLayout labelFrom;
    @Bind(R.id.converter_edittext_from)
    EditTextCompat editTextFrom;
    @Bind(R.id.converter_spinner_to)
    Spinner spinnerTo;
    @Bind(R.id.converter_label_to)
    TextInputLayout labelTo;
    @Bind(R.id.converter_edittext_to)
    EditText editTextTo;
    @Bind(R.id.converter_swap_button)
    ImageButton swapButton;
    private ArrayAdapter<Named<ConvertibleDimension>> dimensionsAdapter;
    private ArrayAdapter<Named<Convertible>> adapterFrom;
    private ArrayAdapter<Named<Convertible>> adapterTo;

    private int pendingFromSelection = NONE;
    private int pendingToSelection = NONE;
    private boolean useSystemKeyboard = true;

    public static void show(@Nonnull FragmentActivity activity) {
        show(activity, 1d);
    }

    public static void show(@Nonnull FragmentActivity activity, double value) {
        final ConverterFragment fragment = new ConverterFragment();
        final Bundle args = new Bundle(1);
        args.putDouble(EXTRA_VALUE, value);
        fragment.setArguments(args);
        App.showDialog(fragment, "converter", activity.getSupportFragmentManager());
    }

    @NonNull
    @Override
    public AlertDialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        final AlertDialog dialog = super.onCreateDialog(savedInstanceState);
        dialog.setCanceledOnTouchOutside(false);
        return dialog;
    }

    @Override
    protected void onPrepareDialog(@NonNull AlertDialog.Builder builder) {
        builder.setPositiveButton(R.string.c_use, null);
        builder.setNegativeButton(R.string.cpp_cancel, null);
        builder.setNeutralButton(R.string.cpp_copy, null);
    }

    @Override
    protected void inject(@NonNull AppComponent component) {
        super.inject(component);
        component.inject(this);
    }

    @SuppressLint("InflateParams")
    @Nullable
    @Override
    protected View onCreateDialogView(@NonNull Context context, @NonNull LayoutInflater inflater,
                                      @Nullable Bundle savedInstanceState) {
        final View view = inflater.inflate(R.layout.cpp_unit_converter, null);
        ButterKnife.bind(this, view);

        dimensionsAdapter = App.makeSimpleSpinnerAdapter(context);
        for (ConvertibleDimension dimension : UnitDimension.values()) {
            dimensionsAdapter.add(dimension.named(context));
        }
        dimensionsAdapter.add(NumeralBaseDimension.get().named(context));
        adapterFrom = App.makeSimpleSpinnerAdapter(context);
        adapterTo = App.makeSimpleSpinnerAdapter(context);

        dimensionsSpinner.setAdapter(dimensionsAdapter);
        spinnerFrom.setAdapter(adapterFrom);
        spinnerTo.setAdapter(adapterTo);

        dimensionsSpinner.setOnItemSelectedListener(this);
        spinnerFrom.setOnItemSelectedListener(this);
        spinnerTo.setOnItemSelectedListener(this);

        editTextFrom.setOnFocusChangeListener(this);
        editTextFrom.setOnEditorActionListener(this);
        editTextFrom.addTextChangedListener(this);
        editTextFrom.setOnClickListener(this);
        onKeyboardTypeChanged();

        swapButton.setOnClickListener(this);
        swapButton.setImageResource(App.getTheme().light ? R.drawable.ic_swap_vert_black_24dp : R.drawable.ic_swap_vert_white_24dp);

        if (savedInstanceState == null) {
            editTextFrom.setText(String.valueOf(getArguments().getDouble(EXTRA_VALUE, 1f)));
            pendingFromSelection = lastUnitsFrom.getPreference(uiPreferences);
            pendingToSelection = lastUnitsTo.getPreference(uiPreferences);
            dimensionsSpinner.setSelection(MathUtils.clamp(lastDimension.getPreference(uiPreferences), 0, dimensionsAdapter.getCount() - 1));
        } else {
            pendingFromSelection = savedInstanceState.getInt(STATE_SELECTION_FROM, NONE);
            pendingToSelection = savedInstanceState.getInt(STATE_SELECTION_TO, NONE);
        }

        return view;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(STATE_SELECTION_FROM, spinnerFrom.getSelectedItemPosition());
        outState.putInt(STATE_SELECTION_TO, spinnerTo.getSelectedItemPosition());
    }

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

    private void saveLastUsedValues() {
        final SharedPreferences.Editor editor = uiPreferences.edit();
        lastDimension.putPreference(editor, dimensionsSpinner.getSelectedItemPosition());
        lastUnitsFrom.putPreference(editor, spinnerFrom.getSelectedItemPosition());
        lastUnitsTo.putPreference(editor, spinnerTo.getSelectedItemPosition());
        editor.apply();
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        switch (parent.getId()) {
            case R.id.converter_dimensions_spinner:
                onDimensionChanged(dimensionsAdapter.getItem(position).item);
                break;
            case R.id.converter_spinner_from:
                onUnitFromChanged(adapterFrom.getItem(position).item);
                break;
            case R.id.converter_spinner_to:
                convert();
                break;
        }
    }

    private void onUnitFromChanged(@NonNull Convertible unit) {
        final int dimensionPosition = dimensionsSpinner.getSelectedItemPosition();
        updateUnitsTo(dimensionsAdapter.getItem(dimensionPosition).item, unit);
        convert();
    }

    private void onDimensionChanged(@NonNull ConvertibleDimension dimension) {
        updateUnitsFrom(dimension);
        updateUnitsTo(dimension, adapterFrom.getItem(spinnerFrom.getSelectedItemPosition()).item);
        convert();

        checkKeyboardType(dimension);
    }

    private void checkKeyboardType(@NonNull ConvertibleDimension dimension) {
        keyboardWindow.hide();
        useSystemKeyboard = !(dimension instanceof NumeralBaseDimension);
        onKeyboardTypeChanged();
    }

    private void onKeyboardTypeChanged() {
        editTextFrom.setInputType(useSystemKeyboard ? NUMBER_INPUT_TYPE : InputType.TYPE_CLASS_TEXT);
        editTextFrom.setShowSoftInputOnFocusCompat(useSystemKeyboard);
    }

    private void updateUnitsFrom(@NonNull ConvertibleDimension dimension) {
        adapterFrom.setNotifyOnChange(false);
        adapterFrom.clear();
        for (Convertible unit : dimension.getUnits()) {
            adapterFrom.add(unit.named(getActivity()));
        }
        adapterFrom.sort(NaturalComparator.INSTANCE);
        adapterFrom.setNotifyOnChange(true);
        adapterFrom.notifyDataSetChanged();
        if (pendingFromSelection != NONE) {
            spinnerFrom.setSelection(MathUtils.clamp(pendingFromSelection, 0, adapterFrom.getCount() - 1));
            pendingFromSelection = NONE;
        } else {
            spinnerFrom.setSelection(MathUtils.clamp(spinnerFrom.getSelectedItemPosition(), 0, adapterFrom.getCount() - 1));
        }
    }

    private void updateUnitsTo(@NonNull ConvertibleDimension dimension, @NonNull Convertible except) {
        final Convertible selectedUnit;
        if (pendingToSelection > NONE) {
            selectedUnit = null;
        } else {
            final int selectedPosition = spinnerTo.getSelectedItemPosition();
            selectedUnit = selectedPosition >= 0 && selectedPosition < adapterTo.getCount() ? adapterTo.getItem(selectedPosition).item : null;
        }
        adapterTo.setNotifyOnChange(false);
        adapterTo.clear();
        for (Convertible unit : dimension.getUnits()) {
            if (!except.equals(unit)) {
                adapterTo.add(unit.named(getActivity()));
            }
        }
        adapterTo.sort(NaturalComparator.INSTANCE);
        adapterTo.setNotifyOnChange(true);
        adapterTo.notifyDataSetChanged();
        if (selectedUnit != null && !except.equals(selectedUnit)) {
            for (int i = 0; i < adapterTo.getCount(); i++) {
                final Convertible unit = adapterTo.getItem(i).item;
                if (unit.equals(selectedUnit)) {
                    spinnerTo.setSelection(i);
                    return;
                }
            }
        }
        if (pendingToSelection != NONE) {
            spinnerTo.setSelection(MathUtils.clamp(pendingToSelection, 0, adapterTo.getCount() - 1));
            pendingToSelection = NONE;
        } else {
            spinnerTo.setSelection(MathUtils.clamp(spinnerTo.getSelectedItemPosition(), 0, adapterTo.getCount() - 1));
        }
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
    }

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        switch (v.getId()) {
            case R.id.converter_edittext_from:
                if (!hasFocus) {
                    convert();
                } else {
                    clearError(labelFrom);
                    showKeyboard();
                }
                break;
        }
    }

    private void showKeyboard() {
        if (useSystemKeyboard) {
            return;
        }
        keyboardWindow.show(new FloatingNumberKeyboard(new KeyboardUser()), null);
    }

    private void convert() {
        convert(true);
    }

    private void convert(boolean validate) {
        final String value = editTextFrom.getText().toString();
        if (TextUtils.isEmpty(value)) {
            if (validate) {
                setError(labelFrom, R.string.cpp_nan);
            }
            return;
        }

        try {
            final Convertible from = adapterFrom.getItem(spinnerFrom.getSelectedItemPosition()).item;
            final Convertible to = adapterTo.getItem(spinnerTo.getSelectedItemPosition()).item;
            editTextTo.setText(from.convert(to, value));
            clearError(labelFrom);
        } catch (RuntimeException e) {
            editTextTo.setText("");
            if (validate) {
                setError(labelFrom, R.string.cpp_nan);
            }
        }
    }

    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        switch (v.getId()) {
            case R.id.converter_edittext_from:
                if (actionId == EditorInfo.IME_ACTION_DONE) {
                    App.hideIme(editTextFrom);
                    convert();
                    return true;
                }
                break;
        }
        return false;
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.converter_swap_button:
                keyboardWindow.hide();
                swap();
                break;
            case R.id.converter_edittext_from:
                showKeyboard();
                break;
            default:
                super.onClick(v);
                break;
        }
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        if (which == DialogInterface.BUTTON_NEGATIVE) {
            dismiss();
            return;
        }
        final String text = editTextTo.getText().toString();
        try {
            switch (which) {
                case DialogInterface.BUTTON_POSITIVE:
                    editor.insert(text);
                    dismiss();
                    break;
                case DialogInterface.BUTTON_NEUTRAL:
                    clipboard.setText(text);
                    Toast.makeText(getActivity(), getString(R.string.cpp_text_copied),
                            Toast.LENGTH_SHORT).show();
                    break;
            }
        } catch (RuntimeException ignored) {
        }
    }

    private void swap() {
        editTextFrom.setText(editTextTo.getText());
        final Convertible oldFromUnit = adapterFrom.getItem(spinnerFrom.getSelectedItemPosition()).item;
        final Convertible oldToUnit = adapterTo.getItem(spinnerTo.getSelectedItemPosition()).item;

        pendingToSelection = NONE;
        for (int i = 0; i < adapterFrom.getCount(); i++) {
            pendingToSelection++;
            final Convertible unit = adapterFrom.getItem(i).item;
            if (unit.equals(oldToUnit)) {
                pendingToSelection--;
            } else if (unit.equals(oldFromUnit)) {
                break;
            }
        }

        for (int i = 0; i < adapterFrom.getCount(); i++) {
            final Convertible unit = adapterFrom.getItem(i).item;
            if (unit.equals(oldToUnit)) {
                spinnerFrom.setSelection(i);
                break;
            }
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        convert(false);
    }

    @Override
    public void dismiss() {
        App.hideIme(this);
        super.dismiss();
    }

    private class KeyboardUser implements FloatingKeyboard.User {
        @NonNull
        @Override
        public Context getContext() {
            return getActivity();
        }

        @NonNull
        @Override
        public EditText getEditor() {
            return editTextFrom;
        }

        @NonNull
        @Override
        public ViewGroup getKeyboard() {
            return keyboardWindow.getContentView();
        }

        @Override
        public void done() {
            keyboardWindow.hide();
            convert();
        }

        @Override
        public void showIme() {
            final InputMethodManager keyboard = (InputMethodManager)
                    getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            keyboard.showSoftInput(getEditor(), InputMethodManager.SHOW_FORCED);
            keyboardWindow.hide();
        }

        @Override
        public boolean isVibrateOnKeypress() {
            return keyboard.isVibrateOnKeypress();
        }

        @NonNull
        @Override
        public Typeface getTypeface() {
            return typeface;
        }
    }
}
