package eu.printingin3d.models.models.box;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import eu.printingin3d.javascad.coords.Angles3d;
import eu.printingin3d.javascad.coords.Coords3d;
import eu.printingin3d.javascad.coords.Dims3d;
import eu.printingin3d.javascad.enums.AlignType;
import eu.printingin3d.javascad.enums.Side;
import eu.printingin3d.javascad.models.Abstract3dModel;
import eu.printingin3d.javascad.models.Cube;
import eu.printingin3d.javascad.models.Cylinder;
import eu.printingin3d.javascad.tranzitions.Difference;
import eu.printingin3d.javascad.tranzitions.Mirror;
import eu.printingin3d.javascad.tranzitions.Union;
import eu.printingin3d.javascad.utils.IModelProvider;
import eu.printingin3d.javascad.utils.ModelWithPath;
import eu.printingin3d.javascad.utils.SaveScadFiles;

public class BoxWithLid implements IModelProvider {
	private static final double AXIS_RADIUS = 1.0;
	private static final double HINGE_THICKNESS = 4.0;
	private static final double WALL_THICKNESS = 1.2;
	
	private final double length;
	private final double width;
	private final double height;
	
	public BoxWithLid(double length, double width, double height) {
		this.length = length;
		this.width = width;
		this.height = height;
	}

	public Abstract3dModel createBox() {
		Abstract3dModel base = new Cube(new Dims3d(width, length, height));
		Abstract3dModel minus = new Cube(new Dims3d(width-HINGE_THICKNESS-WALL_THICKNESS, length-WALL_THICKNESS*2.0, height))
				.align(new Side(AlignType.MIN, AlignType.CENTER, AlignType.MIN), base, true)
				.move(new Coords3d(HINGE_THICKNESS, 0.0, WALL_THICKNESS));
		
		return new Union(new Difference(base, minus), 
				createHinges(false)
					.align(Side.CENTER, base, true)
					.align(Side.LEFT, base, true)
					.align(Side.TOP, base, false)); 
	}
	
	public Abstract3dModel createLid() {
		Abstract3dModel hinges = createHinges(true);
		double lidWidht = width-hinges.getBoundaries().getX().getSize();
		Abstract3dModel base = new Cube(new Dims3d(lidWidht, length, HINGE_THICKNESS));
		Abstract3dModel minus = new Cube(new Dims3d(lidWidht-WALL_THICKNESS*2.0, length-WALL_THICKNESS*2.0, HINGE_THICKNESS))
				.align(new Side(AlignType.CENTER, AlignType.CENTER, AlignType.MIN), base, true)
				.move(Coords3d.zOnly(WALL_THICKNESS));
		
		return new Union(new Difference(base, minus), 
				hinges
					.align(Side.CENTER, base, true)
					.align(Side.LEFT, base, false)); 
	}

	private Abstract3dModel createHinges(boolean odds) {
		List<Abstract3dModel> hinges = new ArrayList<>();
		
		for (int i=0;i<5;i++) {
			if ((i % 2==0) == odds) {
				hinges.add(
						createHinge(length/5.0-1.0)
								.move(Coords3d.yOnly(i*length/5.0))
								.rotate(i % 2 ==0 ? Angles3d.ROTATE_MINUS_Y : Angles3d.ZERO ));
			}
		}
		
		return new Union(hinges);
	}
	
	private static Abstract3dModel createHinge(double length) {
		Abstract3dModel base = new Cube(new Dims3d(HINGE_THICKNESS, length, HINGE_THICKNESS/2.0+AXIS_RADIUS));
		Coords3d midPoint = Coords3d.midPoint(base.getBoundaries().getMaxCorner(), base.getBoundaries().getMinCorner());
		Coords3d basePoint = new Coords3d(midPoint.getX(), midPoint.getY(), base.getBoundaries().getMaxCorner().getZ());
		Abstract3dModel cylinder = new Cylinder(length, HINGE_THICKNESS/2.0)
				.rotate(Angles3d.ROTATE_PLUS_X)
				.align(Side.CENTER, basePoint);
		return new Difference(
				new Union(base, cylinder),
				new Cylinder(length+1.0, AXIS_RADIUS)
					.rotate(Angles3d.ROTATE_PLUS_X)
					.align(Side.CENTER, cylinder, true));
	}
	
	/**
	 * We create a box with the given sizes. These dimensions are referring of the outer size of the box.
	 */
	public static void main(String[] args) throws IOException {
		BoxWithLid boxWithLid = new BoxWithLid(86.0, 60.0, 15.0);
		new SaveScadFiles(new File("C:/temp/box")).
				addModelProvider(boxWithLid).
				saveScadFiles();
	}

	@Override
	public Abstract3dModel getAssembledModel() {
		Abstract3dModel box = createBox();
		Abstract3dModel lid = Mirror.mirrorZ(createLid())
				.align(new Side(AlignType.MAX, AlignType.CENTER, AlignType.MAX), box, true);
		
		return new Union(box, lid);
	}

	@Override
	public List<ModelWithPath> getModelsAndPaths() {
		return Arrays.asList(
				new ModelWithPath(createBox(), "box.print.scad"),
				new ModelWithPath(createLid(), "lid.print.scad"),
				new ModelWithPath(getAssembledModel(), "box.scad")
			);
	}
}
