package com.liyu.sqlitetoexcel;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.text.TextUtils;

/**
 * SQLite to Excel
 * Created by liyu on 2015-9-8
 */
public class SQLiteToExcel {

    private static Handler handler = new Handler(Looper.getMainLooper());

    private String protectKey;
    private String encryptKey;
    private String fileName;
    private String filePath;
    private List<String> tables;
    private String sql;
    private String sheetName;

    private SQLiteDatabase database;
    private Workbook workbook;

    public static class Builder {
        private String dataBaseName;
        private String filePath;
        private String fileName;
        private String protectKey;
        private String encryptKey;
        private List<String> tables;
        private String sql;
        private String sheetName;

        public Builder(Context context) {
            this.filePath = context.getExternalFilesDir(null).getPath();
        }

        public SQLiteToExcel build() {
            if (TextUtils.isEmpty(dataBaseName)) {
                throw new IllegalArgumentException("Database name must not be null.");
            }
            if (TextUtils.isEmpty(fileName)) {
                throw new IllegalArgumentException("Output file name must not be null.");
            }
            return new SQLiteToExcel(tables, protectKey, encryptKey, fileName, dataBaseName, filePath, sql, sheetName);
        }

        public Builder setDataBase(String dataBaseName) {
            this.dataBaseName = dataBaseName;
            this.fileName = new File(dataBaseName).getName() + ".xls";
            return this;
        }

        /**
         * @deprecated Use {@link #setOutputFileName(String fileName)} instead.
         * @param fileName
         * @return Builder
         */
        @Deprecated
        public Builder setFileName(String fileName) {
            return setOutputFileName(fileName);
        }

        public Builder setOutputFileName(String fileName) {
            this.fileName = fileName;
            return this;
        }

        public Builder setProtectKey(String protectPassword) {
            this.protectKey = protectPassword;
            return this;
        }

        public Builder setEncryptKey(String encryptKey) {
            this.encryptKey = encryptKey;
            return this;
        }

        public Builder setTables(String... tables) {
            this.tables = Arrays.asList(tables);
            return this;
        }


        /**
         * @deprecated Use {@link #setOutputPath(String path)} instead.
         * @param path
         * @return Builder
         */
        @Deprecated
        public Builder setPath(String path) {
            return setOutputPath(path);
        }

        public Builder setOutputPath(String path) {
            this.filePath = path;
            return this;
        }

        public Builder setSQL(@NonNull String sheetName, @NonNull String sql) {
            this.sql = sql;
            this.sheetName = sheetName;
            return this;
        }

        public Builder setSQL(@NonNull String sql) {
            return setSQL("Sheet1", sql);
        }

        public String start() {
            final SQLiteToExcel sqliteToExcel = build();
            return sqliteToExcel.start();
        }

        public void start(ExportListener listener) {
            final SQLiteToExcel sqliteToExcel = build();
            sqliteToExcel.start(listener);
        }
    }

    /**
     * import Tables task
     *
     * @return output file path
     */
    public String start() {
        try {
            if (tables == null || tables.size() == 0) {
                tables = getTablesName(database);
            }
            return exportTables(getTablesName(database), fileName);
        } catch (Exception e) {
            if (database != null && database.isOpen()) {
                database.close();
            }
            return null;
        }
    }

    /**
     * importTables task with a listener
     *
     * @param listener callback
     */
    public void start(final ExportListener listener) {
        if (listener != null) {
            listener.onStart();
        }
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    if (tables == null || tables.size() == 0) {
                        tables = getTablesName(database);
                    }
                    final String filePath = exportTables(getTablesName(database), fileName);
                    if (listener != null) {
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                listener.onCompleted(filePath);
                            }
                        });
                    }
                } catch (final Exception e) {
                    if (database != null && database.isOpen()) {
                        database.close();
                    }
                    if (listener != null)
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                listener.onError(e);
                            }
                        });
                }
            }
        }).start();
    }

    private SQLiteToExcel(List<String> tables, String protectKey, String encryptKey, String fileName,
                          String dataBaseName, String filePath, String sql, String sheetName) {
        this.protectKey = protectKey;
        this.encryptKey = encryptKey;
        this.fileName = fileName;
        this.filePath = filePath;
        this.sql = sql;
        this.sheetName = sheetName;

        try {
            database = SQLiteDatabase.openOrCreateDatabase(dataBaseName, null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * core code, export tables to a excel file
     *
     * @param tables   database tables
     * @param fileName target file name
     * @return target file path
     * @throws Exception
     */
    private String exportTables(List<String> tables, final String fileName) throws Exception {
        if (fileName.toLowerCase().endsWith(".xls")) {
            workbook = new HSSFWorkbook();
        } else {
            throw new IllegalArgumentException("File name is null or unsupported file format!");
        }
        if (TextUtils.isEmpty(sql)) {
            for (int i = 0; i < tables.size(); i++) {
                Sheet sheet = workbook.createSheet(tables.get(i));
                String sqlAll = "select * from " + tables.get(i);
                fillSheet(sqlAll, sheet);
                if (!TextUtils.isEmpty(protectKey)) {
                    sheet.protectSheet(protectKey);
                }
            }
        } else {
            Sheet sheet = workbook.createSheet(sheetName);
            fillSheet(sql, sheet);
            if (!TextUtils.isEmpty(protectKey)) {
                sheet.protectSheet(protectKey);
            }
        }
        File file = new File(filePath, fileName);
        FileOutputStream fos1 = new FileOutputStream(file);
        workbook.write(fos1);
        if (fos1 != null) {
            fos1.flush();
            fos1.close();
        }
        workbook.close();
        database.close();
        if (!TextUtils.isEmpty(encryptKey)) {
            SecurityUtil.EncryptFile(file, encryptKey);
        }

        return file.getPath();
    }

    /**
     * Query the database ,then fill in to the sheet
     *
     * @param sql   query sql
     * @param sheet target sheet
     */
    private void fillSheet(String sql, Sheet sheet) {
        Drawing patriarch = sheet.createDrawingPatriarch();
        Cursor cursor = database.rawQuery(sql, null);
        cursor.moveToFirst();
        final int columnsCount = cursor.getColumnCount();
        Row headerRow = sheet.createRow(0);
        for (int i = 0; i < columnsCount; i++) {
            Cell cellA = headerRow.createCell(i);
            cellA.setCellValue(new HSSFRichTextString("" + cursor.getColumnNames()[i]));
        }
        int n = 1;
        while (!cursor.isAfterLast()) {
            Row rowA = sheet.createRow(n);
            for (int j = 0; j < columnsCount; j++) {
                Cell cellA = rowA.createCell(j);
                if (cursor.getType(j) == Cursor.FIELD_TYPE_BLOB) {
                    ClientAnchor anchor = new HSSFClientAnchor(0, 0, 0, 0, (short) j, n, (short) (j + 1), n + 1);
                    anchor.setAnchorType(3);
                    patriarch.createPicture(anchor, workbook.addPicture(cursor.getBlob(j), HSSFWorkbook.PICTURE_TYPE_JPEG));
                } else {
                    String value = cursor.getString(j);
                    if (!TextUtils.isEmpty(value) && value.length() >= 32767) {
                        value = value.substring(0, 32766);
                    }
                    cellA.setCellValue(new HSSFRichTextString(value));
                }
            }
            n++;
            cursor.moveToNext();
        }
        cursor.close();
    }

    /**
     * get database all tables
     *
     * @return tables
     */
    private List<String> getTablesName(SQLiteDatabase database) {
        List<String> tables = new ArrayList<>();
        Cursor cursor = database.rawQuery("select name from sqlite_master where type='table' order by name", null);
        while (cursor.moveToNext()) {
            tables.add(cursor.getString(0));
        }
        cursor.close();
        return tables;
    }

    /**
     * Callbacks for export events.
     */
    public interface ExportListener {
        void onStart();

        void onCompleted(String filePath);

        void onError(Exception e);
    }

}