package com.datarecovery.master.utils;

import static android.content.Context.POWER_SERVICE;

import android.content.Context;
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;

import com.datarecovery.master.BR;
import com.datarecovery.master.utils.filedetect.FileScanHelper;
import com.datarecovery.master.utils.xfile.XFile;
import com.datarecovery.master.utils.xfile.XFileSearch;

import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;

import atmob.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import atmob.reactivex.rxjava3.core.BackpressureStrategy;
import atmob.reactivex.rxjava3.core.Flowable;
import atmob.reactivex.rxjava3.core.FlowableOnSubscribe;

public class FilesSearch {

    public static Flowable<List<DocumentFile>> search(Context context, int... acceptCategory) {
        if (acceptCategory == null || acceptCategory.length == 0) {
            return Flowable.empty();
        }
        HashSet<Integer> categories = new HashSet<>();
        for (int category : acceptCategory) {
            categories.add(category);
        }
        return Flowable.create((FlowableOnSubscribe<XFile>) emitter -> {
                    try {
                        CancellationSignal cancellationSignal = XFileSearch.searchPreExternalStorageAsync(context, FileScanHelper.getPreScanFileRelatedDirectory(),
                                new XFileSearch.FileFilter() {

                                    @Override
                                    public boolean acceptFile(XFile file) {
                                        return isAcceptFile(file);
                                    }

                                    @Override
                                    public boolean acceptDirectory(XFile file) {
                                        return false;
                                    }

                                    @Override
                                    public boolean filterDirectory(XFile file) {
                                        try {
                                            Log.d("lzplzp", "filterDirectory: " + file.getPath());
                                        } catch (Exception e) {
                                            throw new RuntimeException(e);
                                        }
                                        return false;
                                    }
                                }, new XFileSearch.FileSearchCallback() {
                                    @Override
                                    public void onStart() {

                                    }

                                    @Override
                                    public void onEachFile(XFile file) {
                                        emitter.onNext(file);
                                    }

                                    @Override
                                    public void onFinish() {
                                        emitter.onComplete();
                                    }
                                });
                        emitter.setCancellable(cancellationSignal::cancel);
                    } catch (Exception e) {
                        emitter.onError(e);
                    }
                }, BackpressureStrategy.BUFFER)
                .filter(xFile -> xFile.getTag() != null)
                .filter(xFile -> {
                    Integer tag = (Integer) xFile.getTag();
                    return categories.contains(tag);
                })
                .map(xFile -> new DocumentFile(xFile, (Integer) xFile.getTag()))
                .buffer(200, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(subscription -> {
                    subscription.request(Long.MAX_VALUE);

                    acquireWakeLock(context);
                })
                .doOnCancel(() -> releaseWakeLock(context))
                .doOnTerminate(() -> releaseWakeLock(context));
    }

    private static void releaseWakeLock(Context context) {
        PowerManager powerManager = (PowerManager) context.getSystemService(POWER_SERVICE);
        PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                "DocumentSearch::search");
        if (wakeLock.isHeld()) {
            wakeLock.release();
        }
    }

