/*--
  This file is a part of ZetaGrid, a simple and secure Grid Computing
  kernel.

  Copyright (c) 2001-2004 Sebastian Wedeniwski.  All rights reserved.

  Use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:

  1. The source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.

  2. The origin of this software must not be misrepresented; you must 
     not claim that you wrote the original software.  If you plan to
     use this software in a product, please contact the author.

  3. Altered source versions must be plainly marked as such, and must
     not be misrepresented as being the original software. The author
     must be informed about these changes.

  4. The name of the author may not be used to endorse or promote 
     products derived from this software without specific prior written 
     permission.

  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

  Version 1.9.0, February 8, 2004

  This program is based on the work of:
     S. Wedeniwski
--*/

package zeta.tool;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
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.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import zeta.TaskManager;
import zeta.tool.check.CheckConsistencyZetaZeros;
import zeta.util.StreamUtils;
import zeta.util.ThrowableHandler;

public class ZetaStatistic {
  public static void main(String[] args) {
    if (args.length == 3 && args[0].length() == 1) {
      if (args[0].charAt(0) == 'v') {
        verify(args[1], args[2]);
        return;
      } else if (args[0].charAt(0) == 'c') {
        check(Integer.parseInt(args[1]), args[2]);
        return;
      } else if (args[0].charAt(0) == 'e') {
        try {
          expandFileAndCheck(args[1], args[2], null);
        } catch (Exception e) {
          ThrowableHandler.handle(e);
        }
        return;
      }
    } else if (args.length == 2 && args[0].length() == 1 && args[0].charAt(0) == 'r') {
      reduceFilesize2(args[1]);
      return;
    } else if (args.length == 2 && args[0].length() == 1 && args[0].charAt(0) == 'g') {
      System.out.println(getStartN(Double.parseDouble(args[1])));
      return;
    } else if (args.length == 1 && args[0].length() == 1 && args[0].charAt(0) == '?') {
      System.out.println("USAGE: e(xpand) <input> <output>\n"
                       + "       r(educe) <input/output>"
                       + "       g(etStartN) <t>");
      return;
    }
    calc(-1);
  }

  static void calc(int maxNumberToProceed) { // maxNumberToProceed = -1 means infinitely many
    try {
      TaskManager.getTask("zeta-zeros").setResources(2);    // use more memory
    } catch (Exception e) {
      ThrowableHandler.handle(e);
    }
    ZetaStatistic.maxNumberToProceed = maxNumberToProceed;
    PrintStream stdOut = System.out;
    PrintStream log = null;
    try {
      log = new PrintStream(new FileOutputStream("statistic.log", true), true);
      System.setOut(log);
      String[] lastError = new String[2];
      lastError[0] = lastError[1] = "";
      int anzahlError = 0;
      while (true) {
        String[] error = null;
        try {
          error = statistic();
        } catch (ZipException ze) {
          ThrowableHandler.handle(ze);
          WorkUnit workUnit = new WorkUnit(ze.getMessage());
          if (workUnit.isValid()) {
            if (recomputation(workUnit.workUnitId, workUnit.range)) {
              continue;
            } else {
              throw ze;
            }
          }
        }
        if (error == null) {
          break;
        }
        if (error.length == 0) {
          continue;
        }
        long[] sizeRange = new long[2];
        sizeRange[0] = Long.parseLong(error[1])-100;
        WorkUnit workUnit = new WorkUnit(error[4]);
        long workUnitId = (workUnit.isValid())? workUnit.workUnitId : 0;
        if (sizeRange[0] < workUnitId) {
          sizeRange[0] = workUnitId;
        }
        if (!lastError[0].equals(error[0]) || !lastError[1].equals(error[1])) {
          anzahlError = 0;
        } else if (++anzahlError == 2) {
          break;
        }
        lastError = error;
        if (anzahlError == 0) {
          sizeRange[1] = 200;
          zetaZerosExpand(sizeRange[0], (int)sizeRange[1], 0);
          if (updateData(sizeRange, error[2], error[3], error[4])) continue;
        }

        // Check fast algorithm:
        String statisticTmp = fastCheck(workUnitId, error);
        int idx = statisticTmp.indexOf(';')+1;
        int idx2 = statisticTmp.indexOf(';', idx);
        if (idx2 == -1 || statisticTmp == error[5]) {
          // Faster at beginning:
          while (true) {
            statisticTmp = error[5];
            for (sizeRange[1] = 300; sizeRange[1] <= 1300; sizeRange[1] += 500, sizeRange[0] -= 100) {
              zetaZerosExpand(sizeRange[0], (int)sizeRange[1], 0);
              if (updateData(sizeRange, error[2], null, null)) break;
            }
            if (sizeRange[1] > 1300) {
              for (sizeRange[1] = 10000; sizeRange[1] <= 50000; sizeRange[1] += 20000, sizeRange[0] -= 100) {
                zetaZerosExpand(sizeRange[0], (int)sizeRange[1], 0);
                if (updateData(sizeRange, error[2], null, null)) break;
              }
              if (sizeRange[1] > 50000) break;
            }
            statisticTmp = statistic(statisticTmp, ConstantProperties.TEMP_DIR, true);
            idx = statisticTmp.indexOf(';')+1;
            idx2 = statisticTmp.indexOf(';', idx);
            if (idx2 != -1) break;
            sizeRange[0] = Long.parseLong(statisticTmp.substring(idx))-100;
            if (sizeRange[0] < workUnitId) sizeRange[0] = workUnitId;
          }
        }
        reduceFilesize2(ConstantProperties.TEMP_DIR + "zeta_zeros.txt", error[4]);
        updateData(null, error[2], error[3], error[4]);
      }
    } catch (Exception e) {
      ThrowableHandler.handle(e);
    } finally {
      StreamUtils.close(log);
    }
    System.setOut(stdOut);
  }

