package ethanjones.cubes.world.save;

import ethanjones.cubes.core.logging.Log;
import ethanjones.cubes.world.storage.Area;

import com.badlogic.gdx.files.FileHandle;

import java.io.*;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;

public class SaveAreaIO {
  private static final ThreadLocal<ThreadData> local = new ThreadLocal<ThreadData>() {
    @Override
    protected ThreadData initialValue() {
      return new ThreadData();
    }
  };

  public static Area read(Save save, int x, int z) {
    FileHandle file = file(save, x, z);

    if (!file.exists()) {
      //Log.warning("Area does not exist");
      return null;
    }

    ThreadData data = local.get();

    try {
      data.inflater.reset();
      InputStream inputStream = file.read(8192);
      InflaterInputStream inflaterInputStream = new InflaterInputStream(inputStream, data.inflater);
      BufferedInputStream bufferedInputStream = new BufferedInputStream(inflaterInputStream);
      DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
      Area area = new Area(x, z);
      area.read(dataInputStream, false);
      dataInputStream.close();
      return area;
    } catch (Exception e) {
      Log.error("Failed to read area", e);
      return null;
    }
  }

  protected static boolean write(Save save, Area area) {
    if (!area.isReady()) return false;
    if (!area.modifiedSinceSave()) return false;
    area.saveModCount();

    ThreadData data = local.get();

    FileHandle file = file(save, area.areaX, area.areaZ);

    boolean write = !file.exists();
    if (write) {
      try {
        data.deflater.reset();
        OutputStream stream = file.write(false, 8192);
        DeflaterOutputStream deflaterStream = new DeflaterOutputStream(stream, data.deflater);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(deflaterStream);
        DataOutputStream dataOutputStream = new DataOutputStream(bufferedOutputStream);
        area.write(dataOutputStream, false, false); //TODO resize when writing
        bufferedOutputStream.flush();
        deflaterStream.finish();
        stream.close();
      } catch (Exception e) {
        Log.error(e);
        return false;
      }
    }

    return write;
  }

  private static class ThreadData {
    final Deflater deflater = new Deflater();
    final Inflater inflater = new Inflater() {
      @Override
      public void end() {
        // do nothing, as android calls inflater.end() when closing InflaterInputStream
      }
    };
  }

  public static FileHandle file(Save save, int x, int z) {
    FileHandle folderArea = save.folderArea();
    FileHandle xMostSignificant = folderArea.child(Integer.toString(x & 0xFFFF0000));
    FileHandle xLeastSignificant = xMostSignificant.child(Integer.toString(x & 0xFFFF));
    FileHandle zMostSignificant = xLeastSignificant.child(Integer.toString(z & 0xFFFF0000));
    zMostSignificant.mkdirs();
    return zMostSignificant.child(Integer.toString(z & 0xFFFF));
  }
}

@@ -392,54 +392,255 @@
 cal%3C
-ThreadData%3E local = new ThreadLocal%3CThreadData
+Deflater%3E deflaterThreadLocal = new ThreadLocal%3CDeflater%3E() %7B%0A    @Override%0A    protected Deflater initialValue() %7B%0A      return new Deflater();%0A    %7D%0A  %7D;%0A  private static final ThreadLocal%3CInflater%3E inflaterThreadLocal = new ThreadLocal%3CInflater
 %3E() 
@@ -669,26 +669,24 @@
 otected 
-ThreadData
+Inflater
  initial
@@ -716,20 +716,174 @@
 new 
-ThreadData()
+Inflater() %7B%0A        @Override%0A        public void end() %7B%0A          // do nothing, as android calls inflater.end() when closing InflaterInputStream%0A        %7D%0A      %7D
 ;%0A  
@@ -1086,35 +1086,51 @@
  %7D%0A%0A    
-ThreadData data = l
+Inflater inflater = inflaterThreadL
 ocal.get
@@ -1150,21 +1150,16 @@
 %7B%0A      
-data.
 inflater
@@ -1302,21 +1302,16 @@
 Stream, 
-data.
 inflater
@@ -1894,27 +1894,43 @@
     
-ThreadData data = l
+Deflater deflater = deflaterThreadL
 ocal
@@ -2000,64 +2000,8 @@
 Z);%0A
-%0A    boolean write = !file.exists();%0A    if (write) %7B%0A  
     
@@ -2016,15 +2016,8 @@
     
-  data.
 defl
@@ -2026,26 +2026,24 @@
 er.reset();%0A
-  
       Output
@@ -2089,18 +2089,16 @@
 ;%0A      
- 
- 
 Deflater
@@ -2164,13 +2164,8 @@
 am, 
-data.
 defl
@@ -2173,26 +2173,24 @@
 ter);%0A      
-  
 BufferedOutp
@@ -2265,26 +2265,24 @@
 eam);%0A      
-  
 DataOutputSt
@@ -2345,26 +2345,24 @@
 putStream);%0A
-  
       area.w
@@ -2432,18 +2432,16 @@
 g%0A      
-  
 buffered
@@ -2464,26 +2464,24 @@
 sh();%0A      
-  
 deflaterStre
@@ -2489,26 +2489,24 @@
 m.finish();%0A
-  
       stream
@@ -2519,18 +2519,16 @@
 ();%0A    
-  
 %7D catch 
@@ -2545,26 +2545,24 @@
  e) %7B%0A      
-  
 Log.error(e)
@@ -2559,26 +2559,24 @@
 g.error(e);%0A
-  
       return
@@ -2583,24 +2583,16 @@
  false;%0A
-      %7D%0A
     %7D%0A%0A 
@@ -2605,292 +2605,12 @@
 urn 
-write;%0A  %7D%0A%0A  private static class ThreadData %7B%0A    final Deflater deflater = new Deflater();%0A    final Inflater inflater = new Inflater() %7B%0A      @Override%0A      public void end() %7B%0A        // do nothing, as android calls inflater.end() when closing InflaterInputStream%0A      %7D%0A    %7D
+true
 ;%0A  

Split ThreadLocal<ThreadData> in SaveAreaIO to ThreadLocal<Deflater> and ThreadLocal<Inflater>