package cn.rongcloud.im.stetho;

import android.annotation.TargetApi;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.support.annotation.NonNull;

import com.facebook.stetho.common.Util;
import com.facebook.stetho.inspector.database.DatabaseConnectionProvider;
import com.facebook.stetho.inspector.database.DatabaseFilesProvider;
import com.facebook.stetho.inspector.database.DefaultDatabaseConnectionProvider;
import com.facebook.stetho.inspector.database.DefaultDatabaseFilesProvider;
import com.facebook.stetho.inspector.protocol.module.Database;
import com.facebook.stetho.inspector.protocol.module.DatabaseConstants;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.concurrent.ThreadSafe;

/**
 * Created by jiangecho on 2016/12/21.
 */

@ThreadSafe
public class RongDatabaseDriver extends Database.DatabaseDriver {
    private static final String[] UNINTERESTING_FILENAME_SUFFIXES = new String[]{
            "-journal",
            "-shm",
            "-uid",
            "-wal"
    };

    private final DatabaseFilesProvider mDatabaseFilesProvider;
    private final DatabaseConnectionProvider mDatabaseConnectionProvider;
    private List<String> mDatabases;

    /**
     * Constructs the object with a {@link DatabaseFilesProvider} that supplies the database files
     * from {@link Context#databaseList()}.
     *
     * @param context the context
     * @deprecated use {@link com.facebook.stetho.inspector.database.SqliteDatabaseDriver#SqliteDatabaseDriver(Context, DatabaseFilesProvider, DatabaseConnectionProvider)}
     */
    @Deprecated
    public RongDatabaseDriver(Context context) {
        this(
                context,
                new DefaultDatabaseFilesProvider(context),
                new DefaultDatabaseConnectionProvider());
    }

    /**
     * @param context                    the context
     * @param databaseFilesProvider      a database file name provider
     * @param databaseConnectionProvider a database connection provider
     */
    public RongDatabaseDriver(
            Context context,
            DatabaseFilesProvider databaseFilesProvider,
            DatabaseConnectionProvider databaseConnectionProvider) {
        super(context);
        mDatabaseFilesProvider = databaseFilesProvider;
        mDatabaseConnectionProvider = databaseConnectionProvider;
    }

    @Override
    public List<String> getDatabaseNames() {
        if (mDatabases == null) {
            mDatabases = new ArrayList<>();
            List<File> potentialDatabaseFiles = mDatabaseFilesProvider.getDatabaseFiles();
            Collections.sort(potentialDatabaseFiles);
            Iterable<File> tidiedList = tidyDatabaseList(potentialDatabaseFiles);
            for (File database : tidiedList) {
                mDatabases.add(buildDatabaseDisplayName(database));
            }
        }
        return mDatabases;
    }

    private String buildDatabaseDisplayName(@NonNull File databaseFile) {
        String fileName = databaseFile.getName();
        String path = databaseFile.getAbsolutePath();
        if (path.contains("databases")) { // db files in databases dir
            return fileName;
        } else {
            // rong database
            String parent = databaseFile.getParent();
            return parent.substring(parent.lastIndexOf('/') + 1) + " " + fileName;
        }
    }

    /**
     * Attempt to smartly eliminate uninteresting shadow databases such as -journal and -uid.  Note
     * that this only removes the database if it is true that it shadows another database lacking
     * the uninteresting suffix.
     *
     * @param databaseFiles Raw list of database files.
     * @return Tidied list with shadow databases removed.
     */
    // @VisibleForTesting
    static List<File> tidyDatabaseList(List<File> databaseFiles) {
        Set<File> originalAsSet = new HashSet<File>(databaseFiles);
        List<File> tidiedList = new ArrayList<File>();
        for (File databaseFile : databaseFiles) {
            String databaseFilename = databaseFile.getPath();
            String sansSuffix = removeSuffix(databaseFilename, UNINTERESTING_FILENAME_SUFFIXES);
            if (sansSuffix.equals(databaseFilename) || !originalAsSet.contains(new File(sansSuffix))) {
                tidiedList.add(databaseFile);
            }
        }
        return tidiedList;
    }