  private static String[] statistic() throws Exception { // return not null if an error occur
    File file = new File(ConstantProperties.FINAL_DIR + "/1");
    File[] list = file.listFiles();
    if (list != null) {
      long maxRange = 0;
      File tmpFile = null;
      int size = 0;
      for (int i = 0; i < list.length; ++i) {
        String s = list[i].getName();
        int l = s.length();
        if (s.startsWith("zeta_zeros_") && l > 11 && Character.isDigit(s.charAt(11))) {
          if (s.endsWith(".zip")) {
            ++size;
          } else if (s.endsWith(".tmp") && s.startsWith("zeta_zeros_0_") && l > 13 && Character.isDigit(s.charAt(13))) {
            int idx = 13;
            while (++idx < l && Character.isDigit(s.charAt(idx)));
            long r = Long.parseLong(s.substring(13, idx));
            if (r > maxRange) {
              maxRange = r;
              tmpFile = list[i];
            }
          }
        }
      }
      maxRange += 2;
      WorkUnit[] workUnits = new WorkUnit[size];
      size = 0;
      for (int i = 0; i < list.length; ++i) {
        String s = list[i].getName();
        int l = s.length();
        if (s.startsWith("zeta_zeros_") && s.endsWith(".zip") && l > 11 && Character.isDigit(s.charAt(11))) {
          int idx = 11;
          while (++idx < l && Character.isDigit(s.charAt(idx)));
          if (++idx < l && Character.isDigit(s.charAt(idx)) && idx < l-3) {
            workUnits[size++] = new WorkUnit(Long.parseLong(s.substring(11, idx-1)), Integer.parseInt(s.substring(idx, l-4)));
          } else {
            throw new RuntimeException(s);
          }
        }
      }
      Arrays.sort(workUnits);
      String statisticTmp = null;
      if (tmpFile != null) {
        if (!lastTmpFilename.equals(tmpFile.getName())) {
          lastTmpFilename = tmpFile.getName();
          System.out.println(lastTmpFilename);
        }
        ByteArrayOutputStream buffer = new ByteArrayOutputStream((int)tmpFile.length());
        StreamUtils.writeData(new FileInputStream(tmpFile), buffer, true, true);
        statisticTmp = buffer.toString();
      }
      byte[] buffer = new byte[1000000];
      for (int i = 0; i < workUnits.length; ++i) {
        if (workUnits[i].workUnitId <= maxRange && workUnits[i].workUnitId+workUnits[i].range > maxRange) {
          String s = "zeta_zeros_" + workUnits[i].workUnitId + '_';
          File f = null;
          for (int j = 0; j < list.length; ++j) {
            String s2 = list[j].getName();
            if (s2.startsWith(s) && s2.endsWith(".zip")) {
              f = list[j];
              break;
            }
          }
          if (f != null) {
            System.out.println(f.getName());
            try {
              ZipInputStream zip = new ZipInputStream(new FileInputStream(f));
              for (int k = 0; k < 2; ++k) {
                ZipEntry zEntry = zip.getNextEntry();
                String filename = null;
                String name = zEntry.getName();
                if (name.endsWith(".txt")) {
                  filename = "zeta_zeros.txt";
                } else if (name.endsWith(".log")) {
                  filename = "zeta_zeros.log";
                } else if (name.endsWith(".$$$")) {
                  zip.close();
                  GetData.decrypt(1, ConstantProperties.FINAL_DIR + "/1/" + f.getName());   // ToDo: not fix taskId=1
                  return new String[0];
                }
                if (filename == null) {
                  System.err.println("Fatal error reading '" + f.getName() + "'!");
                  System.exit(1);
                }
                File tempFile = new File(ConstantProperties.TEMP_DIR + filename);
                if (!tempFile.delete() && tempFile.exists()) {
                  System.err.println("Fatal error deleting '" + f.getName() + "'!");
                  System.exit(1);
                }
                try {
                  StreamUtils.writeData(zip, new FileOutputStream(tempFile), false, true);
                } catch (IOException ioe) {
                  if (filename.equals("zeta_zeros.txt") && tempFile.length() > 50*1024*1024) { // 50 MB
                    ThrowableHandler.handle(ioe);
                    throw new ZipException("Too large");
                  } else {
                    throw ioe;
                  }
                }
              }
              zip.close();
            } catch (NullPointerException npe) {
              ThrowableHandler.handle(npe);
              throw new ZipException(workUnits[i].toString());
            } catch (EOFException ioe) {
              ThrowableHandler.handle(ioe);
              throw new ZipException(workUnits[i].toString());
            } catch (ZipException ze) {
              ThrowableHandler.handle(ze);
              throw new ZipException(workUnits[i].toString());
            }
            if (reduceFilesize2(ConstantProperties.TEMP_DIR + "zeta_zeros.txt", ConstantProperties.TEMP_DIR + f.getName().substring(0, f.getName().length()-3) + "txt")) {
              updateData(null, ConstantProperties.TEMP_DIR + "zeta_zeros.txt", ConstantProperties.TEMP_DIR + "zeta_zeros.log", f.getName());
              return new String[0];
            }
            long[] problems = expandFileAndCheck(ConstantProperties.TEMP_DIR + "zeta_zeros.txt", workUnits[i]);
            if (problems != null && problems.length > 1) {
              if (workUnits[i].workUnitId == previousWorkUnitId && problems.length >= previousNumberProblems && previousFirstProblem == problems[0]) {
                System.err.println("Fatal error: Again " + (problems.length/2) + " problems (" + (previousNumberProblems/2) + ')');
                System.exit(1);
              }
              long sizeOfProblem = 0;
              for (int k = 1; k < problems.length; k += 2) {
                sizeOfProblem += problems[k];
              }
              System.out.println("Number of problems: " + (problems.length/2) + ", size=" + sizeOfProblem);
              if (problems.length > 200 || sizeOfProblem*5 > workUnits[i].range) {  // too many errors -> recomputation of the work unit
                throw new ZipException(workUnits[i].toString());
              }
              for (int k = 0; k < problems.length; k += 2) {
                zetaZerosExpand(problems[k], (int)problems[k+1], 0);
              }
              updateData(problems, ConstantProperties.TEMP_DIR + "zeta_zeros.txt", ConstantProperties.TEMP_DIR + "zeta_zeros.log", f.getName());
              previousWorkUnitId = workUnits[i].workUnitId;
              previousNumberProblems = problems.length;
              previousFirstProblem = problems[0];
              return new String[0];
            }
            previousNumberProblems = 0;
            //int lines = checkDuplicateZeros(ConstantProperties.TEMP_DIR + "zeta_zeros.txt");
            //boolean update = (workUnits[i].workUnitId != previousWorkUnitId && lines == -1 && removeDuplicateZeros(ConstantProperties.TEMP_DIR + "zeta_zeros.txt"));
            previousWorkUnitId = workUnits[i].workUnitId;
            if (!checkLog(ConstantProperties.TEMP_DIR + "zeta_zeros.log") || !checkHeader.checkHeader(workUnits[i].workUnitId, workUnits[i].range, ConstantProperties.TEMP_DIR + "zeta_zeros.log")) {
              throw new ZipException(workUnits[i].toString());
            }
            if (problems != null && problems.length == 1) {
              int lines = (int)problems[0];
              if (lines > 0 && lines+500 < workUnits[i].range) {
                String nameTxt = f.getName().substring(0, f.getName().length()-3) + "txt";
                String nameLog = f.getName().substring(0, f.getName().length()-3) + "log";
                file = new File(nameTxt);
                file.delete();
                new File(ConstantProperties.TEMP_DIR + "zeta_zeros.txt").renameTo(file);
                if (!removeLastChar(ConstantProperties.TEMP_DIR + "zeta_zeros.log", nameLog)) {
                  throw new Exception("Internal Error!");
                }
                zetaZerosExpand(workUnits[i].workUnitId, workUnits[i].range, 0);
                file = new File(ConstantProperties.TEMP_DIR + "zeta_zeros.log");
                file.delete();
                new File(nameLog).renameTo(file);
                file = new File(ConstantProperties.TEMP_DIR + "zeta_zeros.txt");
                file.delete();
                new File(nameTxt).renameTo(file);
                updateData(null, ConstantProperties.TEMP_DIR + "zeta_zeros.txt", ConstantProperties.TEMP_DIR + "zeta_zeros.log", f.getName());
                return new String[0];
              }
            }
            String statisticTmpOld = statisticTmp;
            statisticTmp = statistic(statisticTmp, ConstantProperties.TEMP_DIR, false);
            int idx = statisticTmp.indexOf(';')+1;
            int idx2 = statisticTmp.indexOf(';', idx);
            if (idx2 == -1) {   // error occur
              final String[] result = { statisticTmp.substring(0, idx-1), statisticTmp.substring(idx), ConstantProperties.TEMP_DIR + "zeta_zeros.txt", ConstantProperties.TEMP_DIR + "zeta_zeros.log", f.getName(), statisticTmpOld };
              return result;
            }
            maxRange = Long.parseLong(statisticTmp.substring(idx, idx2));
            System.out.println("maxRange=" + maxRange + ", lastLine=" + statisticTmp.substring(0, idx-1));
            FileWriter writer = new FileWriter(ConstantProperties.FINAL_DIR + "/1/zeta_zeros_0_" + maxRange + ".tmp");
            writer.write(statisticTmp, 0, statisticTmp.length());
            writer.close();
            f = new File(ConstantProperties.TEMP_DIR + "zeta_zeros.txt");
            f.delete();
            f = new File(ConstantProperties.TEMP_DIR + "zeta_zeros.log");
            f.delete();

            if (maxNumberToProceed > 0 && --maxNumberToProceed == 0) return null; // terminate to generate CDs
          }
        }
      }
    }
    return null;
  }

