content: /*
   * Copyright (C) 2015 AChep@xda <artemchep@gmail.com>
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version 2
   * of the License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   * MA  02110-1301, USA.
   */
  package com.achep.base.ui.fragments;
  
  import android.content.Context;
  import android.os.Bundle;
  import android.preference.ListPreference;
  import android.preference.MultiSelectListPreference;
  import android.preference.Preference;
  import android.preference.PreferenceScreen;
  import android.preference.TwoStatePreference;
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.annotation.StringRes;
  import android.util.Log;
  import android.view.View;
  import android.view.ViewStub;
  
  import com.achep.acdisplay.R;
  import com.achep.base.content.ConfigBase;
  import com.achep.base.interfaces.ICheckable;
  import com.achep.base.permissions.Permission;
  import com.achep.base.ui.SwitchBarPermissible;
  import com.achep.base.ui.activities.ActivityBase;
  import com.achep.base.ui.preferences.Enabler;
  import com.achep.base.ui.widgets.SwitchBar;
  import com.achep.base.utils.Operator;
  import com.achep.base.utils.ResUtils;
  
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.Set;
  
  import static com.achep.base.Build.DEBUG;
  
  public abstract class PreferenceFragment extends PreferenceFragmentBase {
  
      private static final String TAG = "PreferenceFragment";
  
      private SwitchBar mSwitch;
      private SwitchBarPermissible mSwitchPermissible;
      private Permission[] mEnablerPermissions;
      private String mEnablerKey;
      private Enabler mEnabler;
      private ConfigBase mConfig;
      private ConfigBase.Syncer mSyncer;
  
      private TwoStatePreferenceSetter mTwoStatePreferenceSetter;
  
      public abstract ConfigBase getConfig();
  
      /**
       * Requests the fragment to setup master switch from
       * the corresponding key.
       *
       * @param key the key of one of the {@link com.achep.base.content.ConfigBase}'s options
       * @see com.achep.base.content.ConfigBase
       * @see com.achep.base.ui.preferences.Enabler
       */
      public void requestMasterSwitch(@NonNull String key) {
          requestMasterSwitch(key, null);
      }
  
      public void requestMasterSwitch(@NonNull String key, @Nullable Permission[] permissions) {
          mEnablerKey = key;
          mEnablerPermissions = permissions;
      }
  
      @Override
      public void onAttach(Context context) {
          super.onAttach(context);
          mConfig = getConfig();
          mSyncer = new ConfigBase.Syncer(context, mConfig);
      }
  
      @Override
      public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
          super.onViewCreated(view, savedInstanceState);
  
          if (mEnablerKey != null) {
              if (DEBUG) Log.d(TAG, "Creating the enabler for #" + mEnablerKey + " key.");
  
              // Setup enabler to switch bar.
              mEnabler = new Enabler(getActivity(), mConfig, mEnablerKey, createPermissionSwitch());
          }
      }
  
      @NonNull
      private ICheckable createPermissionSwitch() {
          ActivityBase activity = (ActivityBase) getActivity();
          SwitchBar switchBar = getSwitchBar();
          assert switchBar != null;
          mSwitchPermissible = new SwitchBarPermissible(activity, switchBar, mEnablerPermissions);
          return mSwitchPermissible;
      }
  
      @Override
      public void onResume() {
          super.onResume();
          if (mEnabler != null) {
              mSwitchPermissible.resume();
              mEnabler.start();
          }
          mSyncer.start();
      }
  
      @Override
      public void onPause() {
          super.onPause();
          if (mEnabler != null) {
              mEnabler.stop();
              mSwitchPermissible.pause();
          }
          mSyncer.stop();
      }
  
      /**
       * Synchronizes simple checkbox preference with the config.
       *
       * @param key the key of preference & config's parameter.
       * @see com.achep.acdisplay.Config#getMap()
       */
      protected void syncPreference(@NonNull String key) {
          if (mTwoStatePreferenceSetter == null)
              mTwoStatePreferenceSetter = new TwoStatePreferenceSetter();
          syncPreference(key, mTwoStatePreferenceSetter);
      }
  
      /**
       * Synchronizes any preference with the config.
       *
       * @param key    the key of preference & config's parameter.
       * @param setter preference's setter
       * @see com.achep.acdisplay.Config#getMap()
       * @see ListPreferenceSetter
       * @see TwoStatePreferenceSetter
       */
      protected void syncPreference(@NonNull String key, @NonNull ConfigBase.Syncer.Setter setter) {
          Preference preference = findPreference(key);
          PreferenceScreen preferenceScreen = getPreferenceScreen();
  
          if (preference == null) {
              if (DEBUG) Log.d(TAG, "Tried to sync non-existent preference with config.");
              return;
          }
  
          mSyncer.syncPreference(preferenceScreen, preference, setter);
      }
  
      @Nullable
      public SwitchBar getSwitchBar() {
          if (mSwitch == null) {
              if (getView() == null) {
                  return null;
              }
  
              ViewStub stub = (ViewStub) getView().findViewById(R.id.switch_bar_stub);
              mSwitch = (SwitchBar) stub.inflate().findViewById(R.id.switch_bar);
          }
          return mSwitch;
      }
  
      /**
       * The setter for a {@link TwoStatePreference}.
       *
       * @author Artem Chepurnoy
       */
      protected static class TwoStatePreferenceSetter implements ConfigBase.Syncer.Setter {
  
          /**
           * {@inheritDoc}
           */
          @Override
          public final void updateSummary(@NonNull Preference preference,
                                          @NonNull ConfigBase.Option option,
                                          @NonNull Object value) {
              // This is unneeded, because you should always use
              //     android:summaryOn=""
              //     android:summaryOff=""
              // attributes.
          }
  
          /**
           * {@inheritDoc}
           */
          @Override
          public void setValue(@NonNull Preference preference,
                               @NonNull ConfigBase.Option option,
                               @NonNull Object value) {
              TwoStatePreference cbp = (TwoStatePreference) preference;
              cbp.setChecked((Boolean) value);
          }
  
          /**
           * {@inheritDoc}
           */
          @NonNull
          @Override
          public Object getValue(@NonNull Object value) {
              return value;
          }
  
      }
  
      /**
       * The setter for a {@link ListPreference}.
       *
       * @author Artem Chepurnoy
       */
      protected static class ListPreferenceSetter implements ConfigBase.Syncer.Setter {
  
          /**
           * {@inheritDoc}
           */
          @Override
          public void updateSummary(@NonNull Preference preference,
                                    @NonNull ConfigBase.Option option,
                                    @NonNull Object value) { /* unused */ }
  
          /**
           * {@inheritDoc}
           */
          @Override
          public void setValue(@NonNull Preference preference,
                               @NonNull ConfigBase.Option option,
                               @NonNull Object value) {
              ListPreference cbp = (ListPreference) preference;
              cbp.setValue(Integer.toString((Integer) value));
          }
  
          /**
           * {@inheritDoc}
           */
          @NonNull
          @Override
          public Object getValue(@NonNull Object value) {
              return Integer.parseInt((String) value);
          }
  
      }
  
      /**
       * The setter for a {@link MultiSelectListPreference}. To work currently,
       * its data must be in a bit-mask (bit per selectable item) format.
       *
       * @author Artem Chepurnoy
       */
      protected static class MultiSelectListPreferenceSetter implements ConfigBase.Syncer.Setter {
  
          @NonNull
          private final Context mContext;
          private final int mStrResSummary;
          private final int mStrResDisabled;
  
          public MultiSelectListPreferenceSetter(@NonNull Context context,
                                                 @StringRes int summaryStrRes,
                                                 @StringRes int disabledStrRes) {
              mContext = context;
              mStrResSummary = summaryStrRes;
              mStrResDisabled = disabledStrRes;
          }
  
          /**
           * {@inheritDoc}
           */
          @Override
          public void updateSummary(@NonNull Preference preference,
                                    @NonNull ConfigBase.Option option,
                                    @NonNull Object value) {
              MultiSelectListPreference mslp = (MultiSelectListPreference) preference;
              int mode = (int) value;
  
              CharSequence summary;
              if (mode != 0) {
                  CharSequence[] entries = mslp.getEntries();
                  CharSequence[] values = mslp.getEntryValues();
  
                  String divider = mContext.getString(R.string.settings_multi_list_divider);
                  StringBuilder sb = new StringBuilder();
                  boolean empty = true;
  
                  assert entries != null;
                  assert values != null;
  
                  // Append selected items.
                  for (int i = 0; i < values.length; i++) {
                      int a = Integer.parseInt(values[i].toString());
                      if (Operator.bitAnd(mode, a)) {
                          if (!empty) {
                              sb.append(divider);
                          }
                          sb.append(entries[i]);
                          empty = false;
                      }
                  }
  
                  String itemsText = sb.toString().toLowerCase();
                  summary = mStrResSummary != 0
                          ? ResUtils.getString(mContext, mStrResSummary, itemsText)
                          : sb.charAt(0) + itemsText.substring(1, itemsText.length());
              } else {
                  summary = mContext.getString(mStrResDisabled);
              }
  
              mslp.setSummary(summary);
          }
  
          /**
           * {@inheritDoc}
           */
          @Override
          public void setValue(@NonNull Preference preference,
                               @NonNull ConfigBase.Option option,
                               @NonNull Object value) {
              int mode = (int) value;
              String[] values = new String[Integer.bitCount(mode)];
              for (int i = 1, j = 0; j < values.length; i <<= 1) {
                  if (Operator.bitAnd(mode, i)) {
                      values[j++] = Integer.toString(i);
                  }
              }
  
              Set<String> valuesSet = new HashSet<>();
              Collections.addAll(valuesSet, values);
  
              MultiSelectListPreference mslp = (MultiSelectListPreference) preference;
              mslp.setValues(valuesSet);
          }
  
          /**
           * {@inheritDoc}
           */
          @NonNull
          @Override
          public Object getValue(@NonNull Object value) {
              int mode = 0;
  
              Set<String> values = (Set<String>) value;
              for (String v : values) {
                  mode |= Integer.parseInt(v);
              }
  
              return mode;
          }
  
      }
  
  }
  
  