<commit_before>/*
 * Copyright 2016 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.fuzz.indicator;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.fuzz.indicator.text.CutoutTextCell;

import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

/**
 * A RecyclerView-inspired ViewGroup for showing an indicator traversing multiple child Views ('cells').
 * <p>
 * There's a nice monospace line drawing in the javadoc for {@link #showOffsetIndicator(int, float)} that basically sums up
 * its appearance when operating under an {@link ImageCellGenerator}.
 * </p>
 *
 * @author <NAME> (Fuzz)
 */
public class CutoutViewIndicator extends LinearLayout {

    private static final String TAG = CutoutViewIndicator.class.getSimpleName();

    /**
     * Specialised implementation of {@link UnavailableProxy} for use with
     * {@linkplain #isInEditMode() the preview tools}.
     */
    protected static final UnavailableProxy EDIT_MODE_PROXY = new UnavailableProxy() {
        @Override
        public void resendPositionInfo(CutoutViewIndicator cvi, float pos) {
            int atView = (int) pos;
            cvi.showOffsetIndicator(cvi.fixPosition(atView), pos - atView);
        }
    };

    /**
     * This holds onto the views that may be attached to this ViewGroup. It's initialised
     * with space for 5 values because practical experience says that space for 10 would
     * be excessive.
     * <p>
     *     This is kinda micro-optimising since it can expand automatically later.
     * </p>
     */
    @NonNull
    protected SparseArray<CutoutCell> cells = new SparseArray<>(5);

    /**
     * Default configuration used as a base for {@link #generateDefaultLayoutParams()}
     * and {@link #bindChild(int, View)}
     */
    @NonNull
    protected CutoutViewLayoutParams defaultChildParams;

    @NonNull
    protected StateProxy stateProxy = new UnavailableProxy();

    /**
     * @see #cascadeParamChanges(boolean)
     */
    protected boolean cascadeChanges;
    /**
     * {@code com.eccyan.widget.SpinningViewPager} reports positions as one greater than other ViewPagers. When this variable
     * is true, CutoutViewIndicator will correct for the discrepancy.
     */
    protected boolean usePositiveOffset;

    /**
     * This value is strictly for showing a good tools-style preview of this view. If
     * {@link #isInEditMode()} is false, this value should never be used.
     */
    private int editModePageCount;

    /**
     * This object is responsible for creating new cells
     * @see #showOffsetIndicator(int, float)
     */
    @NonNull
    protected CutoutCellGenerator generator;

    @NonNull
    protected LayoutLogger logger;

