/*
 * Copyright 2019 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 android.media.tv.tuner.filter;

import android.annotation.BytesLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.hardware.tv.tuner.V1_0.Constants;
import android.media.tv.tuner.Tuner;
import android.media.tv.tuner.Tuner.Result;
import android.media.tv.tuner.TunerUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;

/**
 * Tuner data filter.
 *
 * <p>This class is used to filter wanted data according to the filter's configuration.
 *
 * @hide
 */
@SystemApi
public class Filter implements AutoCloseable {
    /** @hide */
    @IntDef(prefix = "TYPE_",
            value = {TYPE_TS, TYPE_MMTP, TYPE_IP, TYPE_TLV, TYPE_ALP})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Type {}

    /**
     * Undefined filter type.
     */
    public static final int TYPE_UNDEFINED = 0;
    /**
     * TS filter type.
     */
    public static final int TYPE_TS = Constants.DemuxFilterMainType.TS;
    /**
     * MMTP filter type.
     */
    public static final int TYPE_MMTP = Constants.DemuxFilterMainType.MMTP;
    /**
     * IP filter type.
     */
    public static final int TYPE_IP = Constants.DemuxFilterMainType.IP;
    /**
     * TLV filter type.
     */
    public static final int TYPE_TLV = Constants.DemuxFilterMainType.TLV;
    /**
     * ALP filter type.
     */
    public static final int TYPE_ALP = Constants.DemuxFilterMainType.ALP;

    /** @hide */
    @IntDef(prefix = "SUBTYPE_",
            value = {SUBTYPE_UNDEFINED, SUBTYPE_SECTION, SUBTYPE_PES, SUBTYPE_AUDIO, SUBTYPE_VIDEO,
                    SUBTYPE_DOWNLOAD, SUBTYPE_RECORD, SUBTYPE_TS, SUBTYPE_PCR, SUBTYPE_TEMI,
                    SUBTYPE_MMTP, SUBTYPE_NTP, SUBTYPE_IP_PAYLOAD, SUBTYPE_IP,
                    SUBTYPE_PAYLOAD_THROUGH, SUBTYPE_TLV, SUBTYPE_PTP, })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Subtype {}
    /**
     * Filter subtype undefined.
     */
    public static final int SUBTYPE_UNDEFINED = 0;
    /**
     * Section filter subtype.
     */
    public static final int SUBTYPE_SECTION = 1;
    /**
     * PES filter subtype.
     */
    public static final int SUBTYPE_PES = 2;
    /**
     * Audio filter subtype.
     */
    public static final int SUBTYPE_AUDIO = 3;
    /**
     * Video filter subtype.
     */
    public static final int SUBTYPE_VIDEO = 4;
    /**
     * Download filter subtype.
     */
    public static final int SUBTYPE_DOWNLOAD = 5;
    /**
     * Record filter subtype.
     */
    public static final int SUBTYPE_RECORD = 6;
    /**
     * TS filter subtype.
     */
    public static final int SUBTYPE_TS = 7;
    /**
     * PCR filter subtype.
     */
    public static final int SUBTYPE_PCR = 8;
    /**
     * TEMI filter subtype.
     */
    public static final int SUBTYPE_TEMI = 9;
    /**
     * MMTP filter subtype.
     */
    public static final int SUBTYPE_MMTP = 10;
    /**
     * NTP filter subtype.
     */
    public static final int SUBTYPE_NTP = 11;
    /**
     * Payload filter subtype.
     */
    public static final int SUBTYPE_IP_PAYLOAD = 12;
    /**
     * IP filter subtype.
     */
    public static final int SUBTYPE_IP = 13;
    /**
     * Payload through filter subtype.
     */
    public static final int SUBTYPE_PAYLOAD_THROUGH = 14;
    /**
     * TLV filter subtype.
     */
    public static final int SUBTYPE_TLV = 15;
    /**
     * PTP filter subtype.
     */
    public static final int SUBTYPE_PTP = 16;



    /** @hide */
    @IntDef(flag = true, prefix = "STATUS_", value = {STATUS_DATA_READY, STATUS_LOW_WATER,
            STATUS_HIGH_WATER, STATUS_OVERFLOW})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Status {}

    /**
     * The status of a filter that the data in the filter buffer is ready to be read.
     */
    public static final int STATUS_DATA_READY = Constants.DemuxFilterStatus.DATA_READY;
    /**
     * The status of a filter that the amount of available data in the filter buffer is at low
     * level.
     *
     * The value is set to 25 percent of the buffer size by default. It can be changed when
     * configuring the filter.
     */
    public static final int STATUS_LOW_WATER = Constants.DemuxFilterStatus.LOW_WATER;
    /**
     * The status of a filter that the amount of available data in the filter buffer is at high
     * level.
     * The value is set to 75 percent of the buffer size by default. It can be changed when
     * configuring the filter.
     */
    public static final int STATUS_HIGH_WATER = Constants.DemuxFilterStatus.HIGH_WATER;
    /**
     * The status of a filter that the filter buffer is full and newly filtered data is being
     * discarded.
     */
    public static final int STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;

    private static final String TAG = "Filter";

