/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.internal.io;

import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import java.util.regex.Pattern;

import org.apache.geode.LogWriter;

/**
 * Extracted from ManagerLogWriter. MainWithChildrenRollingFileHandler is used for both rolling of
 * log files and stat archive files.
 */
public class MainWithChildrenRollingFileHandler implements RollingFileHandler {

  private static final Pattern MAIN_ID_PATTERN = Pattern.compile(".+-\\d+-\\d+\\..+");
  private static final Pattern META_ID_PATTERN = Pattern.compile("meta-.+-\\d+\\..+");
  private static final Pattern CHILD_ID_PATTERN = Pattern.compile(".+-\\d+-\\d+\\..+");

  public MainWithChildrenRollingFileHandler() {}

  @Override
  public int calcNextChildId(final File file, final int mainId) {
    int result = 0;
    File dir = getParentFile(file.getAbsoluteFile());
    int endIdx1 = file.getName().indexOf('-');
    int endIdx2 = file.getName().lastIndexOf('.');
    String baseName;
    if (endIdx1 != -1) {
      baseName = file.getName().substring(0, endIdx1);
    } else if (endIdx2 != -1) {
      baseName = file.getName().substring(0, endIdx2);
    } else {
      baseName = file.getName();
    }

    File[] children = findChildren(dir, CHILD_ID_PATTERN);

    /* Search child logs */

    for (File child : children) {
      String name = child.getName();
      // only compare the child id among the same set of files.
      if (!name.startsWith(baseName)) {
        continue;
      }
      int endIdIdx = name.lastIndexOf('-');
      int startIdIdx = name.lastIndexOf('-', endIdIdx - 1);
      String id = name.substring(startIdIdx + 1, endIdIdx);

      int startChild = name.lastIndexOf("-");
      int endChild = name.lastIndexOf(".");
      if (startChild > 0 && endChild > 0) {
        String childId = name.substring(startChild + 1, endChild);

        try {
          int mainLogId = Integer.parseInt(id);
          int childLogId = Integer.parseInt(childId);
          if (mainLogId == mainId && childLogId > result) {
            result = childLogId;
          }
        } catch (NumberFormatException ignore) {
        }
      }
    }
    result++;
    return result;
  }

  @Override
  public int calcNextMainId(final File dir, final boolean toCreateNew) {
    int result = 0;
    File[] children = findChildren(dir, MAIN_ID_PATTERN);

    /* Search child logs */
    for (File child : children) {
      String name = child.getName();
      int endIdIdx = name.lastIndexOf('-');
      int startIdIdx = name.lastIndexOf('-', endIdIdx - 1);
      String id = name.substring(startIdIdx + 1, endIdIdx);
      try {
        int mid = Integer.parseInt(id);
        if (mid > result) {
          result = mid;
        }
      } catch (NumberFormatException ignore) {
      }
    }

    /* And search meta logs */
    if (toCreateNew) {
      File[] metaFiles = findChildren(dir, META_ID_PATTERN);
      for (File metaFile : metaFiles) {
        String name = metaFile.getName();
        int endIdIdx = name.lastIndexOf('.');
        int startIdIdx = name.lastIndexOf('-', endIdIdx - 1);
        String id = name.substring(startIdIdx + 1, endIdIdx);
        try {
          int mid = Integer.parseInt(id);
          if (mid > result) {
            result = mid;
          }
        } catch (NumberFormatException ignore) {
        }
      }
      result++;
    }

    return result;
  }

  @Override
  public void checkDiskSpace(final String type, final File newFile, final long spaceLimit,
      final File dir, final LogWriter logger) {
    checkDiskSpace(type, newFile, spaceLimit, dir, getFilePattern(newFile.getName()), logger);
  }

  private void checkDiskSpace(final String type, final File newFile, final long spaceLimit,
      final File dir, final Pattern pattern, final LogWriter logger) {
    if (spaceLimit == 0 || pattern == null) {
      return;
    }
    File[] children = findChildrenExcept(dir, pattern, newFile);
    LogWriter logWriter = logger;
    if (children == null) {
      if (dir.isDirectory()) {
        logWriter.warning(
            String.format(
                "Could not check disk space on %s because java.io.File.listFiles returned null. This could be caused by a lack of file descriptors.",
                dir));
      }
      return;
    }
    Arrays.sort(children, (Comparator) (o1, o2) -> {
      File f1 = (File) o1;
      File f2 = (File) o2;
      long diff = f1.lastModified() - f2.lastModified();
      if (diff < 0) {
        return -1;
      } else if (diff > 0) {
        return 1;
      } else {
        return 0;
      }
    });
    long spaceUsed = 0;
    for (File child : children) {
      spaceUsed += child.length();
    }
    int idx = 0;
    while (spaceUsed >= spaceLimit && idx < children.length) { // check array index to 37388
      long childSize = children[idx].length();
      if (delete(children[idx])) {
        spaceUsed -= childSize;
        logWriter.info(String.format("Deleted inactive %s %s.",
            type, children[idx]));
      } else {
        logWriter.warning(String.format("Could not delete inactive %s %s.",
            type, children[idx]));
      }
      idx++;
    }
    if (spaceUsed > spaceLimit) {
      logWriter.warning(
          String.format(
              "Could not free space in %s directory.  The space used is %s which exceeds the configured limit of %s.",
              type, spaceUsed, spaceLimit));
    }
  }

  protected boolean delete(final File file) {
    return file.delete();
  }

  @Override
  public String formatId(final int id) {
    StringBuilder result = new StringBuilder(10);
    result.append('-');
    if (id < 10) {
      result.append('0');
    }
    result.append(id);
    return result.toString();
  }

  @Override
  public File getParentFile(final File file) {
    File tmp = file.getAbsoluteFile().getParentFile();
    if (tmp == null) {
      tmp = new File("."); // as a fix for bug #41474 we use "." if getParentFile returns null
    }
    return tmp;
  }

  Pattern getFilePattern(String name) {
    if (name == null || "".equals(name.trim())) {
      throw new IllegalArgumentException("Name must not be empty");
    }

    int extIdx = name.lastIndexOf('.');
    String ext = "";
    if (extIdx != -1) {
      ext = "\\Q" + name.substring(extIdx) + "\\E";
      name = name.substring(0, extIdx);
    }
    name = "\\Q" + name + "\\E" + "-\\d+-\\d+" + ext;
    return Pattern.compile(name);
  }

  private File[] findChildren(final File dir, final Pattern pattern) {
    return dir.listFiles((dir1, name) -> pattern.matcher(name).matches());
  }

  private File[] findChildrenExcept(final File dir, final Pattern pattern, final File exception) {
    final String exceptionName = (exception == null) ? null : exception.getName();
    if (dir == null) {
      return new File[] {};
    }
    return dir
        .listFiles((dir1, name) -> !name.equals(exceptionName) && pattern.matcher(name).matches());
  }

}
