package be.tarsos.dsp.io.android;

import android.content.Context;
import android.content.res.AssetManager;
import android.os.Build;
import android.util.Log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

/**
 * <p>
 * The Android FFMPEG locator determines the current CPU architecture of the
 * running Android device and extracts a statically compiled <a href="http://ffmpeg.org">ffmpeg</a>
 * binary from the assets folder to the temporary directory of the currently running Android application.
 * For this to work the assets folder should contain these binaries:
 * </p>
 * 
 * <li>
 * <ul><code>assets/x86_ffmpeg</code> for x86</ul>
 * <ul><code>assets/armeabi-v7a_ffmpeg</code> for armeabi-v7a</ul>
 * <ul><code>assets/armeabi-v7a-neon_ffmpeg</code> for armeabi-v7a-neon</ul>
 * </li>
 * 
 * 
 * <p>
 * You can download these binaries 
 * <a href="https://github.com/hiteshsondhi88/ffmpeg-android/releases/download/v0.3.3/prebuilt-binaries.zip">here</a> 
 * and on the <a href="http://0110.be/releases/TarsosDSP/TarsosDSP-static-ffmpeg/Android/">TarsosDSP ffmpeg repository</a>.
 * Other architectures are currently not supported but could be included in later releases.
 * </p>
 *   
 * <p>
 * If you are a masochist and want to compile ffmpeg for Android yourself you can get your fix <a href="https://github.com/hiteshsondhi88/ffmpeg-android">here</a>
 * </p> 
 * @author Joren Six
 */
public class AndroidFFMPEGLocator {

    private static final String TAG = "AndroidFFMPEGLocator";

    public AndroidFFMPEGLocator(Context context){
        CPUArchitecture architecture = getCPUArchitecture();

        Log.i(TAG,"Detected Native CPU Architecture: " + architecture.name());

        if(!ffmpegIsCorrectlyInstalled()){
            String ffmpegFileName = getFFMPEGFileName(architecture);
            AssetManager assetManager = context.getAssets();
            unpackFFmpeg(assetManager,ffmpegFileName);
        }
        File ffmpegTargetLocation = AndroidFFMPEGLocator.ffmpegTargetLocation();
        Log.i(TAG, "Ffmpeg binary location: " + ffmpegTargetLocation.getAbsolutePath() + " is executable? " + ffmpegTargetLocation.canExecute() + " size: " + ffmpegTargetLocation.length() + " bytes");
    }

    private String getFFMPEGFileName(CPUArchitecture architecture){
        final String ffmpegFileName;
        switch (architecture){
            case X86:
                ffmpegFileName = "x86_ffmpeg";
                break;
            case ARMEABI_V7A:
                ffmpegFileName = "armeabi-v7a_ffmpeg";
                break;
            case ARMEABI_V7A_NEON:
                ffmpegFileName = "armeabi-v7a-neon_ffmpeg";
                break;
            default:
                ffmpegFileName = null;
                String message= "Could not determine your processor architecture correctly, no ffmpeg binary available.";
                Log.e(TAG,message);
                throw new Error(message);
        }
        return ffmpegFileName;
    }

    private boolean ffmpegIsCorrectlyInstalled(){
        File ffmpegTargetLocation = AndroidFFMPEGLocator.ffmpegTargetLocation();
        //assumed to be correct if existing and executable and larger than 1MB:
        return ffmpegTargetLocation.exists() && ffmpegTargetLocation.canExecute() && ffmpegTargetLocation.length() > 1000000;
    }

    private void unpackFFmpeg(AssetManager assetManager,String ffmpegAssetFileName) {
        InputStream inputStream=null;
        OutputStream outputStream=null;
        try{
            File ffmpegTargetLocation = AndroidFFMPEGLocator.ffmpegTargetLocation();
            inputStream = assetManager.open(ffmpegAssetFileName);
            outputStream = new FileOutputStream(ffmpegTargetLocation);
            byte buffer[] = new byte[1024];
            int length = 0;
            while((length=inputStream.read(buffer)) > 0) {
                outputStream.write(buffer,0,length);
            }
            //makes ffmpeg executable
            ffmpegTargetLocation.setExecutable(true);
            Log.i(TAG,"Unpacked ffmpeg binary " + ffmpegAssetFileName + " , extracted  " + ffmpegTargetLocation.length() + " bytes. Extracted to: " + ffmpegTargetLocation.getAbsolutePath());
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            //cleanup
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            }catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static final File ffmpegTargetLocation(){
        String tempDirectory = System.getProperty("java.io.tmpdir");
        File ffmpegTargetLocation = new File(tempDirectory,"ffmpeg");
        return ffmpegTargetLocation;
    }

    private enum CPUArchitecture{
        X86,ARMEABI_V7A,ARMEABI_V7A_NEON;
    }

    private boolean isCPUArchitectureSupported(String alias) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            for (String supportedAlias : Build.SUPPORTED_ABIS) {
                if (supportedAlias.equals(alias))
                    return true;
            }

            return false;
        } else {
            return Build.CPU_ABI.equals(alias);
        }
    }

    private CPUArchitecture getCPUArchitecture() {
        // check if device is x86
        if (isCPUArchitectureSupported("x86")) {
            return CPUArchitecture.X86;
        } else if (isCPUArchitectureSupported("armeabi-v7a")) {
            // check if NEON is supported:
            if (isNeonSupported()) {
                return CPUArchitecture.ARMEABI_V7A_NEON;
            } else {
                return CPUArchitecture.ARMEABI_V7A;
            }
        }
        return null;
    }

    private boolean isNeonSupported() {
        try {
            BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(new File("/proc/cpuinfo"))));
            String line = null;
            while ((line = input.readLine()) != null) {
                Log.d(TAG, "CPUINFO line: " + line);
                if(line.toLowerCase().contains("neon")) {
                    return true;
                }
            }
            input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

}