  private static boolean checkLog(String filename) {
    BufferedReader reader = null;
    try {
      int count = 0;
      reader = new BufferedReader(new FileReader(filename));
      while (true) {
        String line = reader.readLine();
        if (line == null) {
          break;
        }
        if (line.indexOf("search3b") >= 0) {
          System.out.println(filename + " used search3b!");
          //System.exit(1);
        }
        if (line.startsWith(".... We make a shift at")) {
          System.out.println(filename + " contains shifts!");
          System.err.println(filename + " contains shifts!");
          return false;
        }
        /*if (line.startsWith("1.") && line.length() > 2) {
          try {
            Double.parseDouble(line.substring(2));
            if (++count >= 100) {
              System.out.println(filename + " contains zeros!");
              System.err.println(filename + " contains zeros!");
              return false;
            }
          } catch (NumberFormatException nfe) {
          }
        }*/
      }
    } catch (IOException ioe) {
      ThrowableHandler.handle(ioe);
    } finally {
      StreamUtils.close(reader);
    }
    return true;
  }

  private static boolean updateData(long[] sizeRange, String sourceTxt, String sourceLog, String destination) throws IOException {
    boolean found = (sizeRange != null);
    boolean foundOne = false;
    boolean change = false;
    int lines = 0;
    if (found) {
      File txt = new File("zeta_zeros_" + sizeRange[0] + '_' + sizeRange[1] + ".txt");
      System.out.println(txt.getName());
      BufferedReader reader = new BufferedReader(new FileReader(txt));
      BufferedReader reader2 = new BufferedReader(new FileReader(sourceTxt));
      BufferedWriter writer = new BufferedWriter(new FileWriter(ConstantProperties.TEMP_DIR + "tmp.txt"));
      String line = reader.readLine();
      String lineFull = line;
      String line2Full = null;
      if (line == null || line.length() == 0) {
        return false;
      }
      String prefix = "";
      String prefix2 = "";
      int idxSizeRange = 2;
      if (line.charAt(0) == '.') {
        prefix = line.substring(1);
        line = reader.readLine();
        int idx = line.indexOf('.')+1;
        lineFull = line.substring(0, idx) + prefix + line.substring(idx);
      }
      double lineValue = Double.parseDouble(lineFull.substring(lineFull.indexOf('.')+1));
      System.out.println("start line=" + lineFull);
      boolean first = true;
      found = false;
      while (true) {
        String line2 = reader2.readLine();
        if (line2 == null || line2.length() == 0) {
          break;
        }
        if (first) {
          first = false;
          if (line2.charAt(0) == '.') {
            prefix2 = line2.substring(1);
            line2 = reader2.readLine();
          }
        }
        int idx = line2.indexOf('.')+1;
        line2Full = line2.substring(0, idx) + prefix2 + repairDot(line2.substring(idx));
        if (equalLines(line2Full, lineFull) || lineValue > 0.0 && Double.parseDouble(line2Full.substring(idx)) > lineValue) {
          writer.write(lineFull);
          writer.newLine();
          ++lines;
          found = true;
          while (true) {
            line = reader.readLine();
            if (line == null) break;
            if (line.length() == 0) continue;
            idx = line.indexOf('.')+1;
            lineFull = line.substring(0, idx) + prefix + line.substring(idx);
            writer.write(lineFull);
            writer.newLine();
            change = true;
            ++lines;
          }
          lineValue = 0.0;
          double valueLineFull = Double.parseDouble(lineFull.substring(lineFull.indexOf('.')+1));
          while (line2Full != null && !equalLines(lineFull, line2Full)) {
            line2 = reader2.readLine();
            if (line2 == null) break;
            if (line2.length() == 0) throw new IOException("wrong last shift!");
            idx = line2.indexOf('.')+1;
            String sl = prefix2 + repairDot(line2.substring(idx));
            if (sl.length() == 0) continue;
            line2Full = line2.substring(0, idx) + sl;
            if (Double.parseDouble(sl) > valueLineFull) {
              System.out.println("error: " + lineFull + ',' + line2Full);
              found = false;
              break;
            }
          }
          if (found) {
            foundOne = true;
          }
          /*if (!found && idxSizeRange == sizeRange.length) {
            break;
          }*/
          System.out.println("end line=" + line2Full + ", found=" + found);
          line = null;
          lineValue = 0.0;
          while (idxSizeRange < sizeRange.length) {
            if (txt != null) {
              reader.close();
              txt.delete();
              File log = new File("zeta_zeros_" + sizeRange[idxSizeRange-2] + '_' + sizeRange[idxSizeRange-1] + ".log");
              log.delete();
            }
            idxSizeRange += 2;
            if (sizeRange[idxSizeRange-2] > 0) {
              txt = new File("zeta_zeros_" + sizeRange[idxSizeRange-2] + '_' + sizeRange[idxSizeRange-1] + ".txt");
              System.out.println(txt.getName());
              reader = new BufferedReader(new FileReader(txt));
              lineFull = line = reader.readLine();
              if (line == null || line.length() == 0) throw new IOException("internal io error!");
              if (line.charAt(0) == '.') {
                prefix = line.substring(1);
                line = reader.readLine();
                idx = line.indexOf('.')+1;
                lineFull = line.substring(0, idx) + prefix + line.substring(idx);
              } else prefix = "";
              System.out.println("start line=" + lineFull);
              lineValue = Double.parseDouble(lineFull.substring(lineFull.indexOf('.')+1));
              found = false;
              break;
            }
            txt = null;
          }
        } else {
          writer.write(line2Full);
          writer.newLine();
          ++lines;
        }
      }
      writer.close();
      reader.close();
      reader2.close();
      if (sizeRange.length > 500 && !foundOne) {
        System.out.println("Fatal error: update not successful!");
        System.exit(1);
      }
      for (int i = 0; i < sizeRange.length; i += 2) {
        txt = new File("zeta_zeros_" + sizeRange[i] + '_' + sizeRange[i+1] + ".txt");
        txt.delete();
        File log = new File("zeta_zeros_" + sizeRange[i] + '_' + sizeRange[i+1] + ".log");
        log.delete();
      }
    } else {
      File f = new File(sourceTxt);
      File txt = new File(ConstantProperties.TEMP_DIR + "tmp.txt");
      txt.delete();
      f.renameTo(txt);
      foundOne = found = true;
    }
    if (foundOne || change && lines+50 >= sizeRange[1]) {
      File f = new File(sourceTxt);
      f.delete();
      File txt = new File(ConstantProperties.TEMP_DIR + "tmp.txt");
      txt.renameTo(f);
      if (destination != null) {
        String workUnit = ConstantProperties.TEMP_DIR + destination.substring(0, destination.length()-3) + "txt";
        f = new File(workUnit);
        new File(sourceTxt).renameTo(f);
        reduceFilesize(workUnit);
        // generate a new zip file including corrections
        ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(destination));
        zipOut.setLevel(Deflater.BEST_COMPRESSION);
        zipOut.putNextEntry(new ZipEntry(destination.substring(0, destination.length()-3) + "txt"));
        StreamUtils.writeData(new FileInputStream(workUnit), zipOut, true, false);
        zipOut.putNextEntry(new ZipEntry(destination.substring(0, destination.length()-3) + "log"));
        String dir = ConstantProperties.FINAL_DIR + "/1/";
        StreamUtils.writeData(new FileInputStream(sourceLog), zipOut, true, true);
        f.delete();
        new File(dir + "tmp").mkdir();
        f = new File(dir + destination);
        f.renameTo(new File(dir + "tmp/" + destination));
        f.delete();
        new File(destination).renameTo(f);
      }
    }
    return found;
  }

  private static boolean equalLines(String line1, String line2) {
    if (line1.equals(line2)) {
      return true;
    }
    int l = line2.length();
    if (l > 2 && line1.startsWith(line2.substring(0, l-1))) {
      int i1 = line1.indexOf('.')+1;
      int i2 = line2.indexOf('.')+1;
      if (i1 > 0 && i1 == i2) {
        if (Math.abs(Double.parseDouble(line1.substring(i1))-Double.parseDouble(line2.substring(i2))) <= 0.001) {
          return true;
        }
      }
    }
    return false;
  }

  private static String fastCheck(long nStart, String[] error) {
    BufferedReader reader = null;
    try {
      int idx = 0;
      long lastN = 0;
      int lastSize = 100;
      List problems = new ArrayList(20);
      reader = new BufferedReader(new FileReader(ConstantProperties.TEMP_DIR + "zeta_zeros.log"));
      long nPos = Long.parseLong(error[1]);
      while (true) {
        String line = reader.readLine();
        if (line == null) break;
        if (line.startsWith("This happened between ") || line.startsWith("Exit n=")) {
          long n = 0;
          int i = (line.charAt(0) == 'T')? 22 : 7;
          final int l = line.length();
          while (i < l && Character.isDigit(line.charAt(i))) { n *= 10; n += Character.digit(line.charAt(i), 10); ++i; }
          if (nPos < n && lastN < nPos) {
            if (lastN+lastSize+10 < nPos) {
              lastSize = 100;
              long n2 = nPos-50;
              if (n2 < nStart) n2 = nStart;
              final Object[] o = { new Long(n2), new Integer(lastSize) };
              problems.add(o);
            } else {
              lastSize = (int)(nPos-lastN)+150;
              nPos = ((Long)((Object[])problems.get(problems.size()-1))[0]).longValue();
              final Object[] o = { new Long(nPos), new Integer(lastSize) };
              problems.add(problems.size()-1, o);
            }
            lastN = nPos;
          }
          if (lastN < n) {
            if (lastN+lastSize+10 < n) {
              lastSize = 100;
              long n2 = n-50;
              if (n2 < nStart) n2 = nStart;
              final Object[] o = { new Long(n2), new Integer(lastSize) };
              problems.add(o);
            } else {
              lastSize = (int)(n-lastN)+150;
              n = ((Long)((Object[])problems.get(problems.size()-1))[0]).longValue();
              final Object[] o = { new Long(n), new Integer(lastSize) };
              problems.set(problems.size()-1, o);
            }
          }
          lastN = n;
        }
      }
      int j = 0;
      int l = problems.size();
      if (l == 0) {
        final Object[] o = { new Long(nPos), new Integer(100) };
        problems.add(o);
        l = 1;
      }
      l *= 2;
      long[] sizeRange = new long[l];
      for (int i = 0; i < l; i += 2) {
        Object[] o = (Object[])problems.get(i/2);
        sizeRange[i]   = ((Long)o[0]).longValue();
        sizeRange[i+1] = ((Integer)o[1]).intValue();
      }
      int repeat = 0;
      do {
        for (int i = 0; i < l; i += 2) {
          long n2 = sizeRange[i]-10;
          if (n2 < nStart) n2 = nStart;
          sizeRange[i] = n2;
          sizeRange[i+1] += 20;
          zetaZerosExpand(sizeRange[i], (int)sizeRange[i+1], 0);
        }
      } while (!updateData(sizeRange, error[2], error[3], null) && ++repeat < 3);
      reader.close();
      reader = null;
      return statistic(error[5], ConstantProperties.TEMP_DIR, false);
    } catch (IOException ioe) {
      ThrowableHandler.handle(ioe);
      return error[5];
    } finally {
      StreamUtils.close(reader);
    }
  }

  private static void check(int gramBlockLength, String filename) {
    BufferedReader reader = null;
    BufferedReader reader2 = null;
    try {
      int blocks = 0;
      WorkUnit workUnit = new WorkUnit(filename);
      if (workUnit.isValid()) {
        reader = new BufferedReader(new FileReader(filename));
        while (true) {
          String line = reader.readLine();
          if (line == null) {
            break;
          }
          if (line.indexOf('.') == gramBlockLength) {
            boolean found = false;
            if (zetaZerosExpand(workUnit.workUnitId-5, 20, 0) != 0) {
              throw new IOException();
            }
            File f = new File("zeta_zeros_" + (workUnit.workUnitId-5) + "_20.txt");
            reader2 = new BufferedReader(new FileReader(f));
            while (true) {
              String line2 = reader2.readLine();
              if (line2 == null) {
                break;
              }
              if (line2.equals(line)) {
                found = true;
                break;
              }
            }
            reader2.close();
            if (!found) {
              System.out.println("Error: " + line);
              return;
            }
            f.delete();
            f = new File("zeta_zeros_" + (workUnit.workUnitId-5) + "_20.log");
            f.delete();
            for (int i = 1; i < gramBlockLength; ++i) {
              reader.readLine();
            }
            workUnit.workUnitId += gramBlockLength-1;
            ++blocks;
          }
          ++workUnit.workUnitId;
        }
        reader.close();
      }
      System.out.println(blocks + " blocks are checked.");
    } catch (IOException ioe) {
      ThrowableHandler.handle(ioe);
    } finally {
      StreamUtils.close(reader);
      StreamUtils.close(reader2);
    }
  }

  static {
    try {
      System.loadLibrary("zeta_zeros");
    } catch (Exception e) {
      ThrowableHandler.handle(e);
    }
  }

  static long[] expandFileAndCheck(String filename, WorkUnit workUnit) throws Exception {
    return expandFileAndCheck(filename, filename, workUnit);
  }

  static long[] expandFileAndCheck(String source, String destination, WorkUnit workUnit) throws Exception {
    long maxWorkUnitId = (workUnit == null)? 0 : workUnit.workUnitId+workUnit.range;
    List problems = new ArrayList(100);
    BufferedReader reader = null;
    BufferedWriter writer = null;
    boolean equal = false;
    if (source.equals(destination)) {
      equal = true;
      destination += ".tmp";
    }
    int lines = 0;
    int wrongLines = 0;
    try {
      reader = new BufferedReader(new FileReader(source));
      String line = reader.readLine();
      if (line == null || line.length() == 0) {
        throw new Exception("Internal Error!");
      }
      writer = new BufferedWriter(new FileWriter(destination));
      String prefix = "";
      String previousLineFull = null;
      if (line.charAt(0) == '.') {
        prefix = line.substring(1);
      } else {
        previousLineFull = repairDot(line);
        writer.write(previousLineFull);
        writer.newLine();
        ++lines;
      }
      int expandLines = 0;
      int plausibilityChecks = 0;
      long nStart = -1;
      long problemNStart = -1;
      long workUnitId = -1;
      double gram = 9.6669;
      double value = 0.0;
      double nextGramPoint = 0.0;
      while (true) {
        line = reader.readLine();
        if (line == null || line.length() == 0) {
          break;
        }
        if (Character.isDigit(line.charAt(line.length()-1))) {
          plausibilityChecks |= 2;
        }
        int idx = line.indexOf('.')+1;
        if (idx > 1) {
          plausibilityChecks |= 1;
        }
        String postfix = repairDot(line.substring(idx));
        if (problemNStart >= 0) {
          if (justDots(line, idx)) {
            continue;
          }
          try {
            nextGramPoint = Double.parseDouble(prefix + postfix);
          } catch (NumberFormatException nfe) {
            continue;
          }
          long nPos = Math.max(getStartN(nextGramPoint)-2, problemNStart);
          double d = nextGramPoint;
          do {
            d = Gram(++nPos, d);
//System.err.println("nextGramPoint="+nextGramPoint+", d="+d);
          } while (d < nextGramPoint);
          nextGramPoint = d;
          boolean contains = false;
          if (problems.size() > 0) {
            long[] o = (long[])problems.get(problems.size()-1);
            contains = (nPos <= o[0]+o[1] && problemNStart >= o[0]);
          }
//System.err.println("postfix="+postfix+", nPos="+nPos+", problemNStart="+problemNStart+", previousLineFull="+previousLineFull+", contains="+contains);
          if (!contains) {
            problems.add(new long[] { problemNStart, nPos-problemNStart+5 });
          }
          problemNStart = -1;
          nStart = nPos;
        }
        if (workUnitId > 0 && nStart-workUnitId > 10 && previousLineFull != null && (idx == line.length() && line.charAt(0) == '1' && idx == 2 || line.indexOf(".........................", idx) > 0)) {
          double d = Double.parseDouble(previousLineFull.substring(previousLineFull.indexOf('.')+1));
          if (value > 0.0 && d < value) {
            continue;
          }
          nStart = getStartN(d);
          problemNStart = nStart-5;
//System.err.println("postfix="+postfix+", problemNStart="+problemNStart+", previousLineFull="+previousLineFull);
          continue;
        }
        String lineFull = "";
        if (prefix.length() > 0 && (postfix.length() == 0 && nStart >= 0 || postfix.length() > 0 && postfix.charAt(0) == '.')) {
          boolean equalGram = (postfix.length() == 0);
          if (nStart >= 0 && postfix.length() > 0) {
            if (nextGramPoint > 0.0) {
//System.err.println("gram="+nextGramPoint);
              gram = nextGramPoint;
              nextGramPoint = 0.0;
            } else {
              nStart += postfix.length();
              gram = Gram(nStart, gram);
            }
          } else if (postfix.length() > 1) {
            try {
              workUnitId = nStart = Long.parseLong(postfix.substring(1));
            } catch (NumberFormatException nfe) {
              ThrowableHandler.handle(nfe);
              throw new ZipException(workUnit.toString());
            }
            gram = Gram(nStart, (nStart > 1)? 6.2831853*nStart/Math.log((double)nStart) : 9.6669);
          }
          postfix = String.valueOf((long)Math.floor((gram - Math.floor(gram))*1000.0+0.5));
          if (postfix.length() == 4) {
            if (!equalGram) {
              gram += 1.0;
            }
            postfix = String.valueOf(Integer.parseInt(postfix.substring(1)));
          }
          String s = ".";
          switch (postfix.length()) {
            case 0: s = "";
                    break;
            case 1: if (postfix.charAt(0) != '0') {
                      s = ".00";
                    } else {
                      postfix = s = "";
                    }
                    break;
            case 2: s = ".0";
                    if (postfix.charAt(1) == '0') {
                      postfix = String.valueOf(postfix.charAt(0));
                    }
                    break;
            case 3: if (postfix.charAt(2) == '0') {
                      postfix = (postfix.charAt(1) == '0')? String.valueOf(postfix.charAt(0)) : postfix.substring(0, 2);
                    }
                    break;
          }
          lineFull = line.substring(0, idx) + String.valueOf((long)Math.floor(gram)) + s + postfix;
          if (++expandLines >= 10000) {
            System.out.println("Number of lines which are continuously reduced: " + expandLines);
            throw new ZipException(workUnit.toString());
          }
        } else {
          lineFull = line.substring(0, idx) + prefix + postfix;
          expandLines = 0;
        }
        // check if a large gap occur
        double valueLine = 0.0;
        try {
          valueLine = Double.parseDouble(lineFull.substring(lineFull.indexOf('.')+1));
        } catch (NumberFormatException nfe) {
          System.out.println("format error line: " + lineFull + ", prev=" + previousLineFull);
          continue;
        }
        if (value > 0.0 && Math.abs(valueLine-value) > 20000.0 || valueLine < value /*|| lineFull.equals(previousLineFull)*/) {
//System.err.println("error valueLine="+valueLine+", previousLineFull="+previousLineFull+", lineFull="+lineFull);
          ++wrongLines;
          continue;
        }
        if (value > 0.0 && valueLine-value > 5.0) {
          if (justDots(line, idx)) {
            continue;
          }
//System.err.println("gap value="+value+", valueLine="+valueLine+", previousLineFull="+previousLineFull+", lineFull="+lineFull);
          nextGramPoint = valueLine;
          long nPos = Math.max(getStartN(nextGramPoint)-2, problemNStart);
          if (maxWorkUnitId > 0 && nPos > maxWorkUnitId) {
            nPos = maxWorkUnitId-2;
            nextGramPoint = valueLine = Gram(maxWorkUnitId, nextGramPoint);
          }
          double d = nextGramPoint;
          do {
            d = Gram(++nPos, d);
//System.err.println("nextGramPoint="+nextGramPoint+", d="+d);
          } while (d < nextGramPoint);
          nextGramPoint = d;
          long prevNPos = getStartN(value);
//System.err.println("nPos="+nPos+", prevNPos="+prevNPos+", maxWorkUnitId="+maxWorkUnitId);
          boolean contains = false;
          if (problems.size() > 0) {
            long[] o = (long[])problems.get(problems.size()-1);
            contains = (nPos <= o[0]+o[1] && prevNPos >= o[0]);
          }
          if (!contains) {
            problems.add(new long[] { prevNPos, nPos-prevNPos+5 });
          }
          nStart = nPos;
        }
        writer.write(lineFull);
        writer.newLine();
        ++lines;
        previousLineFull = lineFull;
        value = valueLine;
      }
      if (plausibilityChecks < 3) {
        System.out.println("Wrong file format: " + source);
        System.err.println("Wrong file format: " + source);
        throw new ZipException(workUnit.toString());
      }
    } catch (ZipException ze) {
      throw ze;
    } catch (IOException ioe) {
      ThrowableHandler.handle(ioe);
      throw new Exception("Internal Error!");
    } finally {
      StreamUtils.close(reader);
      try {
        if (writer != null) {
          writer.close();
        }
      } catch (IOException ioe) {
        throw new Exception("Internal Error!");
      }
    }
    if (workUnit != null && (wrongLines*5 > workUnit.range || wrongLines > 0 && lines*3 < workUnit.range)) {  // too many errors -> recomputation of the work unit
      System.out.println("Number of wrong lines: " + wrongLines + " of " + lines + " (" + workUnit.range + ')');
      throw new ZipException(workUnit.toString());
    }
    if (equal) {
      File f = new File(source);
      f.delete();
      if (/*!f.delete() ||*/ !new File(destination).renameTo(f)) {
        throw new Exception("Internal Error!");
      }
    }
    if (problems.size() > 0) {
      long[] result = new long[2*problems.size()];
      Iterator i = problems.iterator();
      for (int j = 0; i.hasNext(); j += 2) {
        long[] value = (long[])i.next();
        result[j] = value[0];
        result[j+1] = value[1];
      }
      return result;
    }
    return new long[] { lines };
  }

  private static boolean removeLastChar(String source, String destination) {
    FileInputStream in = null;
    FileOutputStream out = null;
    try {
      byte[] buffer = new byte[1000000];
      long size = new File(source).length();
      in = new FileInputStream(source);
      out = new FileOutputStream(destination);
      while (true) {
        int n = in.read(buffer);
        if (n <= 0) break;
        size -= n;
        if (size == 0) {
          out.write(buffer, 0, n-1);
          break;
        } else {
          out.write(buffer, 0, n);
        }
      }
    } catch (IOException ioe) {
      ThrowableHandler.handle(ioe);
      return false;
    } finally {
      StreamUtils.close(in);
      try {
        if (out != null) {
          out.close();
        }
      } catch (IOException ioe) {
        return false;
      }
    }
    return true;
  }

  private static boolean recomputation(long workUnitId, int range) throws IOException {
    String nameTxt = "zeta_zeros_" + workUnitId + '_'+ range + ".txt";
    String nameLog = "zeta_zeros_" + workUnitId + '_'+ range + ".log";
    String destination = "zeta_zeros_" + workUnitId + '_'+ range + ".zip";
    File fTxt = new File(nameTxt);
    File fLog = new File(nameLog);
    fTxt.delete();
    fLog.delete();
    if (zetaZeros(workUnitId, range, 0) == 0) {
      ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(destination));
      zipOut.setLevel(Deflater.BEST_COMPRESSION);
      zipOut.putNextEntry(new ZipEntry(nameTxt));
      StreamUtils.writeData(new FileInputStream(nameTxt), zipOut, true, false);
      zipOut.putNextEntry(new ZipEntry(nameLog));
      String dir = ConstantProperties.FINAL_DIR + "/1/";
      StreamUtils.writeData(new FileInputStream(nameLog), zipOut, true, true);
      fTxt.delete();
      fLog.delete();
      File f = new File(dir + "tmp");
      f.mkdir();
      f = new File(dir + destination);
      f.renameTo(new File(dir + "tmp/" + destination));
      f.delete();
      return (new File(destination).renameTo(f));
    }
    return false;
  }

  /**
   *  @return -1 if contains a duplicate zero, else number of lines
  **/
  /*private static int checkDuplicateZeros(String filename) {
    File f1 = new File(filename);
    BufferedReader reader = null;
    String prefix = "";
    int lines = 0;
    try {
      reader = new BufferedReader(new FileReader(f1));
      String line = reader.readLine();
      if (line == null || line.length() == 0) {
        return 0;
      }
      ++lines;
      String prevLine = null;
      double value = 0.0;
      if (line.charAt(0) == '.') {
        prefix = line.substring(1);
      } else {
        value = Double.parseDouble(repairDot(line.substring(line.indexOf('.')+1)));
      }
      boolean firstGram = true;
      while (true) {
        line = reader.readLine();
        if (line == null || line.length() == 0) {
          break;
        }
        ++lines;
        int idx = line.indexOf('.')+1;
        String s = line.substring(idx);
        if (firstGram && s.length() > 0 && s.charAt(0) == '.') {
          firstGram = false;
          continue;
        }
        if (justDots(s)) {
          continue;
        }
        if (isRepairDot(s) || s.length() == 0 || idx == 1) {
          return -1;
        }
        if (prevLine != null && prevLine.length() < line.length()) {
          // e.g. zeta_zeros_2650377500_500000.zip:
          // error line: 1.934934785112.559, prev=1.934785112.559
          int i = prevLine.indexOf('.')+1;
          int i2 = prevLine.indexOf('.', i);
          int i3 = s.indexOf('.');
          if (i2 >= 0 && i2-i+1 < i3) {
            return -1;
          }
        }
        double valueLine = 0.0;
        try {
          valueLine = Double.parseDouble(s);
        } catch (NumberFormatException nfe) {
          return -1;
        }
        if (valueLine < value) {
          return -1;
        }
        if (line != null && !line.equals(prevLine)) {
          if (value > 0.0 && valueLine-value > 5.0) {
            return -1;
          }
          prevLine = line;
          value = valueLine;
        }
      }
    } catch (IOException ioe) {
      ThrowableHandler.handle(ioe);
    } finally {
      StreamUtils.close(reader);
    }
    return lines;
  }

  private static boolean removeDuplicateZeros(String filename) {
    boolean change = false;
    boolean largeGap = false;
    File f1 = new File(filename);
    File f2 = new File(ConstantProperties.TEMP_DIR + "tmp.txt");
    BufferedReader reader = null;
    BufferedWriter writer = null;
    String prefix = "";
    long f1Length = f1.length();
    try {
      int numberOfNewLineChars = 0;
      FileInputStream fileIn = new FileInputStream(filename);
      byte[] buffer = new byte[50];
      if (fileIn.read(buffer) >= 0) {
        for (int i = 0; i < buffer.length; ++i) {
          if (buffer[i] == '\n') {
            numberOfNewLineChars |= 1;
            if (numberOfNewLineChars == 3) break;
          }
          if (buffer[i] == '\r') {
            numberOfNewLineChars |= 2;
            if (numberOfNewLineChars == 3) break;
          }
        }
        if (numberOfNewLineChars >= 2) --numberOfNewLineChars;
      }
      fileIn.close();
      reader = new BufferedReader(new FileReader(filename));
      String line = reader.readLine();
      if (line == null || line.length() == 0) return false;
      f1Length -= line.length() + numberOfNewLineChars;
      writer = new BufferedWriter(new FileWriter(f2));
      String prevLine = null;
      double value = 0.0;
      if (line.charAt(0) == '.') {
        prefix = line.substring(1);
      } else {
        int idx = line.indexOf('.')+1;
        String s = repairDot(line.substring(idx));
        value = Double.parseDouble(s);
        line = line.substring(0, idx) + s;
        writer.write(line, 0, line.length());
        writer.newLine();
      }
      while (true) {
        line = reader.readLine();
        if (line == null || line.length() == 0) break;
        f1Length -= line.length() + numberOfNewLineChars;
        int idx = line.indexOf('.')+1;
        String s = line.substring(idx);
        if (isRepairDot(s)) {
          s = repairDot(s);
          change = true;
        }
        if (s.length() == 0 || idx == 1) {
          change = true;
          System.out.println("error line: " + line);
          continue;
        }
        if (prevLine != null && prevLine.length() < line.length()) {
          // e.g. zeta_zeros_2650377500_500000.zip:
          // error line: 1.934934785112.559, prev=1.934785112.559
          int i = prevLine.indexOf('.')+1;
          int i2 = prevLine.indexOf('.', i);
          int i3 = s.indexOf('.');
          if (i2 >= 0 && i2-i+1 < i3) {
            change = true;
            System.out.println("error line: " + line + ", prev=" + prevLine);
            continue;
          }
        }
        double valueLine = 0.0;
        try {
          valueLine = Double.parseDouble(s);
        } catch (NumberFormatException nfe) {
          change = true;
          System.out.println("format error line: " + line + ", prev=" + prevLine);
          continue;
        }
        String lineFull = line.substring(0, idx) + prefix + s;
        if (valueLine < value) {
          change = true;
          System.out.println("duplicate zeros: " + valueLine + " after " + value);
          do {
            line = reader.readLine();
            if (line == null) break;
            f1Length -= line.length() + numberOfNewLineChars;
            idx = line.indexOf('.')+1;
            s = repairDot(line.substring(idx));
            try {
              valueLine = Double.parseDouble(s);
              if (valueLine > value) {
                lineFull = line.substring(0, idx) + prefix + s;
                break;
              }
            } catch (NumberFormatException nfe) {
              System.out.println("format error line: " + line + ", prev=" + prevLine);
            }
          } while (!line.equals(prevLine));
          if (line != null && !line.equals(prevLine)) System.out.println("next " + line);
        } 
        if (line != null && !line.equals(prevLine)) {
          if (value > 0.0 && valueLine-value > 5.0) {
            change = true;
            if (f1Length < 10) continue;
            largeGap = true;
          }
          writer.write(lineFull);
          writer.newLine();
          prevLine = line;
          value = valueLine;
        }
      }
    } catch (IOException ioe) {
      change = false;
      ThrowableHandler.handle(ioe);
    } finally {
      StreamUtils.close(reader);
      StreamUtils.close(writer);
    }
    if (change) {
      double[] largeGapAt = new double[10];
      int idxLargeGapAt = 0;
      if (largeGap) {  // large gap
        System.out.println("contains large gap");
        reader = null;
        writer = null;
        BufferedReader reader2 = null;
        try {
          File f3 = new File(ConstantProperties.TEMP_DIR + "tmp2.txt");
          reader = new BufferedReader(new FileReader(f2));
          writer = new BufferedWriter(new FileWriter(f3));
          double value = 0.0;
          while (true) {
            String line = reader.readLine();
            if (line == null || line.length() == 0) {
              break;
            }
            try {
              double valueLine = Double.parseDouble(repairDot(line.substring(line.indexOf('.')+1)));
              if (value > 0.0 && valueLine-value > 5.0) {
                double originValue = value;
                System.out.println("large gap beginning at " + value);
                reader2 = new BufferedReader(new FileReader(f1));
                while (true) {
                  String line2 = reader2.readLine();
                  if (line2 == null || line2.length() == 0) {
                    break;
                  }
                  try {
                    int idx = line2.indexOf('.')+1;
                    if (idx == 1) continue;
                    String s = repairDot(prefix + line2.substring(idx));
                    double valueLine2 = Double.parseDouble(s);
                    if (valueLine2 > value && valueLine2 < valueLine) {
                      writer.write(line2.substring(0, idx));
                      writer.write(s);
                      writer.newLine();
                      value = valueLine2;
                    }
                  } catch (NumberFormatException nf) {
                    System.out.println("format error line2: " + line2);
                  }
                }
                reader2.close();
                reader2 = null;
                if (value == originValue) {
                  if (idxLargeGapAt < largeGapAt.length) {
                    largeGapAt[idxLargeGapAt++] = value;
                  }
                  System.out.println("gap cannot be filled up!");
                } else {
                  System.out.println("gap filled up to " + value);
                }
              }
              writer.write(line);
              writer.newLine();
              value = valueLine;
            } catch (NumberFormatException nfe) {
              System.out.println("format error line: " + line);
            }
          }
          writer.close();
          writer = null;
          reader.close();
          reader = null;
          if (!f2.delete() || !f3.renameTo(f2)) {
            System.err.println("Fatal error in removeDuplicateZeros!");
            System.exit(1);
          }
        } catch (IOException ioe) {
          ThrowableHandler.handle(ioe);
        } finally {
          StreamUtils.close(reader);
          StreamUtils.close(reader2);
          StreamUtils.close(writer);
        }
      }
      if (!f1.delete() || !f2.renameTo(f1)) {
        System.err.println("Fatal error in removeDuplicateZeros!");
        System.exit(1);
      }
      if (idxLargeGapAt > 0) {
        for (int i = 0; i < idxLargeGapAt; ++i) {
          long[] sizeRange = new long[2];
          sizeRange[0] = getStartN(largeGapAt[i])-100;
          sizeRange[1] = 200;
          zetaZerosExpand(sizeRange[0], (int)sizeRange[1], 0);
          try {
            updateData(sizeRange, filename, null, null);
          } catch (IOException ioe) {
            ThrowableHandler.handle(ioe);
            return false;
          }
        }
      }
    }
    return change;
  }

  private static boolean isRepairDot(String s) {
    final int i = s.indexOf(',');
    return (i >= 0 && s.indexOf(',', i+1) < 0 && s.indexOf('.') < 0);
  }*/

  private static String repairDot(String s) {
    final int i = s.indexOf(',');
    if (i >= 0 && s.indexOf(',', i+1) < 0 && s.indexOf('.') < 0) {
      StringBuffer buffer = new StringBuffer(s);
      buffer.setCharAt(i, '.');
      s = buffer.toString();
    }
    return s;
  }

  private static boolean justDots(String s, int i) {
    final int l = s.length();
    for (; i < l; ++i) {
      if (s.charAt(i) != '.') {
        return false;
      }
    }
    return true;
  }

  static double Gram(long n) {
    return Gram(n, (n > 1)? 6.2831853*n/Math.log((double)n) : 9.6669);
  }


  final static double PI = 3.1415926535897932384626433832795028841971693993751;
  final static double PI_INV = 1.0/PI;
  final static double TWO_PI = 2*PI;
  final static double TWO_PI_INV = 0.5*PI_INV;
  static double Gram(long n, double a) { // Using a as an initial approximation the nth Gram point is calculated and assigned to b.
    double t2,t1 = a*TWO_PI_INV;
    double d = ((double)n) + 0.125;
    do {
      t2 = (t1+d)/Math.log(t1);
      if (Math.abs(t1-t2) < t2*1e-13) return t2*TWO_PI;
      t1 = (t2+d)/Math.log(t2);
    } while (Math.abs(t1-t2) >= t1*1e-13);
    return t1*TWO_PI;
  }

  static long getStartN(double t) {
    long n2,n1 = (long)(t*0.83675);
    double g2,g1 = Gram(n1);
    if (g1 > t) {
      do {
        n2 = n1; g2 = g1;
        n1 >>= 1;
        g1 = Gram(n1);
      } while (g1 > t);
    } else {
      n2 = n1; g2 = g1;
      while (true) {
        n2 <<= 1;
        g2 = Gram(n2);
        if (g2 > t) break;
        g1 = g2; n1 = n2;
      }
    }
    while (n2-n1 > 1) {
      long n = (n1+n2) >> 1;
      double g = Gram(n);
      if (g > t) n2 = n;
      else n1 = n;
    }
    return n1;
  }

  static int zetaZerosExpand(long workUnitId, int range, int sleep) {
    int result = zetaZeros(workUnitId, range, sleep);
    try {
      expandFileAndCheck("zeta_zeros_" + workUnitId + '_' + range + ".txt", new WorkUnit(workUnitId, range));
      if (StreamUtils.search(new String[] { "... Close pair of zeros between" }, new FileInputStream("zeta_zeros_" + workUnitId + '_' + range + ".log"), true)) {
        System.out.println("Close zero in 'zeta_zeros_" + workUnitId + '_' + range + ".log'");
        System.exit(1); // ToDo: transfer this line to the master log file
      }
    } catch (Exception e) {
      ThrowableHandler.handle(e);
      System.exit(1);
    }
    return result;
  }

  private static boolean reduceFilesize2(String filename) {
    return reduceFilesize2(filename, filename);
  }

  private static boolean reduceFilesize2(String filename1, String filename2) {
    /*BufferedReader reader = null;
    boolean expand = false;
    try {
      reader = new BufferedReader(new FileReader(filename1));
      String line = reader.readLine();
      if (line == null || line.length() == 0) return false;
      if (line.charAt(0) == '.') {
        for (int i = 0; i < 40; ++i) {
          line = reader.readLine();
          if (line == null) break;
          if (line.indexOf("..") >= 0) return false;
        }
        expand = true;
      }
    } catch (IOException ioe) {
      ThrowableHandler.handle(ioe);
      return false;
    } finally {
      StreamUtils.close(reader);
    }
    if (expand) {
      try {
        expandFileAndCheck(filename1);
      } catch (Exception e) {
      }
    }*/
    if (!filename1.equals(filename2)) {
      File f1 = new File(filename1);
      File f2 = new File(filename2);
      f2.delete();
      if (!f1.renameTo(f2)) return false;
      boolean result = reduceFilesize(filename2);
      return (f2.renameTo(f1) && result);
    }
    return reduceFilesize(filename1);
  }


  private static int previousNumberProblems = 0;
  private static long previousFirstProblem = 0;
  private static long previousWorkUnitId = 0;
  private static int maxNumberToProceed = -1;
  private static String lastTmpFilename = "";
  private static CheckConsistencyZetaZeros checkHeader = new CheckConsistencyZetaZeros();

  static native boolean reduceFilesize(String filename);
  private static native String statistic(String tmpEntry, String directory, boolean removeLine);
  private static native void verify(String smallFile, String largeFile);
  static native int zetaZeros(long start, int range, int sleepN);

  private static class WorkUnit implements Comparable {
    int range;
    long workUnitId = -1;

    public WorkUnit(long workUnitId, int range) {
      this.workUnitId = workUnitId;
      this.range = range;
    }

    public WorkUnit(String text) {
      if (text != null) {
        int l = text.length();
        if (l > 11 && text.startsWith("zeta_zeros_") && Character.isDigit(text.charAt(11))) {
          int idx = 11;
          while (++idx < l && Character.isDigit(text.charAt(idx)));
          if (idx+1 < l && text.charAt(idx) == '_' && Character.isDigit(text.charAt(idx+1))) {
            int idx2 = ++idx;
            while (++idx2 < l && Character.isDigit(text.charAt(idx2)));
            range = Integer.parseInt(text.substring(idx, idx2));
            workUnitId = Long.parseLong(text.substring(11, idx-1));
          }
        }
      }
    }

    public int compareTo(Object o) {
      WorkUnit w = (WorkUnit)o;
      if (workUnitId < w.workUnitId) {
        return -1;
      } else if (workUnitId > w.workUnitId) {
        return 1;
      } else if (range < w.range) {
        return 1;
      } else if (range > w.range) {
        return -1;
      } else {
        return 0;
      }
    }

    public boolean isValid() {
      return (workUnitId >= 0);
    }

    public String toString() {
      return "zeta_zeros_" + workUnitId + '_' + range;
    }
  }
}