package com.reactlibrary;

import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;

import androidx.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.reactlibrary.interfaces.IDBCallback;
import com.reactlibrary.interfaces.IDBMediaListener;
import com.un4seen.bass.BASS;

import java.io.File;

import static com.reactlibrary.interfaces.IVoiceChangerConstants.FORMAT_NAME_VOICE;
import static com.reactlibrary.interfaces.IVoiceChangerConstants.NAME_FOLDER_RECORD;
import static com.un4seen.bass.BASS.BASS_CONFIG_FLOAT;

public class AudioMixerModule extends ReactContextBaseJavaModule {
    public static final String TAG = AudioMixerModule.class.getSimpleName();

    private final ReactApplicationContext reactContext;

    private EffectObject effectObject;

    private boolean isInit;
    private String mPathAudio;
    private DBMediaPlayer mDBMedia;

    private String mNameExportVoice;

    public AudioMixerModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;

        this.mPathAudio = null;
        this.onInitAudioDevice();
    }

    @Override
    public String getName() {
        return "AudioMixer";
    }


    @ReactMethod
    public void setPath(String path) {
        this.mPathAudio = path;
    }

    @ReactMethod
    public void setEffect(String effect) {
        this.effectObject = JsonParsing.jsonToEffectObject(effect);
    }

    @ReactMethod
    public void saveEffect(String path, Promise promise) {
        onSaveEffect(path, this.effectObject, promise);
    }

    @ReactMethod
    public void createDBMedia() {
        this.onCreateDBMedia();
    }

    @ReactMethod
    public void playEffect(final Promise promise) {
        Log.d(TAG, "audioPath: " + this.mPathAudio);

        if (mPathAudio != null && !mPathAudio.isEmpty()) {
            File mFile = new File(mPathAudio);
            if (!(mFile.exists() && mFile.isFile())) {
                //todo File not found exception
            }
        }

        try {
            onPlayEffect(this.effectObject);
            promise.resolve(true);
        } catch (Exception ex) {
            promise.reject("ERR_UNEXPECTED_EXCEPTION", ex);
        }
    }

    private void onPlayEffect(EffectObject mEffectObject) {
        boolean isPlaying = mEffectObject.isPlaying();
        if (isPlaying) {
            mEffectObject.setPlaying(false);
            if (mDBMedia != null) {
                mDBMedia.pauseAudio();
            }
            sendEvent(reactContext, "onMediaCompletion", null);
        } else {
            onResetState();
            mEffectObject.setPlaying(true);
            if (mDBMedia != null) {
                mDBMedia.prepareAudio();

                mDBMedia.setAudioPitch(mEffectObject.getPitch());
                mDBMedia.setAudioRate(mEffectObject.getRate());

                mDBMedia.startAudio();
            }
        }
    }

    private void onInitAudioDevice() {
        if (!isInit) {
            isInit = true;
            if (!BASS.BASS_Init(-1, 44100, 0)) {
                new Exception(TAG + " Can't initialize device").printStackTrace();
                this.isInit = false;
                return;
            }

            String libpath = this.reactContext.getApplicationInfo().nativeLibraryDir;
            try {
                BASS.BASS_PluginLoad(libpath + "/libbass_fx.so", 0);
                BASS.BASS_PluginLoad(libpath + "/libbassmix.so", 0);
                BASS.BASS_PluginLoad(libpath + "/libbassenc.so", 0);
                int floatsupport = BASS.BASS_GetConfig(BASS_CONFIG_FLOAT);
                Log.d(TAG, "=======>floatsupport=" + floatsupport);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void onSaveEffect(final String path, final EffectObject mEffectObject, final Promise promise) {
        if (mDBMedia != null) {
            onResetState();
        }

        if (mDBMedia != null) {
            startSaveEffect(path, mEffectObject, new IDBCallback() {
                @Override
                public void onAction() {
                    final File mOutPutFile = new File(path);
                    if (mOutPutFile.exists() && mOutPutFile.isFile()) {
                        promise.resolve(mOutPutFile.getAbsolutePath());
                    }
                }
            });

        }
    }

    private void startSaveEffect(final String path, final EffectObject mEffectObject, final IDBCallback mDBCallback) {
        final File mTempOutPutFile = new File(path);

        final DBMediaPlayer mDBExportMedia = new DBMediaPlayer(mPathAudio);

        @SuppressLint("StaticFieldLeak") AsyncTask<Void, Void, Void> mDBTask = new AsyncTask<Void, Void, Void>() {
            protected void onPreExecute() { }

            @Override
            protected Void doInBackground(Void... params) {
                boolean b = mDBExportMedia.initMediaToSave();
                if (b) {
                    mDBExportMedia.setAudioPitch(mEffectObject.getPitch());
                    mDBExportMedia.setAudioRate(mEffectObject.getRate());

                    mDBExportMedia.saveToFile(mTempOutPutFile.getAbsolutePath());
                    mDBExportMedia.releaseAudio();
                }

                return null;
            }
            @Override
            protected void onPostExecute(Void result) {
                if (mDBCallback != null) {
                    mDBCallback.onAction();
                }
            }
        };

        mDBTask.execute();
    }

    @ReactMethod
    private void onCreateDBMedia() {
        if (mPathAudio != null && !mPathAudio.isEmpty()) {
            mDBMedia = new DBMediaPlayer(mPathAudio);
            mDBMedia.prepareAudio();
            mDBMedia.setOnDBMediaListener(new IDBMediaListener() {
                @Override
                public void onMediaError() {

                }

                @Override
                public void onMediaCompletion() {
                    effectObject.setPlaying(false);
                    WritableMap params = Arguments.createMap();
                    sendEvent(reactContext, "onMediaCompletion", params);
                }
            });
        }
    }

    private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
        reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

    public void onResetState() {
        effectObject.setPlaying(false);
    }

    private File getDir() {
        String dirpath = Environment.getExternalStorageDirectory().getPath();
        File dir = new File(dirpath, NAME_FOLDER_RECORD);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return dir;
    }
}