    protected DataSetObserver dataSetObserver = new DataSetObserver() {
        /**
         * This method is called when the entire data set has changed,
         * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
         *
         * In our case, this is most commonly triggered by {@link android.support.v4.view.PagerAdapter#notifyDataSetChanged()}
         */
        @Override
        public void onChanged() {
            super.onChanged();
            int childCount = getChildCount();
            int pageCount = getPageCount();

            // Unlike in RecyclerView, the individual CutoutCells are considered interchangeable.
            // Therefore the only thing that matters here is how many views there are supposed to be on screen

            int newViews = pageCount - childCount;

            if (newViews > 0) {
                // We're adding new views
                for (int i = childCount; i < pageCount; i++) {
                    // This is a cached view
                    CutoutCell cell = cells.get(i);

                    if (cell == null || cell.getItemView().getParent() != null) {
                        // Current CutoutCell is nonexistent or already in use elsewhere...create and add a new one!
                        if (cell != null && cell.getItemView().getParent() == CutoutViewIndicator.this) {
                            Log.w(TAG, "It would appear that the view at " + i + " was not removed properly.");
                        }

                        cell = createCellFor(i);
                        cells.put(i, cell);
                    }

                    // This will invalidate the added view, triggering a call to this class's onMeasure()
                    addView(cell.getItemView(), i);
                }
            } else if (newViews < 0) {
                // We're removing views
                removeViews(pageCount, -newViews);
                // Make sure to null out those references to CutoutCells
                for (int i = pageCount; i < childCount; i++) {
                    unbindChild(cells.get(i));
                }
            } else {
                // Quantity isn't changing.
            }

            stateProxy.resendPositionInfo(CutoutViewIndicator.this, getCurrentIndicatorPosition());
        }

        /**
         * This method is called when the entire data becomes invalid,
         * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
         * {@link Cursor}.
         * <p>
         *     {@link ViewPager} doesn't really call this method.<commit_msg>Split CVI's dataSetObserver::onChanged into two methods<commit_after>/*
 * Copyright 2016 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.fuzz.indicator;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.fuzz.indicator.text.CutoutTextCell;

import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

/**
 * A RecyclerView-inspired ViewGroup for showing an indicator traversing multiple child Views ('cells').
 * <p>
 * There's a nice monospace line drawing in the javadoc for {@link #showOffsetIndicator(int, float)} that basically sums up
 * its appearance when operating under an {@link ImageCellGenerator}.
 * </p>
 *
 * @author <NAME> (Fuzz)
 */
public class CutoutViewIndicator extends LinearLayout {

    private static final String TAG = CutoutViewIndicator.class.getSimpleName();

    /**
     * Specialised implementation of {@link UnavailableProxy} for use with
     * {@linkplain #isInEditMode() the preview tools}.
     */
    protected static final UnavailableProxy EDIT_MODE_PROXY = new UnavailableProxy() {
        @Override
        public void resendPositionInfo(CutoutViewIndicator cvi, float pos) {
            int atView = (int) pos;
            cvi.showOffsetIndicator(cvi.fixPosition(atView), pos - atView);
        }
    };

    /**
     * This holds onto the views that may be attached to this ViewGroup. It's initialised
     * with space for 5 values because practical experience says that space for 10 would
     * be excessive.
     * <p>
     *     This is kinda micro-optimising since it can expand automatically later.
     * </p>
     */
    @NonNull
    protected SparseArray<CutoutCell> cells = new SparseArray<>(5);

    /**
     * Default configuration used as a base for {@link #generateDefaultLayoutParams()}
     * and {@link #bindChild(int, View)}
     */
    @NonNull
    protected CutoutViewLayoutParams defaultChildParams;

    @NonNull
    protected StateProxy stateProxy = new UnavailableProxy();

    /**
     * @see #cascadeParamChanges(boolean)
     */
    protected boolean cascadeChanges;
    /**
     * {@code com.eccyan.widget.SpinningViewPager} reports positions as one greater than other ViewPagers. When this variable
     * is true, CutoutViewIndicator will correct for the discrepancy.
     */
    protected boolean usePositiveOffset;

    /**
     * This value is strictly for showing a good tools-style preview of this view. If
     * {@link #isInEditMode()} is false, this value should never be used.
     */
    private int editModePageCount;

    /**
     * This object is responsible for creating new cells
     * @see #showOffsetIndicator(int, float)
     */
    @NonNull
    protected CutoutCellGenerator generator;

    @NonNull
    protected LayoutLogger logger;

    protected DataSetObserver dataSetObserver = new DataSetObserver() {
        /**
         * This method is called when the entire data set has changed,
         * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
         *
         * In our case, this is most commonly triggered by {@link android.support.v4.view.PagerAdapter#notifyDataSetChanged()}
         */
        @Override
        public void onChanged() {
            super.onChanged();
            int childCount = getChildCount();
            int pageCount = getPageCount();

            // Unlike in RecyclerView, the individual CutoutCells are considered interchangeable.
            // Therefore the only thing that matters here is how many views there are supposed to be on screen

            int newViews = pageCount - childCount;

            if (newViews > 0) {
                // We're adding new views
                addNewViews(childCount, pageCount);
            } else if (newViews < 0) {
                // We're removing views
                removeViews(pageCount, -newViews);
                // Make sure to null out those references to CutoutCells
                for (int i = pageCount; i < childCount; i++) {
                    unbindChild(cells.get(i));
                }
            } else {
                // Quantity isn't changing.
            }

            stateProxy.resendPositionInfo(CutoutViewIndicator.this, getCurrentIndicatorPosition());
        }

        /**
         * Calls {@link CutoutViewIndicator#addView} {@code pageCount - childCount} times. All new
         * views added are placed at the end of the CutoutViewIndicator.
         * <p>
         *     If there is a non-null entry in {@link CutoutViewIndicator#cells} at the
         *     position where this new view will be placed, then that value is queried
         *     for its {@link CutoutCell#getItemView() itemView}. If that itemView is then
         *     not null and not currently attached to a parent it will be added here.
         * </p>
         * <p>
         *     However, if the CutoutCell's itemView <em>is</em> null or attached to a
         *     {@link android.view.ViewParent parent}, it will not be used and a new
         *     CutoutCell will be created (with a backing view) by
         *     {@link CutoutViewIndicator#createCellFor(int)}.
         * </p>
         * At the end of this method, the entry in {@link CutoutViewIndicator#cells} for
         * each position of a newly-added View will contain a reference to that View's
         * {@link CutoutCell}.
         * @param childCount    the number of views currently added as children of this
         * {@link CutoutViewIndicator}
         * @param pageCount     the number of children this CutoutViewIndicator should
         *                      have to match the {@link CutoutViewIndicator#stateProxy}'s
         *                      {@link StateProxy#getCellCount() cell count}.
         */
        protected void addNewViews(int childCount, int pageCount) {
            for (int i = childCount; i < pageCount; i++) {
                // This is a cached view
                CutoutCell cell = cells.get(i);

                if (cell == null || cell.getItemView().getParent() != null) {
                    // Current CutoutCell is nonexistent or already in use elsewhere...create and add a new one!
                    if (cell != null && cell.getItemView().getParent() == CutoutViewIndicator.this) {
                        Log.w(TAG, "It would appear that the view at " + i + " was not removed properly.");
                    }

                    cell = createCellFor(i);
                    cells.put(i, cell);
                }

                // This will invalidate the added view, triggering a call to this class's onMeasure()
                addView(cell.getItemView(), i);
            }
        }

        /**
         * This method is called when the entire data becomes invalid,
         * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
         * {@link Cursor}.
         * <p>
         *     {@link ViewPager} doesn't really call this method.
{'fraction_non_alphanumeric': 0.08624581082005335, 'fraction_numerical': 0.002188632788454962, 'mean_word_length': 2.429978888106967, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4044841', 'n_tokens_mistral': 3794, 'n_tokens_neox': 3697, 'n_words': 1348}