/*
 * Copyright (C) 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 com.android.keyguard.clock;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;

import java.io.FileNotFoundException;
import java.util.List;
import java.util.function.Supplier;

/**
 * Exposes custom clock face options and provides realistic preview images.
 *
 * APIs:
 *
 *   /list_options: List the available clock faces, which has the following columns
 *     name: name of the clock face
 *     title: title of the clock face
 *     id: value used to set the clock face
 *     thumbnail: uri of the thumbnail image, should be /thumbnail/{name}
 *     preview: uri of the preview image, should be /preview/{name}
 *
 *   /thumbnail/{id}: Opens a file stream for the thumbnail image for clock face {id}.
 *
 *   /preview/{id}: Opens a file stream for the preview image for clock face {id}.
 */
public final class ClockOptionsProvider extends ContentProvider {

    private static final String TAG = "ClockOptionsProvider";
    private static final String KEY_LIST_OPTIONS = "/list_options";
    private static final String KEY_PREVIEW = "preview";
    private static final String KEY_THUMBNAIL = "thumbnail";
    private static final String COLUMN_NAME = "name";
    private static final String COLUMN_TITLE = "title";
    private static final String COLUMN_ID = "id";
    private static final String COLUMN_THUMBNAIL = "thumbnail";
    private static final String COLUMN_PREVIEW = "preview";
    private static final String MIME_TYPE_PNG = "image/png";
    private static final String CONTENT_SCHEME = "content";
    private static final String AUTHORITY = "com.android.keyguard.clock";

    private final Supplier<List<ClockInfo>> mClocksSupplier;

    public ClockOptionsProvider() {
        this(() -> Dependency.get(ClockManager.class).getClockInfos());
    }

    @VisibleForTesting
    ClockOptionsProvider(Supplier<List<ClockInfo>> clocksSupplier) {
        mClocksSupplier = clocksSupplier;
    }

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public String getType(Uri uri) {
        List<String> segments = uri.getPathSegments();
        if (segments.size() > 0 && (KEY_PREVIEW.equals(segments.get(0))
                || KEY_THUMBNAIL.equals(segments.get(0)))) {
            return MIME_TYPE_PNG;
        }
        return "vnd.android.cursor.dir/clock_faces";
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        if (!KEY_LIST_OPTIONS.equals(uri.getPath())) {
            return null;
        }
        MatrixCursor cursor = new MatrixCursor(new String[] {
                COLUMN_NAME, COLUMN_TITLE, COLUMN_ID, COLUMN_THUMBNAIL, COLUMN_PREVIEW});
        List<ClockInfo> clocks = mClocksSupplier.get();
        for (int i = 0; i < clocks.size(); i++) {
            ClockInfo clock = clocks.get(i);
            cursor.newRow()
                    .add(COLUMN_NAME, clock.getName())
                    .add(COLUMN_TITLE, clock.getTitle())
                    .add(COLUMN_ID, clock.getId())
                    .add(COLUMN_THUMBNAIL, createThumbnailUri(clock))
                    .add(COLUMN_PREVIEW, createPreviewUri(clock));
        }
        return cursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues initialValues) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        List<String> segments = uri.getPathSegments();
        if (segments.size() != 2 || !(KEY_PREVIEW.equals(segments.get(0))
                || KEY_THUMBNAIL.equals(segments.get(0)))) {
            throw new FileNotFoundException("Invalid preview url");
        }
        String id = segments.get(1);
        if (TextUtils.isEmpty(id)) {
            throw new FileNotFoundException("Invalid preview url, missing id");
        }
        ClockInfo clock = null;
        List<ClockInfo> clocks = mClocksSupplier.get();
        for (int i = 0; i < clocks.size(); i++) {
            if (id.equals(clocks.get(i).getId())) {
                clock = clocks.get(i);
                break;
            }
        }
        if (clock == null) {
            throw new FileNotFoundException("Invalid preview url, id not found");
        }
        return openPipeHelper(uri, MIME_TYPE_PNG, null, KEY_PREVIEW.equals(segments.get(0))
                ? clock.getPreview() : clock.getThumbnail(), new MyWriter());
    }

    private Uri createThumbnailUri(ClockInfo clock) {
        return new Uri.Builder()
                .scheme(CONTENT_SCHEME)
                .authority(AUTHORITY)
                .appendPath(KEY_THUMBNAIL)
                .appendPath(clock.getId())
                .build();
    }

    private Uri createPreviewUri(ClockInfo clock) {
        return new Uri.Builder()
                .scheme(CONTENT_SCHEME)
                .authority(AUTHORITY)
                .appendPath(KEY_PREVIEW)
                .appendPath(clock.getId())
                .build();
    }

    private static class MyWriter implements ContentProvider.PipeDataWriter<Bitmap> {
        @Override
        public void writeDataToPipe(ParcelFileDescriptor output, Uri uri, String mimeType,
                Bundle opts, Bitmap bitmap) {
            try (AutoCloseOutputStream os = new AutoCloseOutputStream(output)) {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
            } catch (Exception e) {
                Log.w(TAG, "fail to write to pipe", e);
            }
        }
    }
}
