import static uk.co.nickthecoder.foocad.arrange.v1.Arrange.*
import uk.co.nickthecoder.foocad.extras.v1.*
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import uk.co.nickthecoder.foocad.smartextrusion.v1.*
import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.*

class Plicks : Model {

    @Custom( about="The nominal size of a 1x1x1 block" )
    var unitSize = 10

    @Custom( about="The holes' deviation from stright. Large for a tight fit." )
    var bumpSize = 0.7

    @Custom( about="Diameter of the hole. Larger than the filament diameter!" )
    var holeSize = 1.75 + 0.7

    @Custom( about="How much to shrink in X and Y so that piece pack together well" )
    var clearance = 0.1

    @Custom( about="How much to shrink in Z so that pieces pack together well" )
    var clearanceZ = 0.1

    @Custom( about="Scale horizontal hole in Z. Adjust if vertical hole are looser/tighter than horizontal holes" )
    var holeZScaling = 1.2

    @Custom( about="Only cosmetic" )
    var chamfer = 1.0


    // Cross-section of the holes. vertical holes are circular, but
    // horizontal holes use Extra's "safeOverhang" for better printing of the overhangs.
    meth holeShape( horizontal : bool ) : Shape2d {
        val circle = Circle( holeSize/2 ).hole()
        if (horizontal) {
            return circle.safeOverhang().mirrorY().scaleY(holeZScaling)
        } else {
            return circle
        }
    }

    // 2D shape of a brick (as seen from above)
    meth shape( width : int, length : int ) : Shape2d {
        return Square( unitSize * width, unitSize * length )
            .roundAllCorners( chamfer, 1 )
            .center().offset(-clearance/2)
    }


    // A 3D brick without any holes
    meth plainBrick( width : int, length : int, height : int ) : Shape3d {
        val shape = shape( width, length )
        return shape.smartExtrude( height * unitSize - clearanceZ )
            .edges( Chamfer( chamfer ) )
    }


    // The path a hole makes. This is the "clever" part of the system which
    // ensures that the filament is held firmly in the holes despite the fact that
    // the diameter of the holes is LARGER than the filament.
    // The holes wiggle, which forces the filament to wiggle, and therefore
    // it presses outwards (due to the springy nature of the filament).
    meth wigglePath( length : double, bump : double ) : Path2d {
        val pre = 1
        val halfLength = length/2 - pre

        return PolygonBuilder().apply {
            moveTo( 0, -length/2 )
            lineBy( 0, pre )
            bezierBy(
                Vector2( 0, halfLength/2 ),
                Vector2( 0, halfLength/2 ),
                Vector2( bump, halfLength )
            )
            bezierBy(
                Vector2( 0, halfLength/2 ),
                Vector2( 0, halfLength/2 ),
                Vector2( -bump, halfLength )
            )
            lineBy( 0, pre )
        }.buildPath()
    }

    // A single hole. Length is in "base" units, i.e. will be 1 for the smallest (unit) cube.
    // The holes are different when horizontal, to aid printing the overhangs.
    meth hole( length : int, horizontal : bool ) : Shape3d {
        val path = wigglePath( unitSize, bumpSize )
        val holeShape = holeShape( horizontal )
        val one = holeShape.extrude(path)
        if (length == 1) {
            return one
        } else {
            val middle = holeShape.extrude( (length - 2)* unitSize )
                .rotateX(-90).centerZ()
                .frontTo( one.back )

            return (middle + one.frontTo( middle.back ).also())
                .frontTo( -unitSize * length/2 )
        }

    }

    // Holes in all 3 directions.
    // width, length and height are in "base" units. i.e. will be 1 for the smallest
    // (unit) cube.
    // `blind` Are the vertical holes blind? i.e. do they stop before exiting the bottom.
    //     Used for "base plates"
    meth holes(  width : int, length : int, height : int, blind : bool ) : Shape3d {

        val small = 0.33
        val large = 1-small

        val holesX = hole( width, true ).rotateZ(90)
            .repeatY( length, unitSize )
            .repeatZ( height, unitSize )
            .translateY( unitSize * (large - length/2) )
            .translateZ( unitSize * small -clearanceZ/2 )
            
        val holesY = hole( length, true )
            .repeatX( width, unitSize )
            .repeatZ( height, unitSize )
            .translateX( unitSize * (large - width/2) )
            .translateZ( unitSize * large -clearanceZ/2 )
    
        val holesZ = hole( height, false )
            .rotateX(90).bottomTo(0).rotateZ(-90-45)
            .repeatX( width, unitSize )
            .repeatY( length, unitSize )
            .translateX( unitSize * (small - width/2) )
            .translateY( unitSize * (small - length/2) )

        val holes = holesX + holesY + holesZ
        return if (blind) {
            holes - holes.boundingCube().topTo( 0.6 )
        } else {
            holes
        }

    }

    // A complete brick of any size.
    meth brick( width : int, length : int, height : int, blind : bool ) : Shape3d {
        return plainBrick( width, length, height ) -
            holes( width, length, height, blind ).color("Red")
    }
    meth brick( width : int, length : int, height : int ) = brick( width, length, height, false )

    // Standard size bricks...

    // === n x 1 x 1 ===
    @Piece
    meth brick1x1x1() = brick( 1,1,1 )

    @Piece
    meth brick2x1x1() = brick( 2,1,1 )

    @Piece
    meth brick3x1x1() = brick( 3,1,1 )

    @Piece
    meth brick4x1x1() = brick( 4,1,1 )

    @Piece
    meth brick5x1x1() = brick( 5,1,1 )

    @Piece
    meth brick6x1x1() = brick( 6,1,1 )


    // === n x 2 x 1 ===

    @Piece
    meth brick2x2x1() = brick( 2,2,1 )

    @Piece
    meth brick3x2x1() = brick( 3,2,1 )

    @Piece
    meth brick4x2x1() = brick( 4,2,1 )

    @Piece
    meth brick5x2x1() = brick( 5,2,1 )

    @Piece
    meth brick6x2x1() = brick( 6,2,1 )


    // === n x 2 x 2 ===

    @Piece
    meth brick2x2x2() = brick( 2,2,2 )

    @Piece
    meth brick3x2x2() = brick( 3,2,2 )

    @Piece
    meth brick4x2x2() = brick( 4,2,2 )

    @Piece
    meth brick5x2x2() = brick( 5,2,2 )

    @Piece
    meth brick6x2x2() = brick( 6,2,2 )


    // Base plates (with blind holes)

    @Piece
    meth base4x4() = brick( 4,4,1, true )
    @Piece
    meth base8x4() = brick( 8,4,1, true )
    @Piece
    meth base8x8() = brick( 8,8,1, true )



    override fun build() : Shape3d {
        // Use this to inspect/debug the holes.
        // return holes( 3, 2, 1, true )

        return arrangeX(
            arrangeY( brick1x1x1(), brick2x1x1(), brick3x1x1(), brick4x1x1(), brick5x1x1(), brick6x1x1() ),
            arrangeY(               brick2x2x1(), brick3x2x1(), brick4x2x1(), brick5x2x1(), brick6x2x1() ),
            arrangeY(               brick2x2x2(), brick3x2x2(), brick4x2x2(), brick5x2x2(), brick6x2x2() )
        )
    }

}