package fr.free.gloumouth1;

import static javax.imageio.ImageIO.read;

import java.awt.Component;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import javax.imageio.ImageIO;

public class Twist extends Component {

	private static boolean hasParameter(String[] args, String parameter, String parameterShortName) {
		for(int i=0; i < args.length; i++)
			if (args[i].equals(parameter) || args[i].equals(parameterShortName)) return true;
		return false;
	}
	
	private static String getArgument(String[] args, String parameter, String parameterShortName) {
		for(int i=0; i < args.length; i++)
			if ((args[i].equals(parameter) || args[i].equals(parameterShortName)) && (i + 1 < args.length)) return args[i + 1];
		return "";
	}
	
	private static String[] getArguments(String[] args, String parameter, String parameterShortName, String delimiter) {
		for(int i=0; i < args.length; i++)
			if ((args[i].equals(parameter) || args[i].equals(parameterShortName)) && (i + 1 < args.length)) return args[i + 1].split(delimiter);
		return new String[0];
	}

	public static void main(String[] args) {
		new Twist(args);
	}

	private int SIZE_PROGRESS_BAR = 50;
	private int SIZE_X;
	private int SIZE_Y;
	private int SIZE_F = 600;
	private static int SIZE_F_MAX = 1120;
	private byte[][][][] voxels;
	private File FFMPEG_DIRECTORY = new File(".");
	private File INPUT = new File("");
	private File OUTPUT = new File("twisted.mp4");
	private int indexImage = 0;
	private static boolean IS_INPUT = true;
	private static boolean IS_OUTPUT = false;
	private boolean KEEP_INPUT = false;
	private boolean KEEP_OUTPUT = false;
	private LocalDateTime startProgressBar = null;
	private LocalDateTime startTwist = LocalDateTime.now();
    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");

