/*
 * Copyright (C) 2010 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.mtp;

import android.content.ContentProviderClient;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.Images;
import android.util.Log;

import java.util.ArrayList;

/**
 * MtpPropertyGroup represents a list of MTP properties.
 * {@hide}
 */
class MtpPropertyGroup {
    private static final String TAG = MtpPropertyGroup.class.getSimpleName();

    private class Property {
        int code;
        int type;
        int column;

        Property(int code, int type, int column) {
            this.code = code;
            this.type = type;
            this.column = column;
        }
    }

    private final ContentProviderClient mProvider;
    private final String mVolumeName;

    // list of all properties in this group
    private final Property[] mProperties;

    // list of columns for database query
    private String[] mColumns;

    private static final String PATH_WHERE = Files.FileColumns.DATA + "=?";

    // constructs a property group for a list of properties
    public MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties) {
        mProvider = provider;
        mVolumeName = volumeName;

        int count = properties.length;
        ArrayList<String> columns = new ArrayList<>(count);
        columns.add(Files.FileColumns._ID);

        mProperties = new Property[count];
        for (int i = 0; i < count; i++) {
            mProperties[i] = createProperty(properties[i], columns);
        }
        count = columns.size();
        mColumns = new String[count];
        for (int i = 0; i < count; i++) {
            mColumns[i] = columns.get(i);
        }
    }

    private Property createProperty(int code, ArrayList<String> columns) {
        String column = null;
        int type;

        switch (code) {
            case MtpConstants.PROPERTY_STORAGE_ID:
                type = MtpConstants.TYPE_UINT32;
                break;
            case MtpConstants.PROPERTY_OBJECT_FORMAT:
                type = MtpConstants.TYPE_UINT16;
                break;
            case MtpConstants.PROPERTY_PROTECTION_STATUS:
                type = MtpConstants.TYPE_UINT16;
                break;
            case MtpConstants.PROPERTY_OBJECT_SIZE:
                type = MtpConstants.TYPE_UINT64;
                break;
            case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_NAME:
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_DATE_MODIFIED:
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_DATE_ADDED:
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
                column = Audio.AudioColumns.YEAR;
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_PARENT_OBJECT:
                type = MtpConstants.TYPE_UINT32;
                break;
            case MtpConstants.PROPERTY_PERSISTENT_UID:
                type = MtpConstants.TYPE_UINT128;
                break;
            case MtpConstants.PROPERTY_DURATION:
                column = Audio.AudioColumns.DURATION;
                type = MtpConstants.TYPE_UINT32;
                break;
            case MtpConstants.PROPERTY_TRACK:
                column = Audio.AudioColumns.TRACK;
                type = MtpConstants.TYPE_UINT16;
                break;
            case MtpConstants.PROPERTY_DISPLAY_NAME:
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_ARTIST:
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_ALBUM_NAME:
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_ALBUM_ARTIST:
                column = Audio.AudioColumns.ALBUM_ARTIST;
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_COMPOSER:
                column = Audio.AudioColumns.COMPOSER;
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_DESCRIPTION:
                column = Images.ImageColumns.DESCRIPTION;
                type = MtpConstants.TYPE_STR;
                break;
            case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
            case MtpConstants.PROPERTY_AUDIO_BITRATE:
            case MtpConstants.PROPERTY_SAMPLE_RATE:
                // these are special cased
                type = MtpConstants.TYPE_UINT32;
                break;
            case MtpConstants.PROPERTY_BITRATE_TYPE:
            case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
                // these are special cased
                type = MtpConstants.TYPE_UINT16;
                break;
            default:
                type = MtpConstants.TYPE_UNDEFINED;
                Log.e(TAG, "unsupported property " + code);
                break;
        }

        if (column != null) {
            columns.add(column);
            return new Property(code, type, columns.size() - 1);
        } else {
            return new Property(code, type, -1);
        }
    }

    /**
     * Gets the values of the properties represented by this property group for the given
     * object and adds them to the given property list.
     * @return Response_OK if the operation succeeded.
     */
    public int getPropertyList(MtpStorageManager.MtpObject object, MtpPropertyList list) {
        Cursor c = null;
        int id = object.getId();
        String path = object.getPath().toString();
        for (Property property : mProperties) {
            if (property.column != -1 && c == null) {
                try {
                    // Look up the entry in MediaProvider only if one of those properties is needed.
                    final Uri uri = MtpDatabase.getObjectPropertiesUri(object.getFormat(),
                            mVolumeName);
                    c = mProvider.query(uri, mColumns,
                            PATH_WHERE, new String[] {path}, null, null);
                    if (c != null && !c.moveToNext()) {
                        c.close();
                        c = null;
                    }
                } catch (IllegalArgumentException e) {
                    return MtpConstants.RESPONSE_INVALID_OBJECT_PROP_CODE;
                } catch (RemoteException e) {
                    Log.e(TAG, "Mediaprovider lookup failed");
                }
            }
            switch (property.code) {
                case MtpConstants.PROPERTY_PROTECTION_STATUS:
                    // protection status is always 0
                    list.append(id, property.code, property.type, 0);
                    break;
                case MtpConstants.PROPERTY_NAME:
                case MtpConstants.PROPERTY_OBJECT_FILE_NAME:
                case MtpConstants.PROPERTY_DISPLAY_NAME:
                    list.append(id, property.code, object.getName());
                    break;
                case MtpConstants.PROPERTY_DATE_MODIFIED:
                case MtpConstants.PROPERTY_DATE_ADDED:
                    // convert from seconds to DateTime
                    list.append(id, property.code,
                            format_date_time(object.getModifiedTime()));
                    break;
                case MtpConstants.PROPERTY_STORAGE_ID:
                    list.append(id, property.code, property.type, object.getStorageId());
                    break;
                case MtpConstants.PROPERTY_OBJECT_FORMAT:
                    list.append(id, property.code, property.type, object.getFormat());
                    break;
                case MtpConstants.PROPERTY_OBJECT_SIZE:
                    list.append(id, property.code, property.type, object.getSize());
                    break;
                case MtpConstants.PROPERTY_PARENT_OBJECT:
                    list.append(id, property.code, property.type,
                            object.getParent().isRoot() ? 0 : object.getParent().getId());
                    break;
                case MtpConstants.PROPERTY_PERSISTENT_UID:
                    // The persistent uid must be unique and never reused among all objects,
                    // and remain the same between sessions.
                    long puid = (object.getPath().toString().hashCode() << 32)
                            + object.getModifiedTime();
                    list.append(id, property.code, property.type, puid);
                    break;
                case MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE:
                    // release date is stored internally as just the year
                    int year = 0;
                    if (c != null)
                        year = c.getInt(property.column);
                    String dateTime = Integer.toString(year) + "0101T000000";
                    list.append(id, property.code, dateTime);
                    break;
                case MtpConstants.PROPERTY_TRACK:
                    int track = 0;
                    if (c != null)
                        track = c.getInt(property.column);
                    list.append(id, property.code, MtpConstants.TYPE_UINT16,
                            track % 1000);
                    break;
                case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC:
                case MtpConstants.PROPERTY_AUDIO_BITRATE:
                case MtpConstants.PROPERTY_SAMPLE_RATE:
                    // we don't have these in our database, so return 0
                    list.append(id, property.code, MtpConstants.TYPE_UINT32, 0);
                    break;
                case MtpConstants.PROPERTY_BITRATE_TYPE:
                case MtpConstants.PROPERTY_NUMBER_OF_CHANNELS:
                    // we don't have these in our database, so return 0
                    list.append(id, property.code, MtpConstants.TYPE_UINT16, 0);
                    break;
                default:
                    switch(property.type) {
                        case MtpConstants.TYPE_UNDEFINED:
                            list.append(id, property.code, property.type, 0);
                            break;
                        case MtpConstants.TYPE_STR:
                            String value = "";
                            if (c != null)
                                value = c.getString(property.column);
                            list.append(id, property.code, value);
                            break;
                        default:
                            long longValue = 0L;
                            if (c != null)
                                longValue = c.getLong(property.column);
                            list.append(id, property.code, property.type, longValue);
                    }
            }
        }
        if (c != null)
            c.close();
        return MtpConstants.RESPONSE_OK;
    }

    private native String format_date_time(long seconds);
}