    private static String removeSuffix(String str, String[] suffixesToRemove) {
        for (String suffix : suffixesToRemove) {
            if (str.endsWith(suffix)) {
                return str.substring(0, str.length() - suffix.length());
            }
        }
        return str;
    }

    public List<String> getTableNames(String databaseName)
            throws SQLiteException {
        SQLiteDatabase database = openDatabase(databaseName);
        try {
            Cursor cursor = database.rawQuery("SELECT name FROM sqlite_master WHERE type IN (?, ?)",
                    new String[]{"table", "view"});
            try {
                List<String> tableNames = new ArrayList<String>();
                while (cursor.moveToNext()) {
                    tableNames.add(cursor.getString(0));
                }
                return tableNames;
            } finally {
                cursor.close();
            }
        } finally {
            database.close();
        }
    }

    public Database.ExecuteSQLResponse executeSQL(String databaseName, String query, ExecuteResultHandler<Database.ExecuteSQLResponse> handler)
            throws SQLiteException {
        Util.throwIfNull(query);
        Util.throwIfNull(handler);
        SQLiteDatabase database = openDatabase(databaseName);
        try {
            String firstWordUpperCase = getFirstWord(query).toUpperCase();
            switch (firstWordUpperCase) {
                case "UPDATE":
                case "DELETE":
                    return executeUpdateDelete(database, query, handler);
                case "INSERT":
                    return executeInsert(database, query, handler);
                case "SELECT":
                case "PRAGMA":
                case "EXPLAIN":
                    return executeSelect(database, query, handler);
                default:
                    return executeRawQuery(database, query, handler);
            }
        } finally {
            database.close();
        }
    }

    private static String getFirstWord(String s) {
        s = s.trim();
        int firstSpace = s.indexOf(' ');
        return firstSpace >= 0 ? s.substring(0, firstSpace) : s;
    }

    @TargetApi(DatabaseConstants.MIN_API_LEVEL)
    private <T> T executeUpdateDelete(
            SQLiteDatabase database,
            String query,
            ExecuteResultHandler<T> handler) {
        SQLiteStatement statement = database.compileStatement(query);
        int count = statement.executeUpdateDelete();
        return handler.handleUpdateDelete(count);
    }

    private <T> T executeInsert(
            SQLiteDatabase database,
            String query,
            ExecuteResultHandler<T> handler) {
        SQLiteStatement statement = database.compileStatement(query);
        long count = statement.executeInsert();
        return handler.handleInsert(count);
    }

    private <T> T executeSelect(
            SQLiteDatabase database,
            String query,
            ExecuteResultHandler<T> handler) {
        Cursor cursor = database.rawQuery(query, null);
        try {
            return handler.handleSelect(cursor);
        } finally {
            cursor.close();
        }
    }

    private <T> T executeRawQuery(
            SQLiteDatabase database,
            String query,
            ExecuteResultHandler<T> handler) {
        database.execSQL(query);
        return handler.handleRawQuery();
    }

    private SQLiteDatabase openDatabase(String databaseName) throws SQLiteException {
        Util.throwIfNull(databaseName);
        return mDatabaseConnectionProvider.openDatabase(findDatabaseFile(databaseName));
    }

    private File findDatabaseFile(String databaseName) {
        String[] tmp;
        for (File providedDatabaseFile : mDatabaseFilesProvider.getDatabaseFiles()) {
            if (providedDatabaseFile.getAbsolutePath().contains("databases")) {
                if (providedDatabaseFile.getName().equals(databaseName)) {
                    return providedDatabaseFile;
                }
            } else {
                tmp = databaseName.split(" ");
                if (tmp.length == 2) {
                    if (providedDatabaseFile.getAbsolutePath().contains(tmp[0]) && providedDatabaseFile.getName().equals(tmp[1])) {
                        return providedDatabaseFile;
                    }
                }
            }
            if (providedDatabaseFile.getName().equals(databaseName)) {
                return providedDatabaseFile;
            }
        }

        return mContext.getDatabasePath(databaseName);
    }
}