/*
 * Copyright 2021 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.interactive;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Xml;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

/**
 * This class is used to specify meta information of a TV interactive app.
 */
public final class TvInteractiveAppServiceInfo implements Parcelable {
    private static final boolean DEBUG = false;
    private static final String TAG = "TvInteractiveAppServiceInfo";

    private static final String XML_START_TAG_NAME = "tv-interactive-app";

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "INTERACTIVE_APP_TYPE_" }, value = {
            INTERACTIVE_APP_TYPE_HBBTV,
            INTERACTIVE_APP_TYPE_ATSC,
            INTERACTIVE_APP_TYPE_GINGA,
            INTERACTIVE_APP_TYPE_TARGETED_AD,
            INTERACTIVE_APP_TYPE_OTHER
    })
    public @interface InteractiveAppType {}

    /** HbbTV interactive app type */
    public static final int INTERACTIVE_APP_TYPE_HBBTV = 0x1;
    /** ATSC interactive app type */
    public static final int INTERACTIVE_APP_TYPE_ATSC = 0x2;
    /** Ginga interactive app type */
    public static final int INTERACTIVE_APP_TYPE_GINGA = 0x4;
    /** Targeted Advertisement interactive app type */
    public static final int INTERACTIVE_APP_TYPE_TARGETED_AD = 0x8;
    /** Other interactive app type */
    public static final int INTERACTIVE_APP_TYPE_OTHER = 0x80000000;

    private final ResolveInfo mService;
    private final String mId;
    private int mTypes;
    private final List<String> mExtraTypes = new ArrayList<>();

    /**
     * Constructs a TvInteractiveAppServiceInfo object.
     *
     * @param context the application context
     * @param component the component name of the TvInteractiveAppService
     */
    public TvInteractiveAppServiceInfo(@NonNull Context context, @NonNull ComponentName component) {
        if (context == null) {
            throw new IllegalArgumentException("context cannot be null.");
        }
        Intent intent =
                new Intent(TvInteractiveAppService.SERVICE_INTERFACE).setComponent(component);
        ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent,
                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
        if (resolveInfo == null) {
            throw new IllegalArgumentException("Invalid component. Can't find the service.");
        }

        ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName,
                resolveInfo.serviceInfo.name);
        String id;
        id = generateInteractiveAppServiceId(componentName);
        List<String> types = new ArrayList<>();
        parseServiceMetadata(resolveInfo, context, types);

        mService = resolveInfo;
        mId = id;
        toTypesFlag(types);
    }
    private TvInteractiveAppServiceInfo(
            ResolveInfo service, String id, int types, List<String> extraTypes) {
        mService = service;
        mId = id;
        mTypes = types;
        mExtraTypes.addAll(extraTypes);
    }

    private TvInteractiveAppServiceInfo(@NonNull Parcel in) {
        mService = ResolveInfo.CREATOR.createFromParcel(in);
        mId = in.readString();
        mTypes = in.readInt();
        in.readStringList(mExtraTypes);
    }

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

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

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        mService.writeToParcel(dest, flags);
        dest.writeString(mId);
        dest.writeInt(mTypes);
        dest.writeStringList(mExtraTypes);
    }

    /**
     * Returns a unique ID for this TV interactive app service. The ID is generated from the package
     * and class name implementing the TV interactive app service.
     */
    @NonNull
    public String getId() {
        return mId;
    }

    /**
     * Returns the component of the TV Interactive App service.
     * @hide
     */
    public ComponentName getComponent() {
        return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
    }

    /**
     * Returns the information of the service that implements this TV Interactive App service.
     */
    @Nullable
    public ServiceInfo getServiceInfo() {
        return mService.serviceInfo;
    }

    /**
     * Gets supported interactive app types.
     *
     * <p>The supported interactive app types is in a bit map format. For example:
     * <pre><code>
     *   int types = tvInteractiveAppInfo.getSupportedTypes();
     *   if (types & TvInteractiveAppInfo.INTERACTIVE_APP_TYPE_HBBTV != 0) {
     *     // HbbTV type is supported. Do something...
     *   }
     *   if (types & TvInteractiveAppInfo.INTERACTIVE_APP_TYPE_ATSC == 0) {
     *     // ATSC type is not supported. Do something...
     *   }
     * </code></pre>
     *
     * @return An int bit map representing supported types.
     */
    @InteractiveAppType
    @NonNull
    public int getSupportedTypes() {
        return mTypes;
    }

    /**
     * Gets custom supported interactive app types which are not listed.
     *
     * @see #getSupportedTypes()
     */
    @NonNull
    public List<String> getCustomSupportedTypes() {
        return mExtraTypes;
    }

    private static String generateInteractiveAppServiceId(ComponentName name) {
        return name.flattenToShortString();
    }

    private static void parseServiceMetadata(
            ResolveInfo resolveInfo, Context context, List<String> types) {
        ServiceInfo si = resolveInfo.serviceInfo;
        PackageManager pm = context.getPackageManager();
        try (XmlResourceParser parser =
                     si.loadXmlMetaData(pm, TvInteractiveAppService.SERVICE_META_DATA)) {
            if (parser == null) {
                throw new IllegalStateException(
                        "No " + TvInteractiveAppService.SERVICE_META_DATA
                        + " meta-data found for " + si.name);
            }

            Resources res = pm.getResourcesForApplication(si.applicationInfo);
            AttributeSet attrs = Xml.asAttributeSet(parser);

            int type;
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && type != XmlPullParser.START_TAG) {
                // move to the START_TAG
            }

            String nodeName = parser.getName();
            if (!XML_START_TAG_NAME.equals(nodeName)) {
                throw new IllegalStateException("Meta-data does not start with "
                        + XML_START_TAG_NAME + " tag for " + si.name);
            }

            TypedArray sa = res.obtainAttributes(attrs,
                    com.android.internal.R.styleable.TvInteractiveAppService);
            CharSequence[] textArr = sa.getTextArray(
                    com.android.internal.R.styleable.TvInteractiveAppService_supportedTypes);
            for (CharSequence cs : textArr) {
                types.add(cs.toString().toLowerCase());
            }

            sa.recycle();
        } catch (IOException | XmlPullParserException e) {
            throw new IllegalStateException(
                    "Failed reading meta-data for " + si.packageName, e);
        } catch (PackageManager.NameNotFoundException e) {
            throw new IllegalStateException("No resources found for " + si.packageName, e);
        }
    }

    private void toTypesFlag(List<String> types) {
        mTypes = 0;
        mExtraTypes.clear();
        for (String type : types) {
            switch (type) {
                case "hbbtv":
                    mTypes |= INTERACTIVE_APP_TYPE_HBBTV;
                    break;
                case "atsc":
                    mTypes |= INTERACTIVE_APP_TYPE_ATSC;
                    break;
                case "ginga":
                    mTypes |= INTERACTIVE_APP_TYPE_GINGA;
                    break;
                case "targeted_ad":
                    mTypes |= INTERACTIVE_APP_TYPE_TARGETED_AD;
                default:
                    mTypes |= INTERACTIVE_APP_TYPE_OTHER;
                    mExtraTypes.add(type);
                    break;
            }
        }
    }
}
