/*
 * Copyright (C) 2020 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.internal.inputmethod;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.ActivityThread;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodManagerGlobal;

import java.io.PrintWriter;

/**
 *
 * An abstract class that declares the methods for ime trace related operations - enable trace,
 * schedule trace and add new trace to buffer. Both the client and server side classes can use
 * it by getting an implementation through {@link ImeTracing#getInstance()}.
 */
public abstract class ImeTracing {

    static final String TAG = "imeTracing";
    public static final String PROTO_ARG = "--proto-com-android-imetracing";

    /* Constants describing the component type that triggered a dump. */
    public static final int IME_TRACING_FROM_CLIENT = 0;
    public static final int IME_TRACING_FROM_IMS = 1;
    public static final int IME_TRACING_FROM_IMMS = 2;

    private static ImeTracing sInstance;
    static boolean sEnabled = false;

    private final boolean mIsAvailable = InputMethodManagerGlobal.isImeTraceAvailable();

    protected boolean mDumpInProgress;
    protected final Object mDumpInProgressLock = new Object();

    /**
     * Returns an instance of {@link ImeTracingServerImpl} when called from a server side class
     * and an instance of {@link ImeTracingClientImpl} when called from a client side class.
     * Useful to schedule a dump for next frame or save a dump when certain methods are called.
     *
     * @return Instance of one of the children classes of {@link ImeTracing}
     */
    public static ImeTracing getInstance() {
        if (sInstance == null) {
            if (android.tracing.Flags.perfettoIme()) {
                sInstance = new ImeTracingPerfettoImpl();
            } else if (isSystemProcess()) {
                sInstance = new ImeTracingServerImpl();
            } else {
                sInstance = new ImeTracingClientImpl();
            }
        }
        return sInstance;
    }

    /**
     * Transmits the information from client or InputMethodService side to the server, in order to
     * be stored persistently to the current IME tracing dump.
     *
     * @param protoDump client or service side information to be stored by the server
     * @param source where the information is coming from, refer to {@see #IME_TRACING_FROM_CLIENT}
     * and {@see #IME_TRACING_FROM_IMS}
     * @param where
     */
    protected void sendToService(byte[] protoDump, int source, String where) {
        InputMethodManagerGlobal.startProtoDump(protoDump, source, where,
                e -> Log.e(TAG, "Exception while sending ime-related dump to server", e));
    }

    /**
     * Start IME trace.
     */
    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
    public final void startImeTrace() {
        InputMethodManagerGlobal.startImeTrace(e -> Log.e(TAG, "Could not start ime trace.", e));
    }

    /**
     * Stop IME trace.
     */
    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
    public final void stopImeTrace() {
        InputMethodManagerGlobal.stopImeTrace(e -> Log.e(TAG, "Could not stop ime trace.", e));
    }

    /**
     * @param proto dump to be added to the buffer
     */
    public abstract void addToBuffer(ProtoOutputStream proto, int source);

    /**
     * Starts a proto dump of the client side information.
     *
     * @param where Place where the trace was triggered.
     * @param immInstance The {@link InputMethodManager} instance to dump.
     * @param icProto {@link android.view.inputmethod.InputConnection} call data in proto format.
     */
    public abstract void triggerClientDump(String where, InputMethodManager immInstance,
            @Nullable byte[] icProto);

    /**
     * A delegate for {@link #triggerServiceDump(String, ServiceDumper, byte[])}.
     */
    @FunctionalInterface
    public interface ServiceDumper {
        /**
         * Dumps internal data into {@link ProtoOutputStream}.
         *
         * @param proto {@link ProtoOutputStream} to be dumped into.
         * @param icProto {@link android.view.inputmethod.InputConnection} call data in proto
         *                format.
         */
        void dumpToProto(ProtoOutputStream proto, @Nullable byte[] icProto);
    }

    /**
     * Starts a proto dump of the currently connected InputMethodService information.
     *
     * @param where Place where the trace was triggered.
     * @param dumper {@link ServiceDumper} to be used to dump
     *               {@link android.inputmethodservice.InputMethodService}.
     * @param icProto {@link android.view.inputmethod.InputConnection} call data in proto format.
     */
    public abstract void triggerServiceDump(String where, ServiceDumper dumper,
            @Nullable byte[] icProto);

    /**
     * Starts a proto dump of the InputMethodManagerService information.
     *
     * @param where Place where the trace was triggered.
     */
    public abstract void triggerManagerServiceDump(String where, @NonNull ServiceDumper dumper);

    /**
     * Being called while taking a bugreport so that tracing files can be included in the bugreport
     * when the IME tracing is running.  Does nothing otherwise.
     *
     * @param pw Print writer
     */
    public void saveForBugreport(@Nullable PrintWriter pw) {
        // does nothing by default.
    }

    /**
     * Sets whether ime tracing is enabled.
     *
     * @param enabled Tells whether ime tracing should be enabled or disabled.
     */
    public void setEnabled(boolean enabled) {
        sEnabled = enabled;
    }

    /**
     * @return {@code true} if dumping is enabled, {@code false} otherwise.
     */
    public boolean isEnabled() {
        return sEnabled;
    }

    /**
     * @return {@code true} if tracing is available, {@code false} otherwise.
     */
    public boolean isAvailable() {
        return mIsAvailable;
    }

    /**
     * Starts a new IME trace if one is not already started.
     *
     * @param pw Print writer
     */
    public abstract void startTrace(@Nullable PrintWriter pw);

    /**
     * Stops the IME trace if one was previously started and writes the current buffers to disk.
     *
     * @param pw Print writer
     */
    public abstract void stopTrace(@Nullable PrintWriter pw);

    private static boolean isSystemProcess() {
        return ActivityThread.isSystem();
    }

    protected void logAndPrintln(@Nullable PrintWriter pw, String msg) {
        Log.i(TAG, msg);
        if (pw != null) {
            pw.println(msg);
            pw.flush();
        }
    }

}
