package com.android.server.backup;

import static android.os.ParcelFileDescriptor.MODE_CREATE;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;

import android.app.IBackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.FullBackup;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;

import com.android.server.backup.restore.PerformAdbRestoreTask;

import libcore.io.IoUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Used by BackupManagerService to perform adb restore for key-value packages. At the moment this
 * class resembles what is done in the standard key-value code paths in BackupManagerService, and
 * should be unified later.
 *
 * TODO: We should create unified backup/restore engines that can be used for both transport and
 * adb backup/restore, and for fullbackup and key-value backup.
 */
public class KeyValueAdbRestoreEngine implements Runnable {
    private static final String TAG = "KeyValueAdbRestoreEngine";
    private static final boolean DEBUG = false;

    private final BackupManagerServiceInterface mBackupManagerService;
    private final File mDataDir;

    FileMetadata mInfo;
    PerformAdbRestoreTask mRestoreTask;
    ParcelFileDescriptor mInFD;
    IBackupAgent mAgent;
    int mToken;

    public KeyValueAdbRestoreEngine(BackupManagerServiceInterface backupManagerService,
            File dataDir, FileMetadata info, ParcelFileDescriptor inFD, IBackupAgent agent,
            int token) {
        mBackupManagerService = backupManagerService;
        mDataDir = dataDir;
        mInfo = info;
        mInFD = inFD;
        mAgent = agent;
        mToken = token;
    }

    @Override
    public void run() {
        try {
            File restoreData = prepareRestoreData(mInfo, mInFD);

            // TODO: version ?
            invokeAgentForAdbRestore(mAgent, mInfo, restoreData, 0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private File prepareRestoreData(FileMetadata info, ParcelFileDescriptor inFD) throws IOException {
        String pkg = info.packageName;
        File restoreDataName = new File(mDataDir, pkg + ".restore");
        File sortedDataName = new File(mDataDir, pkg + ".sorted");

        FullBackup.restoreFile(inFD, info.size, info.type, info.mode, info.mtime, restoreDataName);

        // Sort the keys, as the BackupAgent expect them to come in lexicographical order
        sortKeyValueData(restoreDataName, sortedDataName);
        return sortedDataName;
    }

    private void invokeAgentForAdbRestore(IBackupAgent agent, FileMetadata info, File restoreData,
            int versionCode) throws IOException {
        String pkg = info.packageName;
        File newStateName = new File(mDataDir, pkg + ".new");
        try {
            ParcelFileDescriptor backupData =
                    ParcelFileDescriptor.open(restoreData, MODE_READ_ONLY);
            ParcelFileDescriptor newState = ParcelFileDescriptor.open(newStateName,
                    MODE_READ_WRITE | MODE_CREATE | MODE_TRUNCATE);

            if (DEBUG) {
                Slog.i(TAG, "Starting restore of package " + pkg + " for version code "
                        + versionCode);
            }
            agent.doRestore(backupData, versionCode, newState, mToken,
                    mBackupManagerService.getBackupManagerBinder());
        } catch (IOException e) {
            Slog.e(TAG, "Exception opening file. " + e);
        } catch (RemoteException e) {
            Slog.e(TAG, "Exception calling doRestore on agent: " + e);
        }
    }

    private void sortKeyValueData (File restoreData, File sortedData) throws IOException {
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        try {
            inputStream = new FileInputStream(restoreData);
            outputStream = new FileOutputStream(sortedData);
            BackupDataInput reader = new BackupDataInput(inputStream.getFD());
            BackupDataOutput writer = new BackupDataOutput(outputStream.getFD());
            copyKeysInLexicalOrder(reader, writer);
        } finally {
            if (inputStream != null) {
                IoUtils.closeQuietly(inputStream);
            }
            if (outputStream != null) {
                IoUtils.closeQuietly(outputStream);
            }
        }
    }

    private void copyKeysInLexicalOrder(BackupDataInput in, BackupDataOutput out)
            throws IOException {
        Map<String, byte[]> data = new HashMap<>();
        while (in.readNextHeader()) {
            String key = in.getKey();
            int size = in.getDataSize();
            if (size < 0) {
                in.skipEntityData();
                continue;
            }
            byte[] value = new byte[size];
            in.readEntityData(value, 0, size);
            data.put(key, value);
        }
        List<String> keys = new ArrayList<>(data.keySet());
        Collections.sort(keys);
        for (String key : keys) {
            byte[] value = data.get(key);
            out.writeEntityHeader(key, value.length);
            out.writeEntityData(value, value.length);
        }
    }
}