	public static String getFormatedDuration(Duration duration) {
	    long seconds = duration.getSeconds();
	    return String.format("%02d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, seconds % 60);
	}
	
	private void printProgressBar(int progress, int complete, int sizeProgressBar) {
		if (progress == 0) {
			startProgressBar = LocalDateTime.now();
			for (int i = 0; i < sizeProgressBar; i++) System.out.print("_");
			System.out.println(" T-" + startProgressBar.format(formatter));
		}
		for(int i = 0; i < Math.floor(((double) (progress + 1) / (complete - 1)) * sizeProgressBar) / (double) sizeProgressBar - Math.floor(((double) (progress) / (complete - 1)) * sizeProgressBar) / (double) sizeProgressBar; i++)
			System.out.print("#");
		if (progress == complete - 1) System.out.println(" D-" + getFormatedDuration(Duration.between(startProgressBar, LocalDateTime.now())) + "\n");
	}

	private File getInputFile(int imageIndex) {
		return new File(INPUT.isDirectory() ? INPUT : INPUT.getParentFile(), "i" + String.format("%04d", imageIndex) +".png");
	}
	
	public BufferedImage getInputImage(int imageIndex) throws IOException {
		return read(getInputFile(imageIndex));
	}

	private int getEndFrame() {
		for(int i = 9999; i >= 0; i--) {
			if (getInputFile(i).exists()) return i;
		}
		return -1;
	}
	
	private void deleteImageFiles(boolean input) {
		System.out.println("Delete " + (input ? "input" : "output") + " image files...");
		int deleted = 0;
		for(int i = 0; i < 9999; i++) {
			printProgressBar(i, 9999, SIZE_PROGRESS_BAR);
			File file = (input == IS_INPUT ? getInputFile(i) : getOutputFile(i));
			if (file.exists()) {
				if (file.delete()) deleted++;
				else System.out.println("Warning: " + file.getPath() + " not deleted sucessfully");
			}
		}
		if (deleted > 0) System.out.println(deleted + " previously generated " + (input ? "input" : "output") + " image files deleted sucessfully");
	}
	
	
	private void createVoxels(int start_input_frame, int end_input_frame, int number_frames, double crop_x, double crop_y, double crop_w, double crop_h) throws IOException {
		System.out.println("createVoxels: start_input_frame = " + start_input_frame + ", end_input_frame = " + end_input_frame + ", number_frames = " + number_frames + ", crop_x = " + crop_x + ", crop_y = " + crop_y + ", crop_w = " + crop_w + ", crop_h = " + crop_h + "...");
		for (int frame = 0; frame < number_frames; frame++) {
			printProgressBar(frame, number_frames, SIZE_PROGRESS_BAR);
			int imageIndex = (int) (start_input_frame + frame * (end_input_frame - start_input_frame) / (number_frames - 1));
			BufferedImage image = getInputImage(imageIndex);
			for (int x = 0; x < SIZE_X; x++) {
				for (int y = 0; y < SIZE_Y; y++) {
					int pixel = image.getRGB((int) (crop_x + x * (crop_w - 1) / (SIZE_X - 1)), (int) (crop_y + y * (crop_h - 1) / (SIZE_Y - 1)));
					voxels[0][frame][x][y] = (byte) (((pixel >> 16) & 0xff) - 128);
					voxels[1][frame][x][y] = (byte) (((pixel >> 8) & 0xff) - 128);
					voxels[2][frame][x][y] = (byte) (((pixel) & 0xff) - 128);
				}
			}
		}
	}

	private void setPixel(BufferedImage image, int frame, int x, int y) {
		int r = voxels[0][frame][x][y] + 128;
		int g = voxels[1][frame][x][y] + 128;
		int b = voxels[2][frame][x][y] + 128;
		int color = (r << 16) | (g << 8) | b;
		image.setRGB(x, y, color);
	}

	private BufferedImage getDiagonalImage(int frameA, int frameB) {
		BufferedImage outputImage = new BufferedImage(SIZE_X, SIZE_Y, BufferedImage.TYPE_INT_RGB);
		for(int x = 0; x < SIZE_X; x++) {
			int frame = (x * (frameB - frameA)) / (SIZE_X - 1) + frameA;
			for(int y = 0; y < SIZE_Y; y++) setPixel(outputImage, frame, x, y);
		}
		return  outputImage;
	}

	private double getInterpolatedValue(int channel, int fIndex, int xIndex, int yIndex, double frameVoxel, double xVoxel, double yVoxel) {
		double i1 = voxels[channel][fIndex][xIndex][yIndex] + (frameVoxel - fIndex) * (voxels[channel][fIndex + 1][xIndex][yIndex] - voxels[channel][fIndex][xIndex][yIndex]);
		double i2 = voxels[channel][fIndex][xIndex + 1][yIndex] + (frameVoxel - fIndex) * (voxels[channel][fIndex + 1][xIndex + 1][yIndex] - voxels[channel][fIndex][xIndex + 1][yIndex]);
		double i3 = voxels[channel][fIndex][xIndex][yIndex + 1] + (frameVoxel - fIndex) * (voxels[channel][fIndex + 1][xIndex][yIndex + 1] - voxels[channel][fIndex][xIndex][yIndex + 1]);
		double i4 = voxels[channel][fIndex][xIndex + 1][yIndex + 1] + (frameVoxel - fIndex) * (voxels[channel][fIndex + 1][xIndex + 1][yIndex + 1] - voxels[channel][fIndex][xIndex + 1][yIndex + 1]);
		double i5 = i1 + (xVoxel - xIndex) * (i2 - i1);
		double i6 = i3 + (xVoxel - xIndex) * (i4 - i3);
		return i5 + (yVoxel - yIndex) * (i6 - i5);
	}

	private void setPixel(BufferedImage image, double frameVoxel, double xVoxel, double yVoxel, int xImage, int yImage) {
		int fIndex = (int) frameVoxel; if (fIndex >= SIZE_F - 1) fIndex = SIZE_F - 2;
		int xIndex = (int) xVoxel; if (xIndex >= SIZE_X - 1) xIndex = SIZE_X - 2;
		int yIndex = (int) yVoxel; if (yIndex >= SIZE_Y - 1) yIndex = SIZE_Y - 2;
		//int r = voxels[0][fIndex][xIndex][yIndex] + 128;
		//int g = voxels[1][fIndex][xIndex][yIndex] + 128;
		//int b = voxels[2][fIndex][xIndex][yIndex] + 128;
		int r = (int) (Math.round(getInterpolatedValue(0, fIndex, xIndex, yIndex, frameVoxel, xVoxel, yVoxel)) + 128);
		int g = (int) (Math.round(getInterpolatedValue(1, fIndex, xIndex, yIndex, frameVoxel, xVoxel, yVoxel)) + 128);
		int b = (int) (Math.round(getInterpolatedValue(2, fIndex, xIndex, yIndex, frameVoxel, xVoxel, yVoxel)) + 128);
		int color = (r << 16) | (g << 8) | b;
		image.setRGB(xImage, yImage, color);
	}

	private BufferedImage getRotationImage(double frameA, double xA, double frameB, double xB) {
		BufferedImage outputImage = new BufferedImage(SIZE_X, SIZE_Y, BufferedImage.TYPE_INT_RGB);
		for(int x = 0; x < SIZE_X; x++) {
			double xVoxel = xA + (xB - xA) * x / (SIZE_X - 1);
			double frameVoxel = frameA + (frameB - frameA) * x / (SIZE_X - 1);
			for(int y = 0; y < SIZE_Y; y++) {
				double yVoxel = y;
				setPixel(outputImage, frameVoxel, xVoxel, yVoxel, x, y);
			}
		}
		return outputImage;
	}

	private BufferedImage getCurvedImage(double xCubePeak, double yCubePeak, double frameCubePeak, double xCubeZoomPeak, double yCubeZoomPeak, double power, double frameCubeBase) {
		BufferedImage outputImage = new BufferedImage(SIZE_X, SIZE_Y, BufferedImage.TYPE_INT_RGB);
		for(int x = 0; x < SIZE_X; x++) {
			for(int y = 0; y < SIZE_Y; y++) {
				double frameVoxel = (SIZE_F - 1) * (frameCubeBase + ((frameCubePeak - frameCubeBase) / (Math.pow((Math.pow((x - xCubePeak * (SIZE_X - 1)) / (xCubeZoomPeak * (SIZE_X - 1)), 2) + Math.pow((y - yCubePeak * (SIZE_Y - 1)) / (yCubeZoomPeak * (SIZE_Y - 1)), 2)), power / 2) + 1)));
				setPixel(outputImage, frameVoxel, x, y, x, y);
			}
		}
		return outputImage;
	}

	private File getOutputFile(int i) {
		return new File(OUTPUT.isDirectory() ? OUTPUT : OUTPUT.getParentFile(), "o" + String.format("%04d", i) +".png");
	}

	private void moveRotation(int numberFrames) throws IOException {
		System.out.println("Create rotation move: numberFrames = " + numberFrames + "...");
		for(int i = 0; i < numberFrames; i++) {
			printProgressBar(i, numberFrames, SIZE_PROGRESS_BAR);
			double angle = i * 2 * Math.PI / numberFrames;
			double xA = SIZE_X / 2 - SIZE_X / 2 * Math.cos(angle);
			double frameA = SIZE_F / 2 - SIZE_F / 2 * Math.sin(angle);
			double xB = SIZE_X / 2 + SIZE_X / 2 * Math.cos(angle);
			double frameB = SIZE_F / 2 + SIZE_F / 2 * Math.sin(angle);
			ImageIO.write(getRotationImage(frameA, xA, frameB, xB), "PNG", getOutputFile(indexImage++));
		}
	}

	private void moveSquare(int number_frames_rotation) throws IOException {
		System.out.println("Create square rotation move: numberFrames = " + number_frames_rotation + "...");
		for(int i = 0; i < number_frames_rotation; i++) {
			printProgressBar(i, number_frames_rotation, SIZE_PROGRESS_BAR);
			double angle = i * 2.0 * Math.PI / number_frames_rotation;
			if ((angle < 1.0 / 4 * Math.PI) || (angle >= 7.0 / 4 * Math.PI)) ImageIO.write(getRotationImage(SIZE_F / 2 * (1 - Math.tan(angle)), 0, SIZE_F / 2 * (1 + Math.tan(angle)), SIZE_X - 1), "PNG", getOutputFile(indexImage++));
			if ((angle >= 1.0 / 4 * Math.PI) && (angle < 3.0 / 4 * Math.PI)) ImageIO.write(getRotationImage(0, SIZE_X / 2 * (1 - Math.tan(Math.PI / 2 - angle)), SIZE_F - 1, SIZE_X / 2 * (1 + Math.tan(Math.PI / 2 - angle))), "PNG", getOutputFile(indexImage++));
			if ((angle >= 3.0 / 4 * Math.PI) && (angle < 5.0 / 4 * Math.PI)) ImageIO.write(getRotationImage(SIZE_F / 2 * (1 - Math.tan(Math.PI - angle)), SIZE_X - 1, SIZE_F / 2 * (1 + Math.tan(Math.PI - angle)), 0), "PNG", getOutputFile(indexImage++));
			if ((angle >= 5.0 / 4 * Math.PI) && (angle < 7.0 / 4 * Math.PI)) ImageIO.write(getRotationImage(SIZE_F -1, SIZE_X / 2 * (1 + Math.tan(3 * Math.PI / 2 - angle)), 0, SIZE_X / 2 * (1 - Math.tan(3 * Math.PI / 2 - angle))), "PNG", getOutputFile(indexImage++));
		}
	}

	private void moveOscillation(int numberFrames, double maxAngle) throws IOException {
		System.out.println("Create oscillation move: numberFrames = " + numberFrames + ", maxAngle = " + maxAngle + "...");
		for(int i = 0; i < numberFrames; i++) {
			printProgressBar(i, numberFrames, SIZE_PROGRESS_BAR);
			double angle = maxAngle * Math.sin(i * 2 * Math.PI / numberFrames);
			double xA = SIZE_X / 2 - SIZE_X / 2 * Math.cos(angle);
			double frameA = SIZE_F / 2 - SIZE_F / 2 * Math.sin(angle);
			double xB = SIZE_X / 2 + SIZE_X / 2 * Math.cos(angle);
			double frameB = SIZE_F / 2 + SIZE_F / 2 * Math.sin(angle);
			ImageIO.write(getRotationImage(frameA, xA, frameB, xB), "PNG", getOutputFile(indexImage++));

		}
	}

	private void moveCurved(int numberFrames, double xCubeCenter, double yCubeCenter, double frameCubeCenter, double xCubeRadius, double yCubeRadius, double frameCubeRadius, double xCubeZoomPeak, double yCubeZoomPeak, double power) throws IOException {
		System.out.println("Create curved move: numberFrames = " + numberFrames + ", xCenter = " + xCubeCenter + ", yCenter = " + yCubeCenter + ", frameCenter = " + frameCubeCenter + ", xRadius = " + xCubeRadius + ", yRadius = " + yCubeRadius + ", frameRadius = " + frameCubeRadius + ", xZoomPeak " + xCubeZoomPeak + ", yZoomPeak = " + yCubeZoomPeak + ", power = " + power + "...");
		for(int i = 0; i < numberFrames; i++) {
			printProgressBar(i, numberFrames, SIZE_PROGRESS_BAR);
			double alpha = i * 2 * Math.PI / numberFrames;
			ImageIO.write(getCurvedImage(xCubeCenter + xCubeRadius * Math.cos(alpha), yCubeCenter + yCubeRadius * Math.cos(alpha), frameCubeCenter + frameCubeRadius * Math.sin(alpha), xCubeZoomPeak, yCubeZoomPeak, power, frameCubeCenter), "PNG", getOutputFile(indexImage++));
		}
	}

	private void moveLinear(int numberFrames, double cubeFrameStart, double cubeFrameEnd) throws IOException {        
		System.out.println("Create linear move: numberFrames = " + numberFrames + ", cubeFrameStart = " + cubeFrameStart + ", cubeFrameEnd = " + cubeFrameEnd + "...");
		for(int i = 0; i < numberFrames; i++) {
			printProgressBar(i, numberFrames, SIZE_PROGRESS_BAR);
			int frame = (int) ((SIZE_F - 1) * cubeFrameStart + i * (SIZE_F - 1) * (cubeFrameEnd - cubeFrameStart) / (numberFrames - 1));
			ImageIO.write(getDiagonalImage(frame, frame), "PNG", getOutputFile(indexImage++));
		}
	}

	/**
	 * Creates a linear move.
	 * @param cubeFrameStart start of the move, has to be between 0 and 1
	 * @param cubeFrameEnd end of the move, has to be between 0 and 1
	 */
	private void moveLinear(double cubeFrameStart, double cubeFrameEnd) throws IOException {
		moveLinear((int) (Math.abs(SIZE_F * (cubeFrameEnd - cubeFrameStart) + 1)), cubeFrameStart, cubeFrameEnd);
	}

	public final void launchFfmpeg(boolean input) {
		try {
			final ProcessBuilder pb;
			String execString = new File(FFMPEG_DIRECTORY, "ffmpeg").getCanonicalPath();
			if (input == IS_INPUT)
				pb = new ProcessBuilder(execString, "-i", INPUT.getPath(), INPUT.getCanonicalFile().getParent() + "\\i%04d.png");
			 else {
				if (OUTPUT.exists()) {
					if (OUTPUT.delete()) System.out.println(OUTPUT.getPath() + " was sucessfully deleted");
					else System.out.println(OUTPUT.getPath() + "was not sucessfully deleted");
				} else System.out.println(OUTPUT.getPath() + " do not exist, no delete necesary");
				pb = new ProcessBuilder(execString, "-i", "\"" + OUTPUT.getCanonicalFile().getParent() + "\\o%04d.png\"", "-c:v", "libx264", "-crf", "30", "-pix_fmt", "yuv420p", OUTPUT.getPath());
			}
			pb.redirectErrorStream(true);
			final Process process = pb.start();
			BufferedReader bri = new BufferedReader(new InputStreamReader(process.getInputStream()));
			BufferedReader bre = new BufferedReader(new InputStreamReader(process.getErrorStream()));
			String line;
			while ((line = bri.readLine()) != null) System.out.println(line);
			bri.close();
			while ((line = bre.readLine()) != null) System.err.println(line);
			bre.close();
			process.waitFor();
		} catch (Exception e) {
			System.err.println(e);
		}
	}

	public Twist(String[] args) {
		String input = getArgument(args, "-input", "-i");
		if (input.isEmpty()) {
			System.err.println("Error: no -input parameter defined, which is mandatory. Refer to http://gloumouth1.free.fr/test/Twist for parameters list.");
			return;
		}
		else INPUT = new File(input);
		
		String output = getArgument(args, "-output", "-o");
		if (output.isEmpty()) {
			if (!INPUT.isDirectory()) OUTPUT = new File(INPUT.getName() + ".twisted.mp4");
			System.out.println("Warning: no -output parameter defined, then forced -output " + OUTPUT.getPath());
		} else OUTPUT = new File(output);

		String ffmpeg = getArgument(args, "-ffmpeg", "-ff");
		if (!ffmpeg.isEmpty()) FFMPEG_DIRECTORY = new File(ffmpeg);
		
		try {
			System.out.println("Twisted time: ffmpegDirectory=" + FFMPEG_DIRECTORY.getCanonicalPath() + ", INPUT=" + INPUT.getCanonicalPath() + ", OUTPUT=" + OUTPUT.getCanonicalPath());
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		
		if (hasParameter(args, "-keep", "-k") ) {
			String[] keep = getArguments(args, "-keep", "-k", ":");
			if (keep.length == 0) {
				KEEP_INPUT = true;
				KEEP_OUTPUT = true;
			}
			if (keep.length >= 1) {
				if (keep[0].equals("input")) KEEP_INPUT = true;
				else if (keep[0].equals("output")) KEEP_OUTPUT = true;
				else System.out.println("Warning: unexpected argument for keep \"" + keep[0] + "\"");
			}
			if (keep.length >= 2) {
				System.out.println("Warning: unexpected number of arguments for keep parameter.");
			}
		}

		if (!INPUT.isDirectory()) {
			deleteImageFiles(IS_INPUT);
			launchFfmpeg(IS_INPUT);	
		}
		
		BufferedImage firstImage = null;
		try {
			firstImage = getInputImage(1);
		} catch (IOException e) {
			System.err.println(e);
			return;
		}
		
		String[] crop = getArguments(args, "-crop", "-c", ":");
		int crop_x = 0;
		int crop_y = 0;
		if (crop.length >= 1) crop_x = Integer.parseInt(crop[0]);
		if (crop.length >= 2) crop_y = Integer.parseInt(crop[1]);
		int crop_w = firstImage.getWidth() - crop_x;
		int crop_h = firstImage.getHeight()	- crop_y;
		if (crop.length >= 3) crop_w = Integer.parseInt(crop[2]);
		if (crop.length >= 4) crop_h = Integer.parseInt(crop[3]);
		if (crop.length >= 5) System.out.println("Warning: +" + crop.length + " is an unexpected number of arguments for parameter -crop");
		
		SIZE_X = crop_w;
		SIZE_Y = crop_h;

		String[] definition = getArguments(args, "-definition", "-d", ":");
		if (definition.length == 1) {
			double factor = (double) SIZE_X / SIZE_Y;
			SIZE_Y = Integer.parseInt(definition[0]);
			SIZE_X = (int) (factor * SIZE_Y);
		} else if (definition.length == 2) {
			SIZE_X = Integer.parseInt(definition[0]);
			SIZE_Y = Integer.parseInt(definition[1]);
		} else if (definition.length >= 3) System.out.println("Warning: unexpected number of arguments with -definition");

		if (SIZE_X % 2 == 1) System.out.println("Warning: defined or computed SIZE_X = " + SIZE_X++ + ", which is odd and incompatible with ffmpeg; as a consequence, +1 will be applied.");


		String[] frames = getArguments(args, "-frames", "-f", ":");
		int start_input_frame = 1;
		int end_input_frame = -1;
		int number_frames = -1;
		if (frames.length >= 1) number_frames = Integer.parseInt(frames[0]);
		if (frames.length >= 2) start_input_frame = Integer.parseInt(frames[1]);
		if (frames.length >= 3) end_input_frame = Integer.parseInt(frames[2]);
		if (frames.length >= 4) System.out.println("Warning: "+ frames.length + " is an unexpected number of arguments for parameter -frames");
		if (end_input_frame == -1) end_input_frame = getEndFrame();
		if (number_frames == -1) number_frames = end_input_frame - start_input_frame + 1;
		if (number_frames > SIZE_F_MAX) {
			System.out.println("number frames = " + number_frames + " too big, " + SIZE_F_MAX + " applied.");
			number_frames = SIZE_F_MAX;
		}
		SIZE_F = number_frames;
		System.out.println("[SIZE_X,SIZE_Y,SIZE_F] = [" + SIZE_X + "," + SIZE_Y + "," + SIZE_F + "], then let's allocate memory for voxels array with size = " + 
				SIZE_X + "*" + SIZE_Y + "*" + SIZE_F + "*3 bytes = " + Math.round(SIZE_X * SIZE_Y * SIZE_F * 3.0 / 1000000)  + " MB..." );
		voxels = new byte[3][SIZE_F][SIZE_X][SIZE_Y];
		System.out.println("Succeeded!");
		

		try {   	
			createVoxels(start_input_frame, end_input_frame, number_frames, crop_x, crop_y, crop_w, crop_h);
			deleteImageFiles(IS_OUTPUT);

			String[] move = getArguments(args, "-move", "-m", "~");
			if (move.length == 0) {
				System.out.println("Warning: no '-move' parameter defined, rotation animation applied");
				moveRotation(1000);
			}
			for(int i = 0; i < move.length; i++) {
				String[] moveArguments = move[i].split(":");
				if (moveArguments.length == 0) {
					System.out.println("Warning: parameters expected for -move, rotation animation applied");
					moveRotation(1000);
				}
				if (moveArguments.length >= 1) {
					if (moveArguments[0].equals("linear") || moveArguments[0].equals("l")) {
						double cubeFrameStart = 0.0;
						double cubeFrameEnd = 1.0;
						if (moveArguments.length == 2) cubeFrameEnd = Double.parseDouble(moveArguments[1]);
						if (moveArguments.length >= 3) {
							cubeFrameStart = Double.parseDouble(moveArguments[1]);
							cubeFrameEnd = Double.parseDouble(moveArguments[2]);
						}
						if (moveArguments.length >= 4) System.out.println("Warning: " + moveArguments.length + " is an unxpected number of subarguments for argument -move linear");
						moveLinear(cubeFrameStart, cubeFrameEnd);
					} else if (moveArguments[0].equals("rotation") || moveArguments[0].equals("r")) {
						int framesNumber = 1000;
						if (moveArguments.length >= 2) framesNumber = Integer.parseInt(moveArguments[1]);
						if (moveArguments.length >= 3) System.out.println("Warning: " + moveArguments.length + " is an unxpected number of subarguments for argument -move rotation");
						moveRotation(framesNumber);
					} else if (moveArguments[0].equals("oscillation") || moveArguments[0].equals("o")) {
						int framesNumber = 1000;
						double maxAngle = Math.PI / 8;
						if (moveArguments.length >= 2) framesNumber = Integer.parseInt(moveArguments[1]);
						if (moveArguments.length >= 3) maxAngle = Double.parseDouble(moveArguments[2]);
						if (moveArguments.length >= 4) System.out.println("Warning: " + moveArguments.length + " is an unxpected number of subarguments for argument -move oscillation");
						moveOscillation(framesNumber, maxAngle);
					} else if (moveArguments[0].equals("curved") || moveArguments[0].equals("c")) {
						int framesNumber = 1000;
						double xCubeCenter = 0.5;
						double yCubeCenter = 0.5;
						double frameCubeCenter = 0.5;
						double xCubeRadius = 0.25;
						double yCubeRadius = 0;
						double fCubeRadius = 0.25;
						double xCubeZoomPeak = 0.17;
						double yCubeZoomPeak = 0.17;
						double power = 6;
						if (moveArguments.length >= 2) framesNumber = Integer.parseInt(moveArguments[1]);
						if (moveArguments.length >= 3) xCubeCenter = Double.parseDouble(moveArguments[2]);
						if (moveArguments.length >= 4) yCubeCenter = Double.parseDouble(moveArguments[3]);
						if (moveArguments.length >= 5) frameCubeCenter = Double.parseDouble(moveArguments[4]);
						if (moveArguments.length >= 6) xCubeRadius = Double.parseDouble(moveArguments[5]);
						if (moveArguments.length >= 7) yCubeRadius = Double.parseDouble(moveArguments[6]);
						if (moveArguments.length >= 8) fCubeRadius = Double.parseDouble(moveArguments[7]);
						if (moveArguments.length >= 9) xCubeZoomPeak = Double.parseDouble(moveArguments[8]);
						if (moveArguments.length >= 10) yCubeZoomPeak = Double.parseDouble(moveArguments[9]);
						if (moveArguments.length >= 11) power = Double.parseDouble(moveArguments[10]);
						if (moveArguments.length >= 12) System.out.println("Warning: " + moveArguments.length + " is an unxpected number of subarguments for argument -move  curved");
						moveCurved(framesNumber, xCubeCenter, yCubeCenter, frameCubeCenter, xCubeRadius, yCubeRadius, fCubeRadius, xCubeZoomPeak, yCubeZoomPeak, power);
					} else if (moveArguments[0].equals("square") || moveArguments[0].equals("s")) {
						int framesNumber = 1000;
						if (moveArguments.length >= 2) framesNumber = Integer.parseInt(moveArguments[1]);
						if (moveArguments.length >= 3) System.out.println("Warning: "+ moveArguments.length + " is an unxpected number of subarguments for argument -move  square");      
						moveSquare(framesNumber);
					} else {
						System.out.println("Warning: unexpected parameter '" + moveArguments[0] + "' for -move");
					}
				} else System.out.println("Warning: unexpected number of arguments for -move");
			}
			
			if (!OUTPUT.isDirectory()) launchFfmpeg(IS_OUTPUT);
			if (!KEEP_INPUT) deleteImageFiles(IS_INPUT);
			if (!KEEP_OUTPUT) {
				if (OUTPUT.isDirectory()) System.out.println("Warning: parameter \"-keep output\" forced because no output file name was given as -output parameter.");
				else deleteImageFiles(IS_OUTPUT);
			}
			System.out.println("Video twisted in " + getFormatedDuration(Duration.between(startTwist, LocalDateTime.now())));
		} catch (IOException e) {
			System.err.println(e.getMessage());
		}
	}
}