package com.manning.aip.fileexplorer.util;

import android.os.Environment;
import android.util.Log;

import com.manning.aip.fileexplorer.Constants;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.channels.FileChannel;

/**
 * Metody klasy FileUtil. 
 * 
 * @author ccollins
 *
 */
public final class FileUtil {

   // Są to zalecane ścieżki (według dokumentacji Androida).
   private static final String EXT_STORAGE_PATH_PREFIX = "/Android/data/";
   private static final String EXT_STORAGE_FILES_PATH_SUFFIX = "/files/";
   private static final String EXT_STORAGE_CACHE_PATH_SUFFIX = "/cache/";

   // Obiekty do nakładania blokad (według dokumentacji tablica 
   // o długości 0 jest "lżejsza" od zwykłego obiektu klasy Object).
   public static final Object[] DATA_LOCK = new Object[0];

   private FileUtil() {
   }

   /**
    * Używa klasy Environment do sprawdzenia, czy możliwy jest zapis 
    * w pamięci zewnętrznej.
    * 
    * @return
    */
   public static boolean isExternalStorageWritable() {
      return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
   }

   /**
    * Używa klasy Environment do sprawdzenia, czy możliwy jest odczyt 
    * z pamięci zewnętrznej.
    * 
    * @return
    */
   public static boolean isExternalStorageReadable() {
      if (isExternalStorageWritable()) {
         return true;
      }
      return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
   }

   /**
    * Zwraca zalecany katalog na pliki w pamięci zewnętrznej niezależnie 
    * od używanej wersji interfejsu API.
    * Wywołuje metodę getExternalStorageDirectory, a następnie dołącza zalecaną
    * ścieżkę.
    * 
    * @param packageName
    * @return
    */
   public static File getExternalFilesDirAllApiLevels(final String packageName) {
      return FileUtil.getExternalDirAllApiLevels(packageName, EXT_STORAGE_FILES_PATH_SUFFIX);
   }
   
   /**
    * Zwraca zalecany katalog na zewnętrzną pamięć podręczną niezależnie 
    * od używanej wersji interfejsu API.
    * Wywołuje metodę getExternalStorageDirectory, a następnie dołącza zalecaną
    * ścieżkę.
    * 
    * @param packageName
    * @return
    */
   public static File getExternalCacheDirAllApiLevels(final String packageName) {
      return FileUtil.getExternalDirAllApiLevels(packageName, EXT_STORAGE_CACHE_PATH_SUFFIX);
   }

   private static File getExternalDirAllApiLevels(final String packageName, final String suffixType) {
      File dir =
               new File(Environment.getExternalStorageDirectory() + EXT_STORAGE_PATH_PREFIX + packageName + suffixType);
      synchronized (FileUtil.DATA_LOCK) {
         try {
            dir.mkdirs();
            dir.createNewFile();
         } catch (IOException e) {
            Log.e(Constants.LOG_TAG, "Błąd przy tworzeniu pliku", e);
         }
      }
      return dir;
   }

   /**
    * Kopiowanie pliku. Metoda zwraca true przy powodzeniu i false 
    * przy niepowodzeniu.
    * 
    * @param src
    * @param dst
    * @return
    */
   public static boolean copyFile(final File src, final File dst) {
      boolean result = false;
      FileChannel inChannel = null;
      FileChannel outChannel = null;
      synchronized (FileUtil.DATA_LOCK) {
         try {
            inChannel = new FileInputStream(src).getChannel();
            outChannel = new FileOutputStream(dst).getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
            result = true;
         } catch (IOException e) {

         } finally {
            if (inChannel != null && inChannel.isOpen()) {
               try {
                  inChannel.close();
               } catch (IOException e) {
                  // Ignorowanie wyjątku.
               }
            }
            if (outChannel != null && outChannel.isOpen()) {
               try {
                  outChannel.close();
               } catch (IOException e) {
                  // Ignorowanie wyjątku.
               }
            }
         }
      }
      return result;
   }

   /**
    * Zastępuje cały plik łańcuchem znaków. Zwraca true przy powodzeniu
    * i false przy niepowodzeniu.
    * 
    * @param fileContents
    * @param file
    * @return
    */
   public static boolean writeStringAsFile(final String fileContents, final File file) {
      boolean result = false;
      try {
         synchronized (FileUtil.DATA_LOCK) {
            if (file != null) {
               file.createNewFile(); 
               Writer out = new BufferedWriter(new FileWriter(file), 1024);
               out.write(fileContents);
               out.close();
               result = true;
            }
         }
      } catch (IOException e) {
         Log.e(Constants.LOG_TAG, "Błąd przy zapisanie tekstu do pliku " + e.getMessage(), e);
      }
      return result;
   }

   /**
    * Dodawanie łańcucha znaków na koniec pliku. Zwraca true przy powodzeniu
    * i false przy niepowodzeniu.
    * 
    * @param appendContents
    * @param file
    * @return
    */
   public static boolean appendStringToFile(final String appendContents, final File file) {
      boolean result = false;
      try {
         synchronized (FileUtil.DATA_LOCK) {
            if ((file != null) && file.canWrite()) {
               file.createNewFile();
               Writer out = new BufferedWriter(new FileWriter(file, true), 1024);
               out.write(appendContents);
               out.close();
               result = true;
            }
         }
      } catch (IOException e) {
         Log.e(Constants.LOG_TAG, "Błąd przy dodawaniu tekstu do pliku " + e.getMessage(), e);
      }
      return result;
   }

   /**
    * Wczytywanie pliku jako łańcucha znaków. Metoda zwraca null, jeśli plik 
    * nie istnieje lub jego odczyt jest niemożliwy.
    * 
    * @param file
    * @return
    */
   public static String readFileAsString(final File file) {
      StringBuilder sb = null;
      try {
         synchronized (FileUtil.DATA_LOCK) {
            if ((file != null) && file.canRead()) {
               sb = new StringBuilder();
               String line = null;
               BufferedReader in = new BufferedReader(new FileReader(file), 1024);
               while ((line = in.readLine()) != null) {
                  sb.append(line + System.getProperty("line.separator"));
               }
            }
         }
      } catch (IOException e) {
         Log.e(Constants.LOG_TAG, "Błąd odczytu pliku " + e.getMessage(), e);
      }
      if (sb != null) {
         return sb.toString();
      }
      return null;
   }
}
