/*
 * Copyright (C) 2013 YIXIA.COM
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.vov.vitamio;

import android.content.Context;

import io.vov.vitamio.utils.CPU;
import io.vov.vitamio.utils.ContextUtils;
import io.vov.vitamio.utils.IOUtils;
import io.vov.vitamio.utils.Log;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Inspect this class before using any other Vitamio classes.
 * <p/>
 * Don't modify this class, or the full Vitamio library may be broken.
 */
public class Vitamio {
  private static final String[] LIBS_ARM_CODECS = {"libvvo.7.so", "libvvo.8.so", "libffmpeg.so", "libOMX.9.so", "libOMX.11.so", "libOMX.14.so", "libOMX.18.so"};
  private static final String[] LIBS_X86_CODECS = {"libffmpeg.so", "libOMX.9.so", "libOMX.14.so", "libOMX.18.so"};
  private static final String[] LIBS_MIPS_CODECS = {"libffmpeg.so", "libOMX.14.so"};
  private static final String[] LIBS_PLAYER = {"libvplayer.so"};
  private static final String[] LIBS_SCANNER = {"libvscanner.so"};
  private static final String[] LIBS_AV = {"libvao.0.so", "libvvo.0.so", "libvvo.9.so", "libvvo.j.so"};
  private static final String LIBS_LOCK = ".lock";
  private static final int VITAMIO_NOT_SUPPORTED = -1;
  private static final int VITAMIO_MIPS = 40;
  private static final int VITAMIO_X86 = 50;
  private static final int VITAMIO_ARMV6 = 60;
  private static final int VITAMIO_ARMV6_VFP = 61;
  private static final int VITAMIO_ARMV7_VFPV3 = 70;
  private static final int VITAMIO_ARMV7_NEON = 71;
  private static final int vitamioType;

  static {
    int cpu = CPU.getFeature();
    if ((cpu & CPU.FEATURE_ARM_NEON) > 0)
      vitamioType = VITAMIO_ARMV7_NEON;
    else if ((cpu & CPU.FEATURE_ARM_VFPV3) > 0 && (cpu & CPU.FEATURE_ARM_V7A) > 0)
      vitamioType = VITAMIO_ARMV7_VFPV3;
    else if ((cpu & CPU.FEATURE_ARM_VFP) > 0 && (cpu & CPU.FEATURE_ARM_V6) > 0)
      vitamioType = VITAMIO_ARMV6_VFP;
    else if ((cpu & CPU.FEATURE_ARM_V6) > 0)
    	vitamioType = VITAMIO_ARMV6;
    else if ((cpu & CPU.FEATURE_X86) > 0)
    	vitamioType = VITAMIO_X86;
    else if ((cpu & CPU.FEATURE_MIPS) > 0) 
    	vitamioType = VITAMIO_MIPS;
    else
    	vitamioType = VITAMIO_NOT_SUPPORTED;
  }

  private static String vitamioPackage;
  private static String vitamioLibraryPath;

  /**
   * Call this method before using any other Vitamio specific classes.
   * <p/>
   * This method will use {@link #isInitialized(Context)} to check if Vitamio is
   * initialized at this device, and initialize it if not initialized.
   *
   * @param ctx Android Context
   * @return true if the Vitamio initialized successfully.
   */
  public static boolean initialize(Context ctx) {
    return isInitialized(ctx) || extractLibs(ctx, R.raw.libarm);
  }

  /**
   * Same as {@link #initialize(Context)}
   *
   * @param ctx   Android Context
   * @param rawId R.raw.libarm
   * @return true if the Vitamio initialized successfully.
   */
  public static boolean initialize(Context ctx, int rawId) {
    return isInitialized(ctx) || extractLibs(ctx, rawId);
  }

  /**
   * Check if Vitamio is initialized at this device
   *
   * @param ctx Android Context
   * @return true if the Vitamio has been initialized.
   */
  public static boolean isInitialized(Context ctx) {
    vitamioPackage = ctx.getPackageName();
    vitamioLibraryPath = ContextUtils.getDataDir(ctx) + "libs/";
    File dir = new File(getLibraryPath());
    if (dir.exists() && dir.isDirectory()) {
      String[] libs = dir.list();
      if (libs != null) {
        Arrays.sort(libs);
        for (String L : getRequiredLibs()) {
          if (Arrays.binarySearch(libs, L) < 0) {
            Log.e("Native libs %s not exists!", L);
            return false;
          }
        }
        File lock = new File(getLibraryPath() + LIBS_LOCK);
        BufferedReader buffer = null; 
        try {
          buffer = new BufferedReader(new FileReader(lock));  
          int appVersion = ContextUtils.getVersionCode(ctx);
          int libVersion = Integer.valueOf(buffer.readLine());  
          Log.i("isNativeLibsInited, APP VERSION: %d, Vitamio Library version: %d", appVersion, libVersion);
          if (libVersion == appVersion)
            return true;
        } catch (IOException e) {
          Log.e("isNativeLibsInited", e);
        } catch (NumberFormatException e) {
        	Log.e("isNativeLibsInited", e);
        } finally {
          IOUtils.closeSilently(buffer);
        }
      }
    }
    return false;
  }

  public static String getVitamioPackage() {
    return vitamioPackage;
  }

  public static int getVitamioType() {
    return vitamioType;
  }

  public static final String getLibraryPath() {
    return vitamioLibraryPath;
  }

  private static final List<String> getRequiredLibs() {
    List<String> libs = new ArrayList<String>();
    String[][] vitamioLibs = null;
    switch (vitamioType) {
		case VITAMIO_ARMV6:
		case VITAMIO_ARMV6_VFP:
		case VITAMIO_ARMV7_VFPV3:
		case VITAMIO_ARMV7_NEON:
			vitamioLibs = new String[][]{LIBS_ARM_CODECS, LIBS_PLAYER, LIBS_SCANNER, LIBS_AV};
			break;
		case VITAMIO_X86:
			vitamioLibs = new String[][]{LIBS_X86_CODECS, LIBS_PLAYER, LIBS_SCANNER, LIBS_AV};
			break;
		case VITAMIO_MIPS:
			vitamioLibs = new String[][]{LIBS_MIPS_CODECS, LIBS_PLAYER, LIBS_SCANNER, LIBS_AV};
			break;
		default:
			break;
		}
    if (vitamioLibs == null)
    	return libs;
    for (String[] libArray : vitamioLibs) {
      for (String lib : libArray)
        libs.add(lib);
    }
    libs.add(LIBS_LOCK);
    return libs;
  }

  private static boolean extractLibs(Context ctx, int rawID) {
    long begin = System.currentTimeMillis();
    final int version = ContextUtils.getVersionCode(ctx);
    Log.d("loadLibs start " + version);
    File lock = new File(getLibraryPath() + LIBS_LOCK);
    if (lock.exists())
      lock.delete();
    String libPath = copyCompressedLib(ctx, rawID, "libarm.so");
    Log.d("copyCompressedLib time: " + (System.currentTimeMillis() - begin) / 1000.0);
    boolean inited = native_initializeLibs(libPath, getLibraryPath(), String.valueOf(Vitamio.getVitamioType()));
    new File(libPath).delete();
    FileWriter fw = null;
    try {
      lock.createNewFile();
      fw = new FileWriter(lock);
      fw.write(String.valueOf(version));
      return true;
    } catch (IOException e) {
      Log.e("Error creating lock file", e);
    } finally {
      Log.d("initializeNativeLibs: " + inited);
      Log.d("loadLibs time: " + (System.currentTimeMillis() - begin) / 1000.0);
      IOUtils.closeSilently(fw);
    }
    return false;
  }

  private static String copyCompressedLib(Context ctx, int rawID, String destName) {
    byte[] buffer = new byte[1024];
    InputStream is = null;
    BufferedInputStream bis = null;
    FileOutputStream fos = null;
    String destPath = null;

    try {
      try {
        String destDir = getLibraryPath();
        destPath = destDir + destName;
        File f = new File(destDir);
        if (f.exists() && !f.isDirectory())
          f.delete();
        if (!f.exists())
          f.mkdirs();
        f = new File(destPath);
        if (f.exists() && !f.isFile())
          f.delete();
        if (!f.exists())
          f.createNewFile();
      } catch (Exception fe) {
        Log.e("loadLib", fe);
      }

      is = ctx.getResources().openRawResource(rawID);
      bis = new BufferedInputStream(is);
      fos = new FileOutputStream(destPath);
      while (bis.read(buffer) != -1) {
        fos.write(buffer);
      }
    } catch (Exception e) {
      Log.e("loadLib", e);
      return null;
    } finally {
      IOUtils.closeSilently(fos);
      IOUtils.closeSilently(bis);
      IOUtils.closeSilently(is);
    }

    return destPath;
  }

  static {
    System.loadLibrary("vinit");
  }

  private native static boolean native_initializeLibs(String libPath, String destDir, String prefix);
}