package %%%PACKAGE%%%;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.Set;

import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

final class TranslationHelper {

    private TranslationHelper() {}

    private static ProgramData programData = null;

    public static Random random = new Random();

    public static void setProgramData(ProgramData pd) {
        programData = pd;
    }

    public static ProgramData getProgramData() {
        return programData;
    }

    public static boolean isValidInteger(String value) {
        try {
            Integer.parseInt(value);
        } catch (NumberFormatException nfe) {
            return false;
        }
        return true;
    }

    private static final char[] BUFFER = new char[500]; 
    public static String readFileText(String path) {
        FileReader fileReader;
        try {
            fileReader = new FileReader(path);
        } catch (FileNotFoundException e) {
            return null;
        }

        BufferedReader br = new BufferedReader(fileReader);
        StringBuilder sb = new StringBuilder();
        int bytesRead = 0;
        while (bytesRead != -1) {
            try {
                bytesRead = br.read(BUFFER);
            } catch (IOException e) {
                bytesRead = -1;
            }

            if (bytesRead != -1) {
                sb.append(BUFFER, 0, bytesRead);
            }
        }

        try {
            br.close();
        } catch (IOException e) {
        }

        return sb.toString();
    }

    public static String getRawByteCodeString() {
        return readFileText("data/bytecode.txt");
    }

    public static String getTextResource(String path) {
        return readFileText("resources/" + path);
    }

    public static <T> void reverseList(ArrayList<T> list) {
        int length = list.size();
        if (length < 2) return;
        int lengthMinusOne = length - 1;
        for (int i = length / 2 - 1; i >= 0; --i) {
            T t = list.get(i);
            int tIndex = lengthMinusOne - i;
            list.set(i, list.get(tIndex));
            list.set(tIndex, t);
        }
    }

    public static String joinList(String sep, ArrayList<String> items) {
        int length = items.size();
        if (length < 2) {
            if (length == 0) return "";
            return items.get(0);
        }

        boolean useSeparator = sep.length() > 0;
        StringBuilder sb = new StringBuilder(useSeparator ? (length * 2 - 1) : length);
        sb.append(items.get(0));
        if (useSeparator) {
            for (int i = 1; i < length; ++i) {
                sb.append(sep);
                sb.append(items.get(i));
            }
        } else {
            for (int i = 1; i < length; ++i) {
                sb.append(items.get(i));
            }
        }

        return sb.toString();
    }

    public static String joinChars(ArrayList<Character> chars) {
        char[] output = new char[chars.size()];
        for (int i = output.length - 1; i >= 0; --i) {
            output[i] = chars.get(i);
        }
        return String.copyValueOf(output);
    }

    public static String reverseString(String original) {
        char[] output = original.toCharArray();
        int length = output.length;
        int lengthMinusOne = length - 1;
        char c;
        for (int i = length / 2 - 1; i >= 0; --i) {
            c = output[i];
            output[i] = output[lengthMinusOne - i];
            output[lengthMinusOne] = c;
        }
        return String.copyValueOf(output);
    }

    public static void shuffleInPlace(ArrayList<Value> list) {
        int length = list.size();
        int tIndex;
        Value t;
        for (int i = length - 1; i >= 0; --i) {
            tIndex = random.nextInt(length);
            t = list.get(tIndex);
            list.set(tIndex, list.get(i));
            list.set(i, t);
        }
    }

    public static int[] convertIntegerSetToArray(Set<Integer> original) {
        int[] output = new int[original.size()];
        int i = 0;
        for (int value : original) {
            output[i++] = value;
        }
        return output;
    }

    public static String[] convertStringSetToArray(Set<String> original) {
        String[] output = new String[original.size()];
        int i = 0;
        for (String value : original) {
            output[i++] = value;
        }
        return output;
    }

    public static ArrayList<Value> concatLists(ArrayList<Value> listA, ArrayList<Value> listB) {
        ArrayList<Value> output = new ArrayList<Value>(listA.size() + listB.size());
        output.addAll(listA);
        output.addAll(listB);
        return output;
    }

    public static ArrayList<Value> multiplyList(ArrayList<Value> list, int num) {
        ArrayList<Value> output = new ArrayList<Value>(list.size() * num);
        while (num-- > 0) {
            output.addAll(list);
        }
        return output;
    }

    public static int[] createIntArray(ArrayList<Integer> nums) {
        int[] output = new int[nums.size()];
        for (int i = nums.size() - 1; i >= 0; --i) {
            output[i] = nums.get(i);
        }
        return output;
    }

    public static void assertion(String message) {
        throw new IllegalStateException(message);
    }

    public static void sortPrimitiveValueList(ArrayList<Value> items, boolean isString) {
        throw new IllegalStateException("Not implemented yet.");
    }

    public static int[] sortedCopyOfIntArray(int[] nums) {
        int[] output = Arrays.copyOf(nums, nums.length);
        Arrays.sort(output);
        return output;
    }

    public static Object flushImagetteToBitmap(Imagette imagette) {
        int width = imagette.width;
        int height = imagette.height;
        BufferedImage finalImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics g = finalImage.getGraphics();
        int length = imagette.nativeBitmaps.size();
        for (int i = 0; i < length; ++i) {
            BufferedImage img = (BufferedImage) imagette.nativeBitmaps.get(i);
            int x = imagette.xs.get(i);
            int y = imagette.ys.get(i);
            g.drawImage(img, x, y, null);
        }
        g.dispose();
        return finalImage;
    }

    public static BufferedImage flipImage(Object original, boolean flipX, boolean flipY) {
        BufferedImage originalImage = (BufferedImage) original;
        if (!flipX && !flipY) return originalImage;
        BufferedImage flippedImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = flippedImage.createGraphics();
        AffineTransform at = new AffineTransform();
        at.concatenate(AffineTransform.getScaleInstance(flipX ? -1 : 1, flipY ? -1 : 1));
        at.concatenate(AffineTransform.getTranslateInstance(flipX ? originalImage.getWidth() : 0, flipY ? originalImage.getHeight() : 0));
        g.transform(at);
        g.drawImage(originalImage, 0, 0, null);
        g.dispose();
        return flippedImage;
    }

    public static BufferedImage loadImageFromLocalFile(String path) {
        try {
            return ImageIO.read(new File("resources/" + path));
        } catch (IOException e) {
            return null;
        }
    }

    // TODO: playSound ought to return a string with an error message, should one occur.
    public static void playSoundImpl(Object soundObject) {
        SoundInstance sound = (SoundInstance) soundObject;
        InputStream is = new ByteArrayInputStream((byte[]) sound.nativeObject);
        try {
            Clip clip = AudioSystem.getClip();
            AudioInputStream stream = AudioSystem.getAudioInputStream(is);
            clip.open(stream);
            clip.loop(0);
        } catch (LineUnavailableException e) {
            throw new RuntimeException("Error");
        } catch (UnsupportedAudioFileException e) {
            throw new RuntimeException("Unsupported format");
        } catch (IOException e) {
            throw new RuntimeException("Error");
        }
    }

    private static byte[] readLocalResource(String path) {
        try {
            return readLocalResourceImpl(path);
        } catch (IOException e) {
            return null;
        }
    }

    private static byte[] readLocalResourceImpl(String path) throws IOException {
        File resource = new File("resources/" + path);
        if (!resource.exists()) {
            return null;
        }
        byte[] output = new byte[(int)resource.length()];

        FileInputStream fis = null;
        try {
            fis = new FileInputStream(resource);
            int offset = 0;
            do {
                offset += fis.read(output, offset, output.length - offset);
            } while (offset < output.length);
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
        return output;
    }

    public static SoundInstance readLocalSoundResource(String path) {
        byte[] snd = readLocalResource(path);
        if (snd == null) {
            return null;
        }
        return new SoundInstance(snd, false, -1, 0.0);
    }

    public static BufferedImage readLocalTileResource(String generatedTileId) {
        return loadImageFromLocalFile("generated/spritesheets/" + generatedTileId + ".png");
    }
    
    public static String[] literalStringSplit(String original, String sep) {
        ArrayList<String> output = new ArrayList<>();
        ArrayList<String> currentPiece = new ArrayList<>();
        int length = original.length();
        int sepLength = sep.length();
        char firstSepChar = sep.charAt(0);
        char c;
        int j;
        boolean match;
        for (int i = 0; i < length; ++i) {
            c = original.charAt(i);
            match = false;
            if (c == firstSepChar) {
                match = true;
                for (j = 1; j < sepLength; ++j) {
                    if (i + j < length ) {
                        if (sep.charAt(j) != original.charAt(i + j)) {
                            match = false;
                            break;
                        }
                    } else {
                        match = false;
                    }
                }
            }

            if (match) {
                output.add(String.join("", currentPiece));
                currentPiece.clear();
                i += sepLength - 1;
            } else {
                currentPiece.add("" + c);
            }
        }
        output.add(String.join("", currentPiece));
        return output.toArray(new String[output.size()]);
    }

    public static String[] directoryListing(String path) {
        File folder = new File(path);
        ArrayList<String> output = new ArrayList<>();
        for (File file : folder.listFiles()) {
            output.add(file.getName());
        }
        return output.toArray(new String[output.size()]);
    }

    private static boolean isWindowsCached = System.getProperty("os.name").startsWith("Windows");
    public static boolean isWindows() {
        return isWindowsCached;
    }

    public static String readFile(String path) {
        try {
            byte[] bytes = Files.readAllBytes(Paths.get(path));
            return new String(bytes);
        } catch (IOException e) {
            return null;
        }
    }

    public static int writeFile(String path, String contents) {
        byte[] bytes = contents.getBytes();
        try {
            FileOutputStream fos = new FileOutputStream(path);
            fos.write(bytes);
            fos.close();
            return 0;
        } catch (IOException e) {
            // TODO: test with errors 1-3
            return 4;
        }
    }

    public static boolean checkPathExistence(String path, boolean dirOnly, boolean checkCase) {
        Path p = Paths.get(path);
        return dirOnly ? Files.isDirectory(p) : Files.exists(p);
    }
}
