package eu.printingin3d.models.models.bigwandholder;

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.coords2d.Coords2d;
import eu.printingin3d.javascad.coords2d.Dims2d;
import eu.printingin3d.javascad.enums.Side;
import eu.printingin3d.javascad.models.Abstract3dModel;
import eu.printingin3d.javascad.models.Cylinder;
import eu.printingin3d.javascad.models.LinearExtrude;
import eu.printingin3d.javascad.models.Ring;
import eu.printingin3d.javascad.models2d.Abstract2dModel;
import eu.printingin3d.javascad.models2d.Circle;
import eu.printingin3d.javascad.models2d.RoundedSquare;
import eu.printingin3d.javascad.models2d.Square;
import eu.printingin3d.javascad.tranzitions.Difference;
import eu.printingin3d.javascad.tranzitions.Direction;
import eu.printingin3d.javascad.tranzitions.Slicer;
import eu.printingin3d.javascad.tranzitions.Union;
import eu.printingin3d.javascad.utils.IModelProvider;
import eu.printingin3d.javascad.utils.ModelWithPath;
import eu.printingin3d.javascad.utils.SaveScadFiles;
import eu.printingin3d.javascad.utils.UsefulConsts;

public class BigWandHolder implements IModelProvider {
	private static final double SECTION1_D = 48; 
	private static final double SECTION2_D = 100;
	private static final double RING1_D = 110;
	private static final double RING2_D = 120;
	
	private static final double OUTER_DIAMETER = 130;
	
	private static final double SMALL_HOLE_DIAMETER = 5;
	private static final double SMALL_HOLE_DIST_FROM_ORIG = 15;
	
	private static final int NUMBER_OF_BIG_RINGS = 8;
	
	private static final double THICKNESS = 3.5;
	private static final double THICKNESS_THIN = 2;
	private static final double MIDLE_RING_THICKNESS = 4;
	
	private static final double PILLAR_LENGTH = 160;
	private static final double PILLAR_RADIUS = 5;
	
	private static final double DOWEL_RADIUS = 3.5/2;
	private static final double DOWEL_DEPTH = 7; 
	
	public static enum PillarType {
		SLICED, SPIRAL
	}
	
	private final PillarType pillarType;
	
	public BigWandHolder(PillarType pillarType) {
		this.pillarType = pillarType;
	}

	public Abstract3dModel createTopPart() {
		return createTopPart(THICKNESS_THIN);
	}
	
	private Abstract3dModel createTopPart(double thickness) {
		Abstract3dModel _base = new Cylinder(THICKNESS, OUTER_DIAMETER/2);
		
		Abstract3dModel ring = createMiddleRing(thickness);
		
		Abstract3dModel base = new Union(
				createOuterRing().align(Side.BOTTOM, _base, true),
				ring.align(Side.BOTTOM, _base, true),
				new Cylinder(THICKNESS, SECTION1_D/2)
			);
		
		Abstract3dModel smallHole =
			trice(new Cylinder(THICKNESS+1, SMALL_HOLE_DIAMETER/2), SMALL_HOLE_DIAMETER*0.43)
					.move(Coords3d.yOnly(-SMALL_HOLE_DIST_FROM_ORIG));
		
		return new Difference(base,
				smallHole.cloneModel(),
				smallHole.cloneModel().rotate(new Angles3d(0, 0, + 90)),
				smallHole.cloneModel().rotate(new Angles3d(0, 0, +180)),
				smallHole.cloneModel().rotate(new Angles3d(0, 0, +270)),
				new Cylinder(THICKNESS, DOWEL_RADIUS)
					.align(Side.TOP, _base, true)
					.move(Coords3d.zOnly(-1))
			);
	}

	public Abstract3dModel createBottomPart() {
		Abstract3dModel top = createTopPart(THICKNESS);
		return new Union(new Cylinder(THICKNESS_THIN, OUTER_DIAMETER/2).align(Side.BOTTOM, top, false),
				top)
			.subtractModel(new Cylinder(THICKNESS, DOWEL_RADIUS)
						.align(Side.BOTTOM, top, true)
						.move(Coords3d.zOnly(+1)));
	}
	
	public Abstract3dModel getSlicedPillar(double size, double radius) {
		List<Abstract3dModel> subs = new ArrayList<>();
		
		for (int i=0;i<4;i++) {
			subs.add(
					new Slicer(new Cylinder(size, radius+1.0), Direction.X, 0.45, 0.45)
							.rotate(new Angles3d(0, 0, i*180.0/4+22.5))
					);
		}
		
		return new Union(new Cylinder(size, radius*0.85),
				new Difference(new Cylinder(size, radius), subs)
				);
	}

	public Abstract3dModel createPillar() {
		Abstract3dModel ring1 = new Union(new Ring(PILLAR_RADIUS*1.5, new RoundedSquare(new Dims2d(4, 4), 1)), new Cylinder(4.0, PILLAR_RADIUS*1.5));
		Abstract3dModel ring2 = new Union(new Ring(PILLAR_RADIUS*1.25, new RoundedSquare(new Dims2d(4, 4), 1)), new Cylinder(4.0, PILLAR_RADIUS*1.25));
		Abstract3dModel cyl1 = new Cylinder(4.0, PILLAR_RADIUS);
		Abstract3dModel ring3 = new Union(new Ring(PILLAR_RADIUS*1.5, new RoundedSquare(new Dims2d(4, 4), 1)), new Cylinder(4.0, PILLAR_RADIUS*1.5));
		Abstract3dModel ring4 = new Union(new Ring(PILLAR_RADIUS*1.25, new RoundedSquare(new Dims2d(4, 4), 1)), new Cylinder(4.0, PILLAR_RADIUS*1.25));
		Abstract3dModel cyl2 = new Cylinder(4.0, PILLAR_RADIUS);
		double middlePillarLength = PILLAR_LENGTH
				-ring1.getBoundaries().getZ().getSize()
				-ring2.getBoundaries().getZ().getSize()
				-cyl1.getBoundaries().getZ().getSize()
				-ring3.getBoundaries().getZ().getSize()
				-ring4.getBoundaries().getZ().getSize() 
				-cyl2.getBoundaries().getZ().getSize();
		Abstract3dModel pillar =
				pillarType==PillarType.SLICED ? getSlicedPillar(middlePillarLength, PILLAR_RADIUS) :
				pillarType==PillarType.SPIRAL ? getSpiralPillar(middlePillarLength, PILLAR_RADIUS/2) :
				null;
				
		Abstract3dModel dowels = new Cylinder(DOWEL_DEPTH+1, DOWEL_RADIUS);
				
		return ring1
				.addModelTo(Side.TOP, ring2)
				.addModelTo(Side.TOP, cyl1)
				.addModelTo(Side.TOP, pillar)
				.addModelTo(Side.TOP, cyl2)
				.addModelTo(Side.TOP, ring4)
				.addModelTo(Side.TOP, ring3)
				.subtractModel(dowels.cloneModel().align(Side.BOTTOM, ring1, true).move(Coords3d.zOnly(-1)))
				.subtractModel(dowels.cloneModel().align(Side.TOP, ring3, true).move(Coords3d.zOnly(+1)));
	}
	
	private Abstract3dModel getSpiralPillar(double length, double radius) {
		Abstract2dModel baseCircle = new Circle(radius).move(new Coords2d(radius, 0));
		return new Cylinder(length, radius)
				.addModel(new LinearExtrude(baseCircle, length, 720, 1))
				.addModel(new LinearExtrude(baseCircle, length, 720, 1).rotate(new Angles3d(0, 0, 120)))
				.addModel(new LinearExtrude(baseCircle, length, 720, 1).rotate(new Angles3d(0, 0, 240)));
	}

	private Abstract3dModel createOuterRing() {
		List<Coords3d> moves = new ArrayList<>();
		
		for (int i=0;i<24;i++) {
			double alpha = i*2*Math.PI/24;
			double beta = (i+0.5)*2*Math.PI/24;
			
			moves.add(new Coords3d(RING1_D/2*Math.sin(alpha), RING1_D/2*Math.cos(alpha), 0));
			moves.add(new Coords3d(RING2_D/2*Math.sin(beta), RING2_D/2*Math.cos(beta), 0));
		}
		
		Abstract3dModel ringBase = new Cylinder(THICKNESS, OUTER_DIAMETER/2);
		return new Union( 
			new Difference(
				ringBase,
				new Cylinder(THICKNESS+1, SECTION2_D/2),
				new Ring(RING1_D/2, new Square(new Dims2d(2, 2))).align(Side.TOP, ringBase, true).move(Coords3d.zOnly(0.1)),
				new Ring(RING2_D/2, new Square(new Dims2d(2, 2))).align(Side.TOP, ringBase, true).move(Coords3d.zOnly(0.1))
			),
			new Cylinder(THICKNESS, 1.1).moves(moves)
			);
	}

	private Abstract3dModel createMiddleRing(double thickness) {
		double offset = (SECTION1_D+SECTION2_D)/4;
		
		List<Coords3d> moves = new ArrayList<>();
		for (int i=0;i<NUMBER_OF_BIG_RINGS;i++) {
			double alpha = i*2*Math.PI/NUMBER_OF_BIG_RINGS;
			moves.add(new Coords3d(offset*Math.sin(alpha), offset*Math.cos(alpha), 0));
		}
		
		return new Ring((SECTION2_D-SECTION1_D)/4, new Square(new Dims2d(MIDLE_RING_THICKNESS, thickness)))
				.moves(moves);
	}
	
	private static Abstract3dModel trice(Abstract3dModel orig, double dist) {
		return orig.moves(Arrays.asList(
					new Coords3d(+dist, -dist/2, 0),
					new Coords3d(-dist, -dist/2, 0),
					Coords3d.yOnly(dist*(UsefulConsts.SQRT_3-0.5))
				));
	}

	@Override
	public Abstract3dModel getAssembledModel() {
		Abstract3dModel bottom = createBottomPart();
		Abstract3dModel pillar = createPillar()
					.align(Side.TOP, bottom, false);
		return new Union(
				bottom,
				createTopPart().align(Side.TOP, pillar, false),
				pillar
			);
	}

	@Override
	public List<ModelWithPath> getModelsAndPaths() {
		return Arrays.asList(
				new ModelWithPath(createTopPart(), "top.print.scad"),
				new ModelWithPath(createBottomPart(), "bottom.print.scad"),
				new ModelWithPath(getAssembledModel(), "bigwandholder.scad"),
				new ModelWithPath(new Slicer(createPillar().rotate(Angles3d.ROTATE_MINUS_Y),Direction.Z, 2, 1), 
						"pillar1.print.scad"),
				new ModelWithPath(new Slicer(createPillar().rotate(Angles3d.ROTATE_PLUS_Y),Direction.Z, 2, 1), 
						"pillar2.print.scad")
			);
	}
	
	public static void main(String[] args) throws IOException {
		
		new SaveScadFiles(new File("C:/temp/bigwandholder/")).
				addModelProvider(new BigWandHolder(PillarType.SPIRAL)).
				saveScadFiles();
		
	}
	
}