    private long mNativeContext;
    private FilterCallback mCallback;
    private Executor mExecutor;
    private final int mId;
    private int mMainType;
    private int mSubtype;
    private Filter mSource;
    private boolean mStarted;
    private boolean mIsClosed = false;
    private final Object mLock = new Object();

    private native int nativeConfigureFilter(
            int type, int subType, FilterConfiguration settings);
    private native int nativeGetId();
    private native int nativeSetDataSource(Filter source);
    private native int nativeStartFilter();
    private native int nativeStopFilter();
    private native int nativeFlushFilter();
    private native int nativeRead(byte[] buffer, long offset, long size);
    private native int nativeClose();

    // Called by JNI
    private Filter(int id) {
        mId = id;
    }

    private void onFilterStatus(int status) {
        if (mCallback != null && mExecutor != null) {
            mExecutor.execute(() -> mCallback.onFilterStatusChanged(this, status));
        }
    }

    private void onFilterEvent(FilterEvent[] events) {
        if (mCallback != null && mExecutor != null) {
            mExecutor.execute(() -> mCallback.onFilterEvent(this, events));
        }
    }

    /** @hide */
    public void setType(@Type int mainType, @Subtype int subtype) {
        mMainType = mainType;
        mSubtype = TunerUtils.getFilterSubtype(mainType, subtype);
    }

    /** @hide */
    public void setCallback(FilterCallback cb, Executor executor) {
        mCallback = cb;
        mExecutor = executor;
    }

    /** @hide */
    public FilterCallback getCallback() {
        return mCallback;
    }

    /**
     * Configures the filter.
     *
     * @param config the configuration of the filter.
     * @return result status of the operation.
     */
    @Result
    public int configure(@NonNull FilterConfiguration config) {
        synchronized (mLock) {
            TunerUtils.checkResourceState(TAG, mIsClosed);
            Settings s = config.getSettings();
            int subType = (s == null) ? mSubtype : s.getType();
            if (mMainType != config.getType() || mSubtype != subType) {
                throw new IllegalArgumentException("Invalid filter config. filter main type="
                        + mMainType + ", filter subtype=" + mSubtype + ". config main type="
                        + config.getType() + ", config subtype=" + subType);
            }
            return nativeConfigureFilter(config.getType(), subType, config);
        }
    }

    /**
     * Gets the filter Id.
     */
    public int getId() {
        synchronized (mLock) {
            TunerUtils.checkResourceState(TAG, mIsClosed);
            return nativeGetId();
        }
    }

    /**
     * Sets the filter's data source.
     *
     * A filter uses demux as data source by default. If the data was packetized
     * by multiple protocols, multiple filters may need to work together to
     * extract all protocols' header. Then a filter's data source can be output
     * from another filter.
     *
     * @param source the filter instance which provides data input. Switch to
     * use demux as data source if the filter instance is NULL.
     * @return result status of the operation.
     * @throws IllegalStateException if the data source has been set.
     */
    @Result
    public int setDataSource(@Nullable Filter source) {
        synchronized (mLock) {
            TunerUtils.checkResourceState(TAG, mIsClosed);
            if (mSource != null) {
                throw new IllegalStateException("Data source is existing");
            }
            int res = nativeSetDataSource(source);
            if (res == Tuner.RESULT_SUCCESS) {
                mSource = source;
            }
            return res;
        }
    }

    /**
     * Starts filtering data.
     *
     * <p>Does nothing if the filter is already started.
     *
     * @return result status of the operation.
     */
    @Result
    public int start() {
        synchronized (mLock) {
            TunerUtils.checkResourceState(TAG, mIsClosed);
            return nativeStartFilter();
        }
    }


    /**
     * Stops filtering data.
     *
     * <p>Does nothing if the filter is stopped or not started.
     *
     * @return result status of the operation.
     */
    @Result
    public int stop() {
        synchronized (mLock) {
            TunerUtils.checkResourceState(TAG, mIsClosed);
            return nativeStopFilter();
        }
    }

    /**
     * Flushes the filter.
     *
     * <p>The data which is already produced by filter but not consumed yet will
     * be cleared.
     *
     * @return result status of the operation.
     */
    @Result
    public int flush() {
        synchronized (mLock) {
            TunerUtils.checkResourceState(TAG, mIsClosed);
            return nativeFlushFilter();
        }
    }

    /**
     * Copies filtered data from filter output to the given byte array.
     *
     * @param buffer the buffer to store the filtered data.
     * @param offset the index of the first byte in {@code buffer} to write.
     * @param size the maximum number of bytes to read.
     * @return the number of bytes read.
     */
    public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
        synchronized (mLock) {
            TunerUtils.checkResourceState(TAG, mIsClosed);
            size = Math.min(size, buffer.length - offset);
            return nativeRead(buffer, offset, size);
        }
    }

    /**
     * Stops filtering data and releases the Filter instance.
     */
    @Override
    public void close() {
        synchronized (mLock) {
            if (mIsClosed) {
                return;
            }
            int res = nativeClose();
            if (res != Tuner.RESULT_SUCCESS) {
                TunerUtils.throwExceptionForResult(res, "Failed to close filter.");
            } else {
                mIsClosed = true;
            }
        }
    }
}
