/*  Copyright (C) 2016-2021 Carsten Pfeiffer, Daniele Gobbetti, Taavi Eomäe

    This file is part of Gadgetbridge.

    Gadgetbridge is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Gadgetbridge is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.util;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class UriHelper {
    @NonNull
    private final Uri uri;
    @NonNull
    private final Context context;
    private String fileName;
    private long fileSize;
    @Nullable
    private File file;

    private static final Logger LOG = LoggerFactory.getLogger(UriHelper.class);

    private UriHelper(@NonNull Uri uri, @NonNull Context context) {
        this.uri = uri;
        this.context = context;
    }

    /**
     * Returns the uri as passed to #get(Uri, Context)
     */
    @NonNull
    public Uri getUri() {
        return uri;
    }

    /**
     * Returns the context as passed to #get(Uri, Context)
     */
    @NonNull
    public Context getContext() {
        return context;
    }

    /**
     * Returns an immutable helper to access the given Uri. In case the uri cannot be read/resolved
     * an IOException is thrown.
     * @param uri the uri to access
     * @param context the context for accessing uris
     * @throws IOException
     */
    @NonNull
    public static UriHelper get(@NonNull Uri uri, @NonNull Context context) throws FileNotFoundException, IOException {
        UriHelper helper = new UriHelper(uri, context);
        helper.resolveMetadata();
        return helper;
    }

    /**
     * Opens a stream to read the contents of the uri.
     * Note: the caller has to close the stream after usage.
     * Every invocation of this method will open a new stream.
     * FIXME: make sure that every caller actually closes the returned stream!
     * @throws FileNotFoundException
     */
    @NonNull
    public InputStream openInputStream() throws FileNotFoundException {
        ContentResolver cr = context.getContentResolver();
        InputStream inputStream = cr.openInputStream(uri);
        if (inputStream != null) {
            return new BufferedInputStream(inputStream);
        }
        throw new FileNotFoundException("Unable to open inputstream for " + uri);
    }

    /**
     * Returns the content length (file size) in bytes
     */
    public long getFileSize() {
        return fileSize;
    }

    /**
     * Returns the name of the file referenced by the Uri. Does not include the path.
     */
    @NonNull
    public String getFileName() {
        return fileName;
    }

    /**
     * Returns the file behind the uri, or null in case it is not a file:/ Uri.
     * @return the file or null
     */
    @Nullable
    public File getFile() {
        return file;
    }

    private void resolveMetadata() throws IOException {
        if (uri == null) {
            throw new IOException("URI was null, can't query metadata");
        }

        String uriScheme = uri.getScheme();

        if (uriScheme == null) {
            throw new IOException("URI scheme was null, can't query metadata");
        }

        switch (uriScheme) {
            case ContentResolver.SCHEME_CONTENT:
                Cursor cursor;
                try {
                    ContentResolver resolver = context.getContentResolver();
                    cursor = resolver.query(
                            uri,
                            new String[]{
                                    MediaStore.MediaColumns.DISPLAY_NAME,
                                    MediaStore.MediaColumns.SIZE
                            }, null, null, null);
                } catch (IllegalStateException e) {
                    LOG.error(e.toString());
                    throw new IOException("IllegalStateException when trying to query metadata for: " + uri);
                }

                if (cursor == null) {
                    throw new IOException("Unable to query metadata for: " + uri);
                }

                try {
                    if (cursor.moveToFirst()) {
                        int name_index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
                        if (name_index == -1) {
                            throw new IOException("Unable to retrieve name for: " + uri);
                        }
                        int size_index = cursor.getColumnIndex(MediaStore.MediaColumns.SIZE);
                        if (size_index == -1) {
                            throw new IOException("Unable to retrieve size for: " + uri);
                        }
                        try {
                            fileName = cursor.getString(name_index);
                            if (fileName == null) {
                                throw new IOException("Unable to retrieve name for: " + uri);
                            }
                            fileSize = cursor.getLong(size_index);
                            if (fileSize < 0) {
                                throw new IOException("Unable to retrieve size for: " + uri);
                            }
                        } catch (Exception ex) {
                            throw new IOException("Unable to retrieve metadata for: " + uri + ": " + ex.getMessage());
                        }
                    }
                } finally {
                    cursor.close();
                }
                break;
            case ContentResolver.SCHEME_FILE:
                file = new File(uri.getPath());
                if (!file.exists()) {
                    throw new FileNotFoundException("Does not exist: " + file);
                }
                fileName = file.getName();
                fileSize = file.length();
                break;
            case ContentResolver.SCHEME_ANDROID_RESOURCE:
                // we could actually read it, but I don't see how we can determine the file size
                throw new IOException("Unsupported scheme for uri: " + uri);
            default:
                throw new IOException("Unsupported scheme for uri: " + uri);
        }
    }
}
