/*
 * Copyright (C) 2015 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.messaging.datamodel.action;

import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

import androidx.test.filters.MediumTest;

import com.android.messaging.Factory;
import com.android.messaging.FakeContext;
import com.android.messaging.FakeContext.FakeContextHost;
import com.android.messaging.FakeFactory;
import com.android.messaging.datamodel.BugleServiceTestCase;
import com.android.messaging.datamodel.FakeDataModel;
import com.android.messaging.datamodel.action.ActionMonitor.ActionCompletedListener;
import com.android.messaging.datamodel.action.ActionMonitor.ActionStateChangedListener;
import com.android.messaging.datamodel.action.ActionTestHelpers.ResultTracker;
import com.android.messaging.datamodel.action.ActionTestHelpers.StubBackgroundWorker;
import com.android.messaging.datamodel.action.ActionTestHelpers.StubLoader;

import java.util.ArrayList;

@MediumTest
public class ActionServiceTest extends BugleServiceTestCase<ActionServiceImpl>
        implements FakeContextHost, ActionStateChangedListener, ActionCompletedListener {
    private static final String TAG = "ActionServiceTest";

    @Override
    public void onActionStateChanged(final Action action, final int state) {
        mStates.add(state);
    }

    @Override
    public void onActionSucceeded(final ActionMonitor monitor,
            final Action action, final Object data, final Object result) {
        final TestChatAction test = (TestChatAction) action;
        assertNotSame(test.dontRelyOnMe, dontRelyOnMe);
        // This will be true - but only briefly
        assertEquals(test.dontRelyOnMe, becauseIChange);

        final ResultTracker tracker = (ResultTracker) data;
        tracker.completionResult = result;
        synchronized(tracker) {
            tracker.notifyAll();
        }
    }

    @Override
    public void onActionFailed(final ActionMonitor monitor, final Action action,
            final Object data, final Object result) {
        final TestChatAction test = (TestChatAction) action;
        assertNotSame(test.dontRelyOnMe, dontRelyOnMe);
        // This will be true - but only briefly
        assertEquals(test.dontRelyOnMe, becauseIChange);

        final ResultTracker tracker = (ResultTracker) data;
        tracker.completionResult = result;
        synchronized(tracker) {
            tracker.notifyAll();
        }
    }

    /**
     * For a chat action verify that the service intent is constructed and queued correctly and
     * that when that intent is processed it actually executes the action.
     */
    public void testChatServiceCreatesIntentAndExecutesAction() {
        final ResultTracker tracker = new ResultTracker();

        final TestChatActionMonitor monitor = new TestChatActionMonitor(null, tracker, this, this);
        final TestChatAction action = new TestChatAction(monitor.getActionKey(), parameter);

        action.dontRelyOnMe = dontRelyOnMe;
        assertFalse("Expect service initially stopped", mServiceStarted);

        synchronized(mWorker) {
            try {
                action.start(monitor);
                // Wait for callback across threads
                mWorker.wait(2000);
                mServiceStarted = true;
            } catch (final InterruptedException e) {
                assertTrue("Interrupted waiting for execution", false);
            }
        }

        assertTrue("Expect service started", mServiceStarted);

        assertEquals("Expect three states ", mStates.size(), 3);
        assertEquals("State-0 should be STATE_QUEUED", (int)mStates.get(0),
                ActionMonitor.STATE_QUEUED);
        assertEquals("State-1 should be STATE_EXECUTING", (int)mStates.get(1),
                ActionMonitor.STATE_EXECUTING);
        assertEquals("State-2 should be STATE_COMPLETE", (int)mStates.get(2),
                ActionMonitor.STATE_COMPLETE);
    }

    StubBackgroundWorker mWorker;
    FakeContext mContext;
    StubLoader mLoader;
    ActionService mService;

    ArrayList<Integer> mStates;

    private static final String parameter = "parameter";
    private static final Object dontRelyOnMe = "dontRelyOnMe";
    private static final Object becauseIChange = "becauseIChange";
    private static final Object executeActionResult = "executeActionResult";
    private static final Object processResponseResult = "processResponseResult";
    private static final Object processFailureResult = "processFailureResult";

    public ActionServiceTest() {
        super(ActionServiceImpl.class);
    }

    @Override
    public void setUp() throws Exception {
        super.setUp();
        Log.d(TAG, "ChatActionTest setUp");

        sLooper = Looper.myLooper();

        mWorker = new StubBackgroundWorker();
        mContext = new FakeContext(getContext(), this);
        FakeFactory.registerWithFakeContext(getContext(),mContext)
                .withDataModel(new FakeDataModel(mContext)
                .withBackgroundWorkerForActionService(mWorker)
                .withActionService(new ActionService()));

        mStates = new ArrayList<Integer>();
        setContext(Factory.get().getApplicationContext());
    }

    @Override
    public String getServiceClassName() {
        return ActionServiceImpl.class.getName();
    }

    boolean mServiceStarted = false;

    @Override
    public void startServiceForStub(final Intent intent) {
        // Do nothing until later
        assertFalse(mServiceStarted);
        mServiceStarted = true;
    }

    @Override
    public void onStartCommandForStub(final Intent intent, final int flags, final int startId) {
        assertTrue(mServiceStarted);
    }

    private static Looper sLooper;
    public static void assertRunsOnOtherThread() {
        assertTrue (Looper.myLooper() != Looper.getMainLooper());
        assertTrue (Looper.myLooper() != sLooper);
    }

    public static class TestChatAction extends Action implements Parcelable {
        public static String RESPONSE_TEST = "response_test";
        public static String KEY_PARAMETER = "parameter";

        protected TestChatAction(final String key, final String parameter) {
            super(key);
            this.actionParameters.putString(KEY_PARAMETER, parameter);
        }

        transient Object dontRelyOnMe;

        /**
         * Process the action locally - runs on service thread
         */
        @Override
        protected Object executeAction() {
            this.dontRelyOnMe = becauseIChange;
            assertRunsOnOtherThread();
            return executeActionResult;
        }

        /**
         * Process the response from the server - runs on service thread
         */
        @Override
        protected Object processBackgroundResponse(final Bundle response) {
            assertRunsOnOtherThread();
            return processResponseResult;
        }

        /**
         * Called in case of failures when sending requests - runs on service thread
         */
        @Override
        protected Object processBackgroundFailure() {
            assertRunsOnOtherThread();
            return processFailureResult;
        }

        private TestChatAction(final Parcel in) {
            super(in);
        }

        public static final Parcelable.Creator<TestChatAction> CREATOR
                = new Parcelable.Creator<TestChatAction>() {
            @Override
            public TestChatAction createFromParcel(final Parcel in) {
                return new TestChatAction(in);
            }

            @Override
            public TestChatAction[] newArray(final int size) {
                return new TestChatAction[size];
            }
        };

        @Override
        public void writeToParcel(final Parcel parcel, final int flags) {
            writeActionToParcel(parcel, flags);
        }
    }

    /**
     * An operation that notifies a listener upon state changes, execution and completion
     */
    public static class TestChatActionMonitor extends ActionMonitor {
        public TestChatActionMonitor(final String baseKey, final Object data,
                final ActionStateChangedListener listener, final ActionCompletedListener executed) {
            super(STATE_CREATED, Action.generateUniqueActionKey(baseKey), data);
            setStateChangedListener(listener);
            setCompletedListener(executed);
            assertEquals("Initial state should be STATE_CREATED", mState, STATE_CREATED);
        }
    }
}
