/*
 * Copyright 2010-2021 Australian Signals Directorate
 *
 * 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 au.gov.asd.tac.constellation.utilities.file;

/**
 * Manage the encode/decode of strings into filenames. This essentially ensures
 * the characters in the filename are within a valid set.
 *
 * @author serpens24
 */
public class FilenameEncoder {

    /**
     * Private constructor to hide implicit public one.
     */
    private FilenameEncoder() {
        throw new IllegalStateException("Invalid call to private default constructor");
    }

    /**
     * Encode a String so it can be used as a filename. This method will
     * essentially limit the characters that can be used in the filename, all
     * other characters are escaped.
     *
     * @param s The String to be encoded.
     *
     * @return The encoded String.
     */
    public static String encode(final String s) {
        final StringBuilder b = new StringBuilder();
        for (final char c : s.toCharArray()) {
            if (isValidFileCharacter(c)) {
                b.append(c);
            } else {
                b.append(String.format("_%04x", (int) c));
            }
        }
        return b.toString();
    }

    /**
     * Decode a String that has been encoded by {@link encode(String)}.
     *
     * @param s The String to be decoded.
     *
     * @return The decoded String.
     */
    public static String decode(final String s) {
        final StringBuilder b = new StringBuilder();

        // Loop through characters in source string. The normal case just adds
        // the next character to the destination string, but if a '_ is found,
        // it is treated asa marker which beigns a 4 character hex code - used
        // by the encode method to represent characters identified as non
        // printable.
        int i = 0;
        while (i < s.length()) {
            final char c = s.charAt(i);
            if (c != '_') {
                b.append(c);
                i++;
            } else {
                // An '_' was found, try to nibble off the next 4 characters
                final String hex = s.substring(i + 1, Math.min(i + 5, s.length()));
                if (hex.length() == 4) {
                    // A full 4 characters could be nibbled off, which is expected
                    // as all filenames generated by the encode method will add
                    // this. So the 'bad' case would represent a filename added
                    // to the config directory manually by a user.
                    try {
                        final int value = Integer.parseInt(hex, 16);
                        b.append((char) value);
                        i += 5; // Added 1 for the '_' and 4 for the digits
                    } catch (final NumberFormatException ex) {
                        // Hex string couldnt be converted to int
                        return null;
                    }
                } else {
                    // Hex string couldn't be extracted
                    return null;
                }
            }
        }
        return b.toString();
    }

    /**
     * *
     * Returns true if the supplied character c is a valid character to use in a
     * filename.
     *
     * @param c The character to test
     * @return True if the character is able to be used in a filename.
     */
    public static boolean isValidFileCharacter(char c) {
        return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
                || (c >= 'a' && c <= 'z') || c == ' '
                || c == '-' || c == '.';
    }
}