    private static void acquireWakeLock(Context context) {
        PowerManager powerManager = (PowerManager) context.getSystemService(POWER_SERVICE);
        PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                "DocumentSearch::search");
        wakeLock.acquire(10 * 60 * 1000L /*10 minutes*/);
    }

    private static boolean isAcceptFile(XFile file) {
        if (file == null) {
            return false;
        }
        try {
            if (!file.isDirectory() && file.length() == 0) {
                return false;
            }
            String name = file.getName();
            if (TextUtils.isEmpty(name)) {
                return false;
            }
            if (isWordFile(name)) {
                file.setTag(DocumentFile.WORD);
                return true;
            }
            if (isExcelFile(name)) {
                file.setTag(DocumentFile.EXCEL);
                return true;
            }
            if (isPPTFile(name)) {
                file.setTag(DocumentFile.PPT);
                return true;
            }
            if (isPDFFile(name)) {
                file.setTag(DocumentFile.PDF);
                return true;
            }
            if (isVideoFile(name)) {
                file.setTag(DocumentFile.VIDEO);
                return true;
            }
            if (isAudioFile(name)) {
                file.setTag(DocumentFile.AUDIO);
                return true;
            }
        } catch (Exception ignore) {
        }
        return false;
    }

    private static boolean isAudioFile(String name) {
        return name.endsWith(".mp3") || name.endsWith(".wav") || name.endsWith(".wma") ||
                name.endsWith(".ogg") || name.endsWith(".ape") || name.endsWith(".flac") ||
                name.endsWith(".aac") || name.endsWith(".m4a") || name.endsWith(".ac3") ||
                name.endsWith(".mmf") || name.endsWith(".amr") || name.endsWith(".m4r") ||
                name.endsWith(".m4b") || name.endsWith(".midi") || name.endsWith(".mp2") ||
                name.endsWith(".mka") || name.endsWith(".mpa") || name.endsWith(".mpc") ||
                name.endsWith(".ra") || name.endsWith(".rm") || name.endsWith(".tta") ||
                name.endsWith(".wv") || name.endsWith(".opus");
    }

    private static boolean isVideoFile(String name) {
        return name.endsWith(".mp4") || name.endsWith(".avi") || name.endsWith(".mov") ||
                name.endsWith(".wmv") || name.endsWith(".flv") || name.endsWith(".mkv") ||
                name.endsWith(".rmvb") || name.endsWith(".rm") || name.endsWith(".3gp") ||
                name.endsWith(".mpeg") || name.endsWith(".mpg") || name.endsWith(".ts") ||
                name.endsWith(".webm") || name.endsWith(".vob") || name.endsWith(".swf");
    }

    private static boolean isPDFFile(String name) {
        return name.endsWith(".pdf");
    }

    private static boolean isPPTFile(String name) {
        return name.endsWith(".ppt") || name.endsWith(".pptx") || name.endsWith(".dps");
    }

    private static boolean isExcelFile(String name) {
        return name.endsWith(".xls") || name.endsWith(".xlsx") || name.endsWith(".et");
    }

    private static boolean isWordFile(String name) {
        return name.endsWith(".doc") || name.endsWith(".docx") || name.endsWith(".wps");
    }

    public static class DocumentFile extends BaseObservable {
        public static final int WORD = 1;
        public static final int EXCEL = 2;
        public static final int PPT = 3;
        public static final int PDF = 4;
        public static final int VIDEO = 5;
        public static final int AUDIO = 6;
        private final XFile xFile;
        private final int category;
        private String name;
        private long size;
        private Uri uri;
        private String path;
        private final String sizeDescribe;
        private boolean isCheck;

        private long lastModified;
        private boolean isTrial;

        public DocumentFile(XFile xFile, int category) {
            this.xFile = xFile;
            this.category = category;
            try {
                this.name = xFile.getName();
            } catch (Exception ignore) {
            }
            try {
                this.size = xFile.length();
            } catch (Exception ignore) {
            }
            try {
                this.uri = xFile.getUri();
            } catch (Exception ignore) {
            }
            try {
                this.path = xFile.getPath();
            } catch (Exception ignore) {
            }
            try {
                this.lastModified = xFile.lastModified();
            } catch (Exception ignore) {
            }
            this.sizeDescribe = FileUtil.formatShortBytes(this.size);
        }

        @Bindable
        public boolean isTrial() {
            return isTrial;
        }

        public void setTrial(boolean trial) {
            isTrial = trial;
            notifyPropertyChanged(BR.trial);
        }

        public long getLastModified() {
            return lastModified;
        }

        @Bindable
        public boolean isCheck() {
            return isCheck;
        }

        public void setCheck(boolean check) {
            isCheck = check;
            notifyPropertyChanged(BR.check);
        }

        public String getSizeDescribe() {
            return sizeDescribe;
        }

        public int getCategory() {
            return category;
        }

        public String getName() {
            return name;
        }

        public long getSize() {
            return size;
        }

        public Uri getUri() {
            return uri;
        }

        public String getPath() {
            return path;
        }

        public InputStream newInputStream() throws Exception {
            return xFile.newInputStream();
        }

        public boolean delete() throws Exception {
            return xFile.delete();
        }

        @NonNull
        @Override
        public String toString() {
            return "DocumentFile{" +
                    "xFile=" + xFile +
                    ", category=" + category +
                    ", name='" + name + '\'' +
                    ", size=" + size +
                    ", uri=" + uri +
                    ", path='" + path + '\'' +
                    ", sizeDescribe='" + sizeDescribe + '\'' +
                    ", isCheck=" + isCheck +
                    ", lastModified=" + lastModified +
                    ", isTrial=" + isTrial +
                    '}';
        }
    }
}