<commit_before>/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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.android.deskclock.timer;

import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.android.deskclock.DeskClock;
import com.android.deskclock.CircleButtonsLinearLayout;
import com.android.deskclock.DeskClockFragment;
import com.android.deskclock.LabelDialogFragment;
import com.android.deskclock.R;
import com.android.deskclock.TimerSetupView;
import com.android.deskclock.Utils;
import com.android.deskclock.DeskClock.OnTapListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;


public class TimerFragment extends DeskClockFragment
        implements OnClickListener, OnSharedPreferenceChangeListener {

    private static final String TAG = "TimerFragment";
    private static final String KEY_SETUP_SELECTED = "_setup_selected";
    private static final String KEY_ENTRY_STATE = "entry_state";
    private Bundle mViewState = null;
    private ListView mTimersList;
    private View mNewTimerPage;
    private View mTimersListPage;
    private Button mCancel, mStart;
    private View mSeperator;
    private ImageButton mAddTimer;
    private View mTimerFooter;
    private TimerSetupView mTimerSetup;
    private TimersListAdapter mAdapter;
    private boolean mTicking = false;
    private SharedPreferences mPrefs;
    private NotificationManager mNotificationManager;
    private OnEmptyListListener mOnEmptyListListener;

    public TimerFragment() {
    }

    class ClickAction {
        public static final int ACTION_STOP = 1;
        public static final int ACTION_PLUS_ONE = 2;
        public static final int ACTION_DELETE = 3;

        public int mAction;
        public TimerObj mTimer;

        public ClickAction(int action, TimerObj t) {
            mAction = action;
            mTimer = t;
        }
    }

    // Container Activity that requests TIMESUP_MODE must implement this interface
    public interface OnEmptyListListener {
        public void onEmptyList();
    }

    TimersListAdapter createAdapter(Context context, SharedPreferences prefs) {
        if (mOnEmptyListListener == null) {
            return new TimersListAdapter(context, prefs);
        } else {
            return new TimesUpListAdapter(context, prefs);
        }
    }

    class TimersListAdapter extends BaseAdapter {

        ArrayList<TimerObj> mTimers = new ArrayList<TimerObj> ();
        private final LayoutInflater mInflater;
        Context mContext;
        SharedPreferences mmPrefs;

        public TimersListAdapter(Context context, SharedPreferences prefs) {
            mContext = context;
            mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mmPrefs = prefs;
        }

        @Override
        public int getCount() {
            return mTimers.size();
        }

        @Override
        public Object getItem(int p) {
            return mTimers.get(p);
        }

        @Override
        public long getItemId(int p) {
            if (p >= 0 && p < mTimers.size()) {
                return mTimers.get(p).mTimerId;
            }
            return 0;
        }

        public void deleteTimer(int id) {
            for (int i = 0; i < mTimers.size(); i++) {
                TimerObj t = mTimers.get(i);
                if (t.mTimerId == id) {
                    ((TimerListItem)t.mView).stop();
                    t.deleteFromSharedPref(mmPrefs);
                    mTimers.remove(i);
                    notifyDataSetChanged();
                    return;
                }
            }
        }

        protected int findTimerPositionById(int id) {
            for (int i = 0; i < mTimers.size(); i++) {
                TimerObj t = mTimers.get(i);
                if (t.mTimerId == id) {
                    return i;
                }
            }
            return -1;
        }

        public void removeTimer(TimerObj timerObj) {
            int position = findTimerPositionById(timerObj.mTimerId);
            if (position >= 0) {
                mTimers.remove(position);
                notifyDataSetChanged();
            }
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            TimerListItem v = new TimerListItem (mContext); // TODO: Need to recycle convertView.

            final TimerObj o = (TimerObj)getItem(position);
            o.mView = v;
            long timeLeft =  o.updateTimeLeft(false);
            boolean drawRed = o.mState != TimerObj.STATE_RESTART;
            v.set(o.mOriginalLength, timeLeft, drawRed);
            v.setTime(timeLeft, true);
            switch (o.mState) {
            case TimerObj.STATE_RUNNING:
                v.start();
                break;
            case TimerObj.STATE_TIMESUP:
                v.timesUp();
                break;
            case TimerObj.STATE_DONE:
                v.done();
                break;
            default:
                break;
            }

            // Timer text serves as a virtual start/stop button.
            final CountingTimerView countingTimerView = (CountingTimerView)
                    v.findViewById(R.id.timer_time_text);
            countingTimerView.registerVirtualButtonAction(new Runnable() {
                @Override
                public void run() {
                    TimerFragment.this.onClickHelper(
                            new ClickAction(ClickAction.ACTION_STOP, o));
                }
            });

            ImageButton delete = (ImageButton)v.findViewById(R.id.timer_delete);
            delete.setOnClickListener(TimerFragment.this);
            delete.setTag(new ClickAction(ClickAction.ACTION_DELETE, o));
            ImageButton plusOne = (ImageButton)v. findViewById(R.id.timer_plus_one);
            plusOne.setOnClickListener(TimerFragment.this);
            plusOne.setTag(new ClickAction(ClickAction.ACTION_PLUS_ONE, o));
            TextView stop = (TextView)v. findViewById(R.id.timer_stop);
            stop.setTag(new ClickAction(ClickAction.ACTION_STOP, o));
            TimerFragment.this.setTimerButtons(o);

            v.setBackgroundColor(getResources().getColor(R.color.blackish));
            countingTimerView.registerStopTextView(stop);
            // Use light's out setup when this fragment is within the DeskClock
            if (getActivity() instanceof DeskClock) {
                countingTimerView.registerActivity((DeskClock) getActivity());
            }
            CircleButtonsLinearLayout circleLayout =
                    (CircleButtonsLinearLayout)v.findViewById(R.id.timer_circle);
            circleLayout.setCircleTimerViewIds(
                    R.id.timer_time, R.id.timer_plus_one, R.id.timer_delete, R.id.timer_stop,
                    R.dimen.plusone_reset_button_padding, R.dimen.delete_button_padding,
                    R.id.timer_label, R.id.timer_label_text);

            FrameLayout label = (FrameLayout)v. findViewById(R.id.timer_label);
            ImageButton labelIcon = (ImageButton)v. findViewById(R.id.timer_label_icon);
            TextView labelText = (TextView)v. findViewById(R.id.timer_label_text);
            if (o.mLabel.equals("")) {
                labelText.setVisibility(View.GONE);
                labelIcon.setVisibility(View.VISIBLE);
            } else {
                labelText.setText(o.mLabel);
                labelText.setVisibility(View.VISIBLE);
                labelIcon.setVisibility(View.GONE);
            }
            if (getActivity() instanceof DeskClock) {
                label.setOnTouchListener(new OnTapListener(getActivity(), labelText) {
                    @Override
                    protected void processClick(View v) {
                        onLabelPressed(o);
                    }
                });
            } else {
                labelIcon.setVisibility(View.INVISIBLE);
            }
            return v;
        }

        public void addTimer(TimerObj t) {
            mTimers.add(0, t);
            notifyDataSetChanged();
        }

        public void onSaveInstanceState(Bundle outState) {
            TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
        }

        public void onRestoreInstanceState(Bundle outState) {
            TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers);
            notifyDataSetChanged();
        }

        public void saveGlobalState() {
            TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
        }
    }

    class TimesUpListAdapter extends TimersListAdapter {

        public TimesUpListAdapter(Context context, SharedPreferences prefs) {
            super(context, prefs);
        }

        @Override
        public void onSaveInstanceState(Bundle outState) {
            // This adapter has a data subset and never updates entire database
            // Individual timers are updated in button handlers.
        }

        @Override
        public void saveGlobalState() {
            // This adapter has a data subset and never updates entire database
            // Individual timers are updated in button handlers.
        }

        @Override
        public void onRestoreInstanceState(Bundle outState) {
            // This adapter loads a subset
            TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers, TimerObj.STATE_TIMESUP);

            if (getCount() == 0) {
                mOnEmptyListListener.onEmptyList();
            } else {
                Collections.sort(mTimers, new Comparator<TimerObj>() {
                    @Override
                    public int compare(TimerObj o1, TimerObj o2) {
                       return (int)(o1.mTimeLeft - o2.mTimeLeft);
                    }
                });
            }
        }
    }

    private final Runnable mClockTick = new Runnable() {
        boolean mVisible = true;
        final static int TIME_PERIOD_MS = 1000;
        final static int SPLIT = TIME_PERIOD_MS / 2;

        @Override
        public void run() {
            // Setup for blinking
            boolean visible = Utils.getTimeNow() % TIME_PERIOD_MS < SPLIT;
            boolean toggle = mVisible != visible;
            mVisible = visible;
            for (int i = 0; i < mAdapter.getCount(); i ++) {
                TimerObj t = (TimerObj) mAdapter.getItem(i);
                if (t.mState == TimerObj.STATE_RUNNING || t.mState == TimerObj.STATE_TIMESUP) {
                    long timeLeft = t.updateTimeLeft(false);
                    if ((TimerListItem)(t.mView) != null) {
                        ((TimerListItem)(t.mView)).setTime(timeLeft, false);
                    }
                }
                if (t.mTimeLeft <= 0 && t.mState != TimerObj.STATE_DONE
                        && t.mState != TimerObj.STATE_RESTART) {
                    t.mState = TimerObj.STATE_TIMESUP;
                    TimerFragment.this.setTimerButtons(t);
                    if ((TimerListItem)(t.mView) != null) {
                        ((TimerListItem)(t.mView)).timesUp();
                    }
                }

                // The blinking
                if (toggle && (TimerListItem)(t.mView) != null) {
                    if (t.mState == TimerObj.STATE_TIMESUP) {
                        ((TimerListItem)(t.mView)).setCircleBlink(mVisible);
                    }
                    if (t.mState == TimerObj.STATE_STOPPED) {
                        ((TimerListItem)(t.mView)).setTextBlink(mVisible);
                    }
                }
            }
            mTimersList.postDelayed(mClockTick, 20);
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Cache instance data and consume in first call to setupPage()
        if (savedInstanceState != null) {
            mViewState = savedInstanceState;
        }

        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View v = inflater.inflate(R.layout.timer_fragment, container, false);

        // Handle arguments from parent
        Bundle bundle = getArguments();
        if (bundle != null && bundle.containsKey(Timers.TIMESUP_MODE)) {
            if (bundle.getBoolean(Timers.TIMESUP_MODE, false)) {
                try {
                    mOnEmptyListListener = (OnEmptyListListener) getActivity();
                } catch (ClassCastException e) {
                    Log.wtf(TAG, getActivity().toString() + " must implement OnEmptyListListener");
                }
            }
        }

        mTimersList = (ListView)v.findViewById(R.id.timers_list);

        // Use light's out if this fragment is within the DeskClock
        if (getActivity() instanceof DeskClock) {
            float dividerHeight = getResources().getDimension(R.dimen.timer_divider_height);
            View footerView = inflater.inflate(R.layout.blank_footer_view, mTimersList, false);
            LayoutParams params = footerView.getLayoutParams();
            params.height -= dividerHeight;
            footerView.setLayoutParams(params);
            mTimersList.addFooterView(footerView);
            View headerView = inflater.inflate(R.layout.blank_header_view, mTimersList, false);
            params = headerView.getLayoutParams();
            params.height -= dividerHeight;
            headerView.setLayoutParams(params);
            mTimersList.addHeaderView(headerView);
            mTimersList.setOnTouchListener(new OnTapListener(getActivity()));
            footerView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ((DeskClock) getActivity()).clockOnViewClick(v);
                }
            });
            headerView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ((DeskClock) getActivity()).clockOnViewClick(v);
                }
            });
        } else {
            mTimersList.setBackgroundColor(getResources().getColor(R.color.blackish));
        }

        mNewTimerPage = v.findViewById(R.id.new_timer_page);
        mTimersListPage = v.findViewById(R.id.timers_list_page);
        mTimerSetup = (TimerSetupView)v.findViewById(R.id.timer_setup);
        mSeperator = v.findViewById(R.id.timer_button_sep);
        mCancel = (Button)v.findViewById(R.id.timer_cancel);
        mCancel.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mAdapter.getCount() != 0) {
                    gotoTimersView();
                }
            }
        });
        mStart = (Button)v.findViewById(R.id.timer_start);
        mStart.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // New timer create if timer length is not zero
                // Create a new timer object to track the timer and
                // switch to the timers view.
                int timerLength = mTimerSetup.getTime();
                if (timerLength == 0) {
                    return;
                }
                TimerObj t = new TimerObj(timerLength * 1000);
                t.mState = TimerObj.STATE_RUNNING;
                mAdapter.addTimer(t);
                updateTimersState(t, Timers.START_TIMER);
                gotoTimersView();
            }

        });
        mAddTimer = (ImageButton)v.findViewById(R.id.timer_add_timer);
        mAddTimer.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // This is unchecked because mAddTimer only used within DeskClock
                DeskClock activity = ((DeskClock) getActivity());
                if (!activity.isClockStateNormal()) {
                    activity.bringLightsUp(true);
                    return;
                }
                mTimerSetup.reset();
                gotoSetupView();
            }

        });
        mTimerFooter = v.findViewById(R.id.timer_footer);
        mTimerFooter.setVisibility(mOnEmptyListListener == null ? View.VISIBLE : View.GONE);
        mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
        mNotificationManager = (NotificationManager)
                getActivity().getSystemService(Context.NOTIFICATION_SERVICE);

        return v;
    }

    @Override
    public void onDestroyView() {
        mViewState = new Bundle();
        saveViewState(mViewState);
        super.onDestroyView();
    }

    @Override
    public void onResume() {
        super.onResume();
        mPrefs.registerOnSharedPreferenceChangeListener(this);

        mAdapter = createAdapter(getActivity(), mPrefs);
        mAdapter.onRestoreInstanceState(null);

        if (mPrefs.getBoolean(Timers.FROM_NOTIFICATION, false)) {
            // We need to know if this onresume is being called by the user clicking a
            // buzzing timer notification. If so, we need to set that timer to have "stopped"
            // at the moment the notification was hit.
            long now = mPrefs.getLong(Timers.NOTIF_TIME, Utils.getTimeNow());
            int timerId = mPrefs.getInt(Timers.NOTIF_ID, -1);
            if (timerId != -1) {
                TimerObj t = Timers.findTimer(mAdapter.mTimers, timerId);
                t.mTimeLeft = t.mOriginalLength - (now - t.mStartTime);
                cancelTimerNotification(timerId);
            }
            SharedPreferences.Editor editor = mPrefs.edit();
            editor.putBoolean(Timers.FROM_NOTIFICATION, false);
            editor.apply();
        }

        mTimersList.setAdapter(mAdapter);
        if (mAdapter.getCount() == 0) {
            mCancel.setVisibility(View.GONE);
            mSeperator.setVisibility(View.GONE);
        }
        setPage();
    }

    @Override
    public void onPause() {
        super.onPause();
        stopClockTicks();
        if (mAdapter != null) {
            mAdapter.saveGlobalState ();
        }
        mPrefs.unregisterOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onSaveInstanceState (Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mAdapter != null) {
            mAdapter.onSaveInstanceState (outState);
        }
        if (mNewTimerPage != null) {
            saveViewState(outState);
        } else if (mViewState != null) {
            outState.putAll(mViewState);
        }
    }

    private void saveViewState(Bundle outState) {
        outState.putBoolean(KEY_SETUP_SELECTED, mNewTimerPage.getVisibility() == View.VISIBLE);
        mTimerSetup.saveEntryState(outState, KEY_ENTRY_STATE);
    }

    public void setPage() {
        boolean switchToSetupView = mAdapter.getCount() == 0;
        if (mViewState != null) {
            switchToSetupView |= mViewState.getBoolean(KEY_SETUP_SELECTED, false);
            mTimerSetup.restoreEntryState(mViewState, KEY_ENTRY_STATE);
            mViewState = null;
        }
        if (switchToSetupView) {
            gotoSetupView();
        } else {
            gotoTimersView();
        }
    }

    public void stopAllTimesUpTimers() {
        while (0 < mAdapter.getCount()) {
            TimerObj timerObj = (TimerObj) mAdapter.getItem(0);
            if (timerObj.mState == TimerObj.STATE_TIMESUP) {
                onStopButtonPressed(timerObj);
            }
        }
    }

    private void gotoSetupView() {
        mNewTimerPage.setVisibility(View.VISIBLE);
        mTimersListPage.setVisibility(View.GONE);
        stopClockTicks();
        if (mAdapter.getCount() == 0) {
            mCancel.setVisibility(View.GONE);
            mSeperator.setVisibility(View.GONE);
        } else {
            mSeperator.setVisibility(View.VISIBLE);
            mCancel.setVisibility(View.VISIBLE);
        }
        // Use light's out setup when this fragment is within the DeskClock
        if (getActivity() instanceof DeskClock) {
            ((DeskClock) getActivity()).setTimerAddingTimerState(true);
            ((DeskClock) getActivity()).bringLightsUp(false);
        }
    }
    private void gotoTimersView() {
        mNewTimerPage.setVisibility(View.GONE);
        mTimersListPage.setVisibility(View.VISIBLE);
        startClockTicks();
        // Use light's out setup when this fragment is within the DeskClock
        if (getActivity() instanceof DeskClock) {
            ((DeskClock) getActivity()).setTimerAddingTimerState(false);
            ((DeskClock) getActivity()).bringLightsUp(false);
        }
    }

    @Override
    public void onClick(View v) {
        // Use light's out setup when this fragment is within the DeskClock
        if (getActivity() instanceof DeskClock) {
            ((DeskClock) getActivity()).scheduleLightsOut();
        }
        ClickAction tag = (ClickAction) v.getTag();
        onClickHelper(tag);
    }

    private void onClickHelper(ClickAction clickAction) {
        switch (clickAction.mAction) {
            case ClickAction.ACTION_DELETE:
                TimerObj t = clickAction.mTimer;
                if (t.mState == TimerObj.STATE_TIMESUP) {
                    cancelTimerNotification(t.mTimerId);
                }
                mAdapter.deleteTimer(t.mTimerId);
                if (mAdapter.getCount() == 0) {
                    if (mOnEmptyListListener == null) {
                        mTimerSetup.reset();
                        gotoSetupView();
                    } else {
                        mOnEmptyListListener.onEmptyList();
                    }
                }
                // Tell receiver the timer was deleted.
                // It will stop all activity related to the timer
                updateTimersState(t, Timers.DELETE_TIMER);
                break;
            case ClickAction.ACTION_PLUS_ONE:
                onPlusOneButtonPressed(clickAction.mTimer);
                setTimerButtons(clickAction.mTimer);
                break;
            case ClickAction.ACTION_STOP:
                onStopButtonPressed(clickAction.mTimer);
                setTimerButtons(clickAction.mTimer);
                break;
            default:
                break;
        }
    }

    private void onPlusOneButtonPressed(TimerObj t) {
        switch(t.mState) {
            case TimerObj.STATE_RUNNING:
                 t.addTime(60000); //60 seconds in millis
                 long timeLeft = t.updateTimeLeft(false);
                 ((TimerListItem)(t.mView)).setTime(timeLeft, false);
                 ((TimerListItem)(t.mView)).setLength(timeLeft);
                 mAdapter.notifyDataSetChanged();
                 updateTimersState(t, Timers.TIMER_UPDATE);
                break;
            case TimerObj.STATE_TIMESUP:
                // +1 min when the time is up will restart the timer with 1 minute left.
                t.mState = TimerObj.STATE_RUNNING;
                t.mStartTime = Utils.getTimeNow();
                t.mTimeLeft = t. mOriginalLength = 60000;
                ((TimerListItem)t.mView).setTime(t.mTimeLeft, false);
                ((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, true);
                ((TimerListItem) t.mView).start();
                updateTimersState(t, Timers.TIMER_RESET);
                updateTimersState(t, Timers.START_TIMER);
                updateTimesUpMode(t);
                cancelTimerNotification(t.mTimerId);
                break;
            case TimerObj.STATE_STOPPED:
            case TimerObj.STATE_DONE:
                t.mState = TimerObj.STATE_RESTART;
                t.mTimeLeft = t. mOriginalLength = t.mSetupLength;
                ((TimerListItem)t.mView).stop();
                ((TimerListItem)t.mView).setTime(t.mTimeLeft, false);
                ((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, false);
                updateTimersState(t, Timers.TIMER_RESET);
                break;
            default:
                break;
        }
    }




    private void onStopButtonPressed(TimerObj t) {
        switch(t.mState) {
            case TimerObj.STATE_RUNNING:
                // Stop timer and save the remaining time of the timer
                t.mState = TimerObj.STATE_STOPPED;
                ((TimerListItem) t.mView).pause();
                t.updateTimeLeft(true);
                updateTimersState(t, Timers.TIMER_STOP);
                break;
            case TimerObj.STATE_STOPPED:
                // Reset the remaining time and continue timer
                t.mState = TimerObj.STATE_RUNNING;
                t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
                ((TimerListItem) t.mView).start();
                updateTimersState(t, Timers.START_TIMER);
                break;
            case TimerObj.STATE_TIMESUP:
                t.mState = TimerObj.STATE_DONE;
                ((TimerListItem) t.mView).done();
                updateTimersState(t, Timers.TIMER_DONE);
                cancelTimerNotification(t.mTimerId);
                updateTimesUpMode(t);
                break;
            case TimerObj.STATE_DONE:
                break;
            case TimerObj.STATE_RESTART:
                t.mState = TimerObj.STATE_RUNNING;
                t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
                ((TimerListItem) t.mView).start();
                updateTimersState(t, Timers.START_TIMER);
                break;
            default:
                break;
        }
    }<commit_msg>Fix NPE when stopping off-screen times-up timers<commit_after>/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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.android.deskclock.timer;

import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.android.deskclock.DeskClock;
import com.android.deskclock.CircleButtonsLinearLayout;
import com.android.deskclock.DeskClockFragment;
import com.android.deskclock.LabelDialogFragment;
import com.android.deskclock.R;
import com.android.deskclock.TimerSetupView;
import com.android.deskclock.Utils;
import com.android.deskclock.DeskClock.OnTapListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;


public class TimerFragment extends DeskClockFragment
        implements OnClickListener, OnSharedPreferenceChangeListener {

    private static final String TAG = "TimerFragment";
    private static final String KEY_SETUP_SELECTED = "_setup_selected";
    private static final String KEY_ENTRY_STATE = "entry_state";
    private Bundle mViewState = null;
    private ListView mTimersList;
    private View mNewTimerPage;
    private View mTimersListPage;
    private Button mCancel, mStart;
    private View mSeperator;
    private ImageButton mAddTimer;
    private View mTimerFooter;
    private TimerSetupView mTimerSetup;
    private TimersListAdapter mAdapter;
    private boolean mTicking = false;
    private SharedPreferences mPrefs;
    private NotificationManager mNotificationManager;
    private OnEmptyListListener mOnEmptyListListener;

    public TimerFragment() {
    }

    class ClickAction {
        public static final int ACTION_STOP = 1;
        public static final int ACTION_PLUS_ONE = 2;
        public static final int ACTION_DELETE = 3;

        public int mAction;
        public TimerObj mTimer;

        public ClickAction(int action, TimerObj t) {
            mAction = action;
            mTimer = t;
        }
    }

    // Container Activity that requests TIMESUP_MODE must implement this interface
    public interface OnEmptyListListener {
        public void onEmptyList();
    }

    TimersListAdapter createAdapter(Context context, SharedPreferences prefs) {
        if (mOnEmptyListListener == null) {
            return new TimersListAdapter(context, prefs);
        } else {
            return new TimesUpListAdapter(context, prefs);
        }
    }

    class TimersListAdapter extends BaseAdapter {

        ArrayList<TimerObj> mTimers = new ArrayList<TimerObj> ();
        private final LayoutInflater mInflater;
        Context mContext;
        SharedPreferences mmPrefs;

        public TimersListAdapter(Context context, SharedPreferences prefs) {
            mContext = context;
            mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mmPrefs = prefs;
        }

        @Override
        public int getCount() {
            return mTimers.size();
        }

        @Override
        public Object getItem(int p) {
            return mTimers.get(p);
        }

        @Override
        public long getItemId(int p) {
            if (p >= 0 && p < mTimers.size()) {
                return mTimers.get(p).mTimerId;
            }
            return 0;
        }

        public void deleteTimer(int id) {
            for (int i = 0; i < mTimers.size(); i++) {
                TimerObj t = mTimers.get(i);
                if (t.mTimerId == id) {
                    ((TimerListItem)t.mView).stop();
                    t.deleteFromSharedPref(mmPrefs);
                    mTimers.remove(i);
                    notifyDataSetChanged();
                    return;
                }
            }
        }

        protected int findTimerPositionById(int id) {
            for (int i = 0; i < mTimers.size(); i++) {
                TimerObj t = mTimers.get(i);
                if (t.mTimerId == id) {
                    return i;
                }
            }
            return -1;
        }

        public void removeTimer(TimerObj timerObj) {
            int position = findTimerPositionById(timerObj.mTimerId);
            if (position >= 0) {
                mTimers.remove(position);
                notifyDataSetChanged();
            }
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            TimerListItem v = new TimerListItem (mContext); // TODO: Need to recycle convertView.

            final TimerObj o = (TimerObj)getItem(position);
            o.mView = v;
            long timeLeft =  o.updateTimeLeft(false);
            boolean drawRed = o.mState != TimerObj.STATE_RESTART;
            v.set(o.mOriginalLength, timeLeft, drawRed);
            v.setTime(timeLeft, true);
            switch (o.mState) {
            case TimerObj.STATE_RUNNING:
                v.start();
                break;
            case TimerObj.STATE_TIMESUP:
                v.timesUp();
                break;
            case TimerObj.STATE_DONE:
                v.done();
                break;
            default:
                break;
            }

            // Timer text serves as a virtual start/stop button.
            final CountingTimerView countingTimerView = (CountingTimerView)
                    v.findViewById(R.id.timer_time_text);
            countingTimerView.registerVirtualButtonAction(new Runnable() {
                @Override
                public void run() {
                    TimerFragment.this.onClickHelper(
                            new ClickAction(ClickAction.ACTION_STOP, o));
                }
            });

            ImageButton delete = (ImageButton)v.findViewById(R.id.timer_delete);
            delete.setOnClickListener(TimerFragment.this);
            delete.setTag(new ClickAction(ClickAction.ACTION_DELETE, o));
            ImageButton plusOne = (ImageButton)v. findViewById(R.id.timer_plus_one);
            plusOne.setOnClickListener(TimerFragment.this);
            plusOne.setTag(new ClickAction(ClickAction.ACTION_PLUS_ONE, o));
            TextView stop = (TextView)v. findViewById(R.id.timer_stop);
            stop.setTag(new ClickAction(ClickAction.ACTION_STOP, o));
            TimerFragment.this.setTimerButtons(o);

            v.setBackgroundColor(getResources().getColor(R.color.blackish));
            countingTimerView.registerStopTextView(stop);
            // Use light's out setup when this fragment is within the DeskClock
            if (getActivity() instanceof DeskClock) {
                countingTimerView.registerActivity((DeskClock) getActivity());
            }
            CircleButtonsLinearLayout circleLayout =
                    (CircleButtonsLinearLayout)v.findViewById(R.id.timer_circle);
            circleLayout.setCircleTimerViewIds(
                    R.id.timer_time, R.id.timer_plus_one, R.id.timer_delete, R.id.timer_stop,
                    R.dimen.plusone_reset_button_padding, R.dimen.delete_button_padding,
                    R.id.timer_label, R.id.timer_label_text);

            FrameLayout label = (FrameLayout)v. findViewById(R.id.timer_label);
            ImageButton labelIcon = (ImageButton)v. findViewById(R.id.timer_label_icon);
            TextView labelText = (TextView)v. findViewById(R.id.timer_label_text);
            if (o.mLabel.equals("")) {
                labelText.setVisibility(View.GONE);
                labelIcon.setVisibility(View.VISIBLE);
            } else {
                labelText.setText(o.mLabel);
                labelText.setVisibility(View.VISIBLE);
                labelIcon.setVisibility(View.GONE);
            }
            if (getActivity() instanceof DeskClock) {
                label.setOnTouchListener(new OnTapListener(getActivity(), labelText) {
                    @Override
                    protected void processClick(View v) {
                        onLabelPressed(o);
                    }
                });
            } else {
                labelIcon.setVisibility(View.INVISIBLE);
            }
            return v;
        }

        public void addTimer(TimerObj t) {
            mTimers.add(0, t);
            notifyDataSetChanged();
        }

        public void onSaveInstanceState(Bundle outState) {
            TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
        }

        public void onRestoreInstanceState(Bundle outState) {
            TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers);
            notifyDataSetChanged();
        }

        public void saveGlobalState() {
            TimerObj.putTimersInSharedPrefs(mmPrefs, mTimers);
        }
    }

    class TimesUpListAdapter extends TimersListAdapter {

        public TimesUpListAdapter(Context context, SharedPreferences prefs) {
            super(context, prefs);
        }

        @Override
        public void onSaveInstanceState(Bundle outState) {
            // This adapter has a data subset and never updates entire database
            // Individual timers are updated in button handlers.
        }

        @Override
        public void saveGlobalState() {
            // This adapter has a data subset and never updates entire database
            // Individual timers are updated in button handlers.
        }

        @Override
        public void onRestoreInstanceState(Bundle outState) {
            // This adapter loads a subset
            TimerObj.getTimersFromSharedPrefs(mmPrefs, mTimers, TimerObj.STATE_TIMESUP);

            if (getCount() == 0) {
                mOnEmptyListListener.onEmptyList();
            } else {
                Collections.sort(mTimers, new Comparator<TimerObj>() {
                    @Override
                    public int compare(TimerObj o1, TimerObj o2) {
                       return (int)(o1.mTimeLeft - o2.mTimeLeft);
                    }
                });
            }
        }
    }

    private final Runnable mClockTick = new Runnable() {
        boolean mVisible = true;
        final static int TIME_PERIOD_MS = 1000;
        final static int SPLIT = TIME_PERIOD_MS / 2;

        @Override
        public void run() {
            // Setup for blinking
            boolean visible = Utils.getTimeNow() % TIME_PERIOD_MS < SPLIT;
            boolean toggle = mVisible != visible;
            mVisible = visible;
            for (int i = 0; i < mAdapter.getCount(); i ++) {
                TimerObj t = (TimerObj) mAdapter.getItem(i);
                if (t.mState == TimerObj.STATE_RUNNING || t.mState == TimerObj.STATE_TIMESUP) {
                    long timeLeft = t.updateTimeLeft(false);
                    if ((TimerListItem)(t.mView) != null) {
                        ((TimerListItem)(t.mView)).setTime(timeLeft, false);
                    }
                }
                if (t.mTimeLeft <= 0 && t.mState != TimerObj.STATE_DONE
                        && t.mState != TimerObj.STATE_RESTART) {
                    t.mState = TimerObj.STATE_TIMESUP;
                    TimerFragment.this.setTimerButtons(t);
                    if ((TimerListItem)(t.mView) != null) {
                        ((TimerListItem)(t.mView)).timesUp();
                    }
                }

                // The blinking
                if (toggle && (TimerListItem)(t.mView) != null) {
                    if (t.mState == TimerObj.STATE_TIMESUP) {
                        ((TimerListItem)(t.mView)).setCircleBlink(mVisible);
                    }
                    if (t.mState == TimerObj.STATE_STOPPED) {
                        ((TimerListItem)(t.mView)).setTextBlink(mVisible);
                    }
                }
            }
            mTimersList.postDelayed(mClockTick, 20);
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // Cache instance data and consume in first call to setupPage()
        if (savedInstanceState != null) {
            mViewState = savedInstanceState;
        }

        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View v = inflater.inflate(R.layout.timer_fragment, container, false);

        // Handle arguments from parent
        Bundle bundle = getArguments();
        if (bundle != null && bundle.containsKey(Timers.TIMESUP_MODE)) {
            if (bundle.getBoolean(Timers.TIMESUP_MODE, false)) {
                try {
                    mOnEmptyListListener = (OnEmptyListListener) getActivity();
                } catch (ClassCastException e) {
                    Log.wtf(TAG, getActivity().toString() + " must implement OnEmptyListListener");
                }
            }
        }

        mTimersList = (ListView)v.findViewById(R.id.timers_list);

        // Use light's out if this fragment is within the DeskClock
        if (getActivity() instanceof DeskClock) {
            float dividerHeight = getResources().getDimension(R.dimen.timer_divider_height);
            View footerView = inflater.inflate(R.layout.blank_footer_view, mTimersList, false);
            LayoutParams params = footerView.getLayoutParams();
            params.height -= dividerHeight;
            footerView.setLayoutParams(params);
            mTimersList.addFooterView(footerView);
            View headerView = inflater.inflate(R.layout.blank_header_view, mTimersList, false);
            params = headerView.getLayoutParams();
            params.height -= dividerHeight;
            headerView.setLayoutParams(params);
            mTimersList.addHeaderView(headerView);
            mTimersList.setOnTouchListener(new OnTapListener(getActivity()));
            footerView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ((DeskClock) getActivity()).clockOnViewClick(v);
                }
            });
            headerView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    ((DeskClock) getActivity()).clockOnViewClick(v);
                }
            });
        } else {
            mTimersList.setBackgroundColor(getResources().getColor(R.color.blackish));
        }

        mNewTimerPage = v.findViewById(R.id.new_timer_page);
        mTimersListPage = v.findViewById(R.id.timers_list_page);
        mTimerSetup = (TimerSetupView)v.findViewById(R.id.timer_setup);
        mSeperator = v.findViewById(R.id.timer_button_sep);
        mCancel = (Button)v.findViewById(R.id.timer_cancel);
        mCancel.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mAdapter.getCount() != 0) {
                    gotoTimersView();
                }
            }
        });
        mStart = (Button)v.findViewById(R.id.timer_start);
        mStart.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // New timer create if timer length is not zero
                // Create a new timer object to track the timer and
                // switch to the timers view.
                int timerLength = mTimerSetup.getTime();
                if (timerLength == 0) {
                    return;
                }
                TimerObj t = new TimerObj(timerLength * 1000);
                t.mState = TimerObj.STATE_RUNNING;
                mAdapter.addTimer(t);
                updateTimersState(t, Timers.START_TIMER);
                gotoTimersView();
            }

        });
        mAddTimer = (ImageButton)v.findViewById(R.id.timer_add_timer);
        mAddTimer.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // This is unchecked because mAddTimer only used within DeskClock
                DeskClock activity = ((DeskClock) getActivity());
                if (!activity.isClockStateNormal()) {
                    activity.bringLightsUp(true);
                    return;
                }
                mTimerSetup.reset();
                gotoSetupView();
            }

        });
        mTimerFooter = v.findViewById(R.id.timer_footer);
        mTimerFooter.setVisibility(mOnEmptyListListener == null ? View.VISIBLE : View.GONE);
        mPrefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
        mNotificationManager = (NotificationManager)
                getActivity().getSystemService(Context.NOTIFICATION_SERVICE);

        return v;
    }

    @Override
    public void onDestroyView() {
        mViewState = new Bundle();
        saveViewState(mViewState);
        super.onDestroyView();
    }

    @Override
    public void onResume() {
        super.onResume();
        mPrefs.registerOnSharedPreferenceChangeListener(this);

        mAdapter = createAdapter(getActivity(), mPrefs);
        mAdapter.onRestoreInstanceState(null);

        if (mPrefs.getBoolean(Timers.FROM_NOTIFICATION, false)) {
            // We need to know if this onresume is being called by the user clicking a
            // buzzing timer notification. If so, we need to set that timer to have "stopped"
            // at the moment the notification was hit.
            long now = mPrefs.getLong(Timers.NOTIF_TIME, Utils.getTimeNow());
            int timerId = mPrefs.getInt(Timers.NOTIF_ID, -1);
            if (timerId != -1) {
                TimerObj t = Timers.findTimer(mAdapter.mTimers, timerId);
                t.mTimeLeft = t.mOriginalLength - (now - t.mStartTime);
                cancelTimerNotification(timerId);
            }
            SharedPreferences.Editor editor = mPrefs.edit();
            editor.putBoolean(Timers.FROM_NOTIFICATION, false);
            editor.apply();
        }

        mTimersList.setAdapter(mAdapter);
        if (mAdapter.getCount() == 0) {
            mCancel.setVisibility(View.GONE);
            mSeperator.setVisibility(View.GONE);
        }
        setPage();
    }

    @Override
    public void onPause() {
        super.onPause();
        stopClockTicks();
        if (mAdapter != null) {
            mAdapter.saveGlobalState ();
        }
        mPrefs.unregisterOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onSaveInstanceState (Bundle outState) {
        super.onSaveInstanceState(outState);
        if (mAdapter != null) {
            mAdapter.onSaveInstanceState (outState);
        }
        if (mNewTimerPage != null) {
            saveViewState(outState);
        } else if (mViewState != null) {
            outState.putAll(mViewState);
        }
    }

    private void saveViewState(Bundle outState) {
        outState.putBoolean(KEY_SETUP_SELECTED, mNewTimerPage.getVisibility() == View.VISIBLE);
        mTimerSetup.saveEntryState(outState, KEY_ENTRY_STATE);
    }

    public void setPage() {
        boolean switchToSetupView = mAdapter.getCount() == 0;
        if (mViewState != null) {
            switchToSetupView |= mViewState.getBoolean(KEY_SETUP_SELECTED, false);
            mTimerSetup.restoreEntryState(mViewState, KEY_ENTRY_STATE);
            mViewState = null;
        }
        if (switchToSetupView) {
            gotoSetupView();
        } else {
            gotoTimersView();
        }
    }

    public void stopAllTimesUpTimers() {
        while (0 < mAdapter.getCount()) {
            TimerObj timerObj = (TimerObj) mAdapter.getItem(0);
            if (timerObj.mState == TimerObj.STATE_TIMESUP) {
                onStopButtonPressed(timerObj);
            }
        }
    }

    private void gotoSetupView() {
        mNewTimerPage.setVisibility(View.VISIBLE);
        mTimersListPage.setVisibility(View.GONE);
        stopClockTicks();
        if (mAdapter.getCount() == 0) {
            mCancel.setVisibility(View.GONE);
            mSeperator.setVisibility(View.GONE);
        } else {
            mSeperator.setVisibility(View.VISIBLE);
            mCancel.setVisibility(View.VISIBLE);
        }
        // Use light's out setup when this fragment is within the DeskClock
        if (getActivity() instanceof DeskClock) {
            ((DeskClock) getActivity()).setTimerAddingTimerState(true);
            ((DeskClock) getActivity()).bringLightsUp(false);
        }
    }
    private void gotoTimersView() {
        mNewTimerPage.setVisibility(View.GONE);
        mTimersListPage.setVisibility(View.VISIBLE);
        startClockTicks();
        // Use light's out setup when this fragment is within the DeskClock
        if (getActivity() instanceof DeskClock) {
            ((DeskClock) getActivity()).setTimerAddingTimerState(false);
            ((DeskClock) getActivity()).bringLightsUp(false);
        }
    }

    @Override
    public void onClick(View v) {
        // Use light's out setup when this fragment is within the DeskClock
        if (getActivity() instanceof DeskClock) {
            ((DeskClock) getActivity()).scheduleLightsOut();
        }
        ClickAction tag = (ClickAction) v.getTag();
        onClickHelper(tag);
    }

    private void onClickHelper(ClickAction clickAction) {
        switch (clickAction.mAction) {
            case ClickAction.ACTION_DELETE:
                TimerObj t = clickAction.mTimer;
                if (t.mState == TimerObj.STATE_TIMESUP) {
                    cancelTimerNotification(t.mTimerId);
                }
                mAdapter.deleteTimer(t.mTimerId);
                if (mAdapter.getCount() == 0) {
                    if (mOnEmptyListListener == null) {
                        mTimerSetup.reset();
                        gotoSetupView();
                    } else {
                        mOnEmptyListListener.onEmptyList();
                    }
                }
                // Tell receiver the timer was deleted.
                // It will stop all activity related to the timer
                updateTimersState(t, Timers.DELETE_TIMER);
                break;
            case ClickAction.ACTION_PLUS_ONE:
                onPlusOneButtonPressed(clickAction.mTimer);
                setTimerButtons(clickAction.mTimer);
                break;
            case ClickAction.ACTION_STOP:
                onStopButtonPressed(clickAction.mTimer);
                setTimerButtons(clickAction.mTimer);
                break;
            default:
                break;
        }
    }

    private void onPlusOneButtonPressed(TimerObj t) {
        switch(t.mState) {
            case TimerObj.STATE_RUNNING:
                 t.addTime(60000); //60 seconds in millis
                 long timeLeft = t.updateTimeLeft(false);
                 ((TimerListItem)(t.mView)).setTime(timeLeft, false);
                 ((TimerListItem)(t.mView)).setLength(timeLeft);
                 mAdapter.notifyDataSetChanged();
                 updateTimersState(t, Timers.TIMER_UPDATE);
                break;
            case TimerObj.STATE_TIMESUP:
                // +1 min when the time is up will restart the timer with 1 minute left.
                t.mState = TimerObj.STATE_RUNNING;
                t.mStartTime = Utils.getTimeNow();
                t.mTimeLeft = t. mOriginalLength = 60000;
                ((TimerListItem)t.mView).setTime(t.mTimeLeft, false);
                ((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, true);
                ((TimerListItem) t.mView).start();
                updateTimersState(t, Timers.TIMER_RESET);
                updateTimersState(t, Timers.START_TIMER);
                updateTimesUpMode(t);
                cancelTimerNotification(t.mTimerId);
                break;
            case TimerObj.STATE_STOPPED:
            case TimerObj.STATE_DONE:
                t.mState = TimerObj.STATE_RESTART;
                t.mTimeLeft = t. mOriginalLength = t.mSetupLength;
                ((TimerListItem)t.mView).stop();
                ((TimerListItem)t.mView).setTime(t.mTimeLeft, false);
                ((TimerListItem)t.mView).set(t.mOriginalLength, t.mTimeLeft, false);
                updateTimersState(t, Timers.TIMER_RESET);
                break;
            default:
                break;
        }
    }




    private void onStopButtonPressed(TimerObj t) {
        switch(t.mState) {
            case TimerObj.STATE_RUNNING:
                // Stop timer and save the remaining time of the timer
                t.mState = TimerObj.STATE_STOPPED;
                ((TimerListItem) t.mView).pause();
                t.updateTimeLeft(true);
                updateTimersState(t, Timers.TIMER_STOP);
                break;
            case TimerObj.STATE_STOPPED:
                // Reset the remaining time and continue timer
                t.mState = TimerObj.STATE_RUNNING;
                t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
                ((TimerListItem) t.mView).start();
                updateTimersState(t, Timers.START_TIMER);
                break;
            case TimerObj.STATE_TIMESUP:
                t.mState = TimerObj.STATE_DONE;
                // Used in a context where the timer could be off-screen and without a view
                if (t.mView != null) {
                    ((TimerListItem) t.mView).done();
                }
                updateTimersState(t, Timers.TIMER_DONE);
                cancelTimerNotification(t.mTimerId);
                updateTimesUpMode(t);
                break;
            case TimerObj.STATE_DONE:
                break;
            case TimerObj.STATE_RESTART:
                t.mState = TimerObj.STATE_RUNNING;
                t.mStartTime = Utils.getTimeNow() - (t.mOriginalLength - t.mTimeLeft);
                ((TimerListItem) t.mView).start();
                updateTimersState(t, Timers.START_TIMER);
                break;
            default:
                break;
        }
    }
{'fraction_non_alphanumeric': 0.08923845927668334, 'fraction_numerical': 0.0022052337547780066, 'mean_word_length': 1.8673727473917168, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 13, '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': '7385600', 'n_tokens_mistral': 14930, 'n_tokens_neox': 14109, 'n_words': 3005}