include <BOSL2/std.scad>;
include <BOSL2/beziers.scad>;
include <BOSL2/rounding.scad>;
include <BOSL2/skin.scad>;
include <BOSL2/threading.scad>;

include <DotScad/src/bend.scad>;
include <DotScad/src/bend_extrude.scad>;
//include <DotScad/src/bezier_curve>;
include <DotScad/src/ellipse_extrude.scad>;
include <DotScad/src/loft.scad>;

include <scad-utils/morphology.scad>;
include <common.scad>

include <./lib/chinook.scad> // US Box


// https://github.com/BelfrySCAD/BOSL2/wiki/Tutorial-Beziers_for_Beginners
// ideas  offset_stroke 

/**
 * Kitesurf Fins mold
 *
 *
 *
 * Requirements : 
 * - The plunger normaly should be twice the height of the part 
 *
 */

/************************************************/
/*  Parameters                                  */
/************************************************/

/* [Fin Specs] */

// Height
fin_height       = 254;      // 10 inches in mm
// Top Width
fin_width        = 270;     // Width at the top in mm
// Base Width
fin_base         = 130;     // Width at the base in mm


// Define top point
fin_top_withdraw    = 35;      
fin_edge_withdraw   = 20;

fin_start_angle  = 70; // Angle [Point 0 ]
fin_sweep        = 25; // Sweep Angle [Point 1] 

fin_edge_angle  = 30;
fin_edge_strength  = 30;

fin_counter_angle = 30;     // counter angle [Point 2] 
fin_counter_strength = 10;  // Length [Point 2] 

// Length at the base in mm



fin_width_tip   = 5; // Width at the tip in mm
fin_thickness   = 9; // Thickness of the fin in mm

fin_end_angle   = 110;
fin_end_strength   = 30;

// Back
fin_back_height = 30; // Percent height
fin_back_widthdraw = 20; // Percent height
fin_back_angle = 20; // back angle
fin_back_strength=30;

/* [Base Specs] */

base_tickness=8; 
base_extra_thickness=40;


// ---------------------*/
/*        [Mold]        */
// ---------------------*/

// Mold base height
mold_base_height        = 10;
mold_top_height         = 5;
mold_extra_width        = 15;

mold_piston_height      = 10;

resin_escape_diameter   = 4; 

// Diameter for screw holes in mm
merge_holes_diameter = 6;
piston_depth = 5;

// ---------------------*/
/*        [US Box]        */
// ---------------------*/
/* [US Box] */
// Dimensions in mm
//length = 180;
//length=fin_base;
height = 25;
thick  = 9.2;
mirror_vec = [1,1,0];

//screw_pos = 0.0096*1000;
screw_pos = 9.6;
//screw_dia = 0.0045*1000;
screw_dia = 4.5;

//tab_height = 0.013*1000;
tab_height = 13;
tab_length = screw_pos * 2;
tab_round  = tab_height;

// real pin: d=0.0048 -> drill to fit
//pin_dia   = 0.003*1000;
pin_dia   = 3;
//pin_depth = 0.0164*1000;
pin_depth = 16.4;
//pin_back  = 0.009*1000;
pin_back  = 9;

// Thickness to cut for easier sliding (0 for no cut)
//thick_cut = 0.001*1000;
thick_cut = 1;

// ---------------------*/
/*     [Debugging]      */
// ---------------------*/

// Showing all layers
show_debug_layers = false;

show_curve_points = false;

// Draw master profile
draw_profile = false;

// ---------------------*/
/*        [Rendering]   */
// ---------------------*/

// Rendering parts
build_fin       = false;
build_box       = false;
build_mold     = true;
render_drill_template     = false;

parts           = "all";      // [all, top, bottom]


mold_part       = "all";   // [all, top, bottom]
// Scaling
scale_factor    = 1.0; // [0.1:0.1:2] 

// Printable rendering
printable       = true;

$fn=64;

/*************/
/* Constants */
/*************/
/* [Hidden] */ 
// Define the offset value as a constant
OFFSET = 0.01;
CLEARING = 0.1;
INCH=2.54;
X=0;
Y=1;
Z=2;

/************************************************/
/* Derived variable */

mold_width  = fin_height +2 * mold_extra_width;
mold_length = fin_width  +2 * mold_extra_width;
mold_height = mold_base_height;

drilling_length = mold_base_height+mold_piston_height+2*OFFSET;

start               = [ 0                ,0                              ];
top_point           = [ fin_width-fin_top_withdraw,fin_height            ];
edge_point          = [ fin_width        ,fin_height-fin_edge_withdraw   ];
counter_edge_point  = [ fin_width -70    ,fin_height-100                 ];
tail_point          = [ fin_width -95    ,fin_height-175                 ];
end_point           = [ fin_base         ,0                              ];



/******************/
/* Profile points */
/******************/
points = flatten([
    // *****************
    //   INITIAL POINT
    // *****************    
    bez_begin(start,70,80),
    // *****************
    //   TOP POINT
    // *****************    
    bez_tang(top_point,0,80,15),
    // *****************
    //   EDGE POINT
    // *****************    
    bez_tang(edge_point,-90,15),
    // **********************
    //   COUNTER EDGE POINT
    // **********************
    bez_tang(counter_edge_point,-120,30),
    // *****************
    //   TAIL Point
    // ***************** 
    bez_tang(tail_point,-120,40),
    // *****************
    //   END POINT                                                               
    // *****************   
    bez_end(end_point,75,40),    
]);

profile_curve_with_base  = translate_path( addBase(asCurve(points,32)),-fin_base/2,0 );
profile  = translate_path( asCurve(points,32),-fin_base/2,0 );

//profile_curve_with_base  = translate_path( asCurve(points,32),-fin_base/2,0 );
//profile_curve_with_base  = resample_path(translate_path( asCurve(points,32),-fin_base/2,0 ),64);

assert(is_path_simple(profile_curve_with_base));


if (show_curve_points)
    color("Blue")move_copies(profile_curve_with_base) circle($fn=16);


// Draw fin profile
if (draw_profile) {
    //left(425) drawProfile( points, true  );
    
    
    layer0 = expandPath(profile_curve_with_base,-2);
    layer1 = expandPath(profile_curve_with_base,-8);
    layer2 = expandPath(profile_curve_with_base,-15);
    layer3 = expandPath(profile_curve_with_base,-30);
    layer4 = expandPath(profile_curve_with_base,-40);
    
    back(10) showDebugPath(layer0);
    
    
    left(500) {
        color("Red") polygon(profile_curve_with_base);
        up(10) polygon(layer0);
        up(20) color("Yellow") polygon(layer1);
        up(30) color("Brown") polygon(layer2);
    }
    
    layers = [profile_curve_with_base,layer0,layer1,layer2,layer3,layer4];
    
    left (800) color ("Green") polygon(profile_curve_with_base);
    
       left (1000) color ("Green") shell(10) polygon(profile_curve_with_base);
    
}    





// *****************
// *  Fin Drawing  *
// *****************
if ( build_fin ) 
    scale([scale_factor, scale_factor, scale_factor]) 
        buildFin( fin_thickness );

if ( build_box ) {
      buildBox();
} 
        
/**
 * Render Mold
 */
if ( build_mold )              buildMold();    
if ( render_drill_template )    buildDrillTemplate();


module buildCompletFin() {
    union(){
        buildFin( fin_thickness );
        buildBox();
    }
}


module buildBox() {
    left(80) {
        xflip() {     
            finfit(fin_base+20, [1,1,0],false,false);
        }        
    
        //up(20) tab_cut();
    }
        
}
module buildBoxMold() {
    zrot(-90) chinook_profile(fin_base+20);
    zrot(-90) up(-2) offset(chinook_profile(fin_base+20),delta=5);
}


/**
 * Build mold
 * 
 */
module buildMold() {
    moldHeight = 10;
    skirtHeight = 30;

    
    moldContour =  expandPath(profile,mold_extra_width);
    buildMoldSkirt(skirtHeight,mold_extra_width,3);
    
    if (mold_part != "top") {
        difference() {
            union() {
                down(moldHeight) linear_extrude (moldHeight) polygon(moldContour);  // bottom 
                //back(5) left(95) cube([20+10,25,moldHeight],anchor=BACK+TOP);          // Box shell
                
                up(5) color("Yellow") left(80) buildBoxMold();
                
            };
            buildCompletFin();
        }
    }
    
    //left(300) linear_extrude (skirtHeight) polygon(moldSkirtRegion);
    /*
    if (mold_part !=  "top") 
        difference() { 
            case(mold_base_height,true);
            // Piston
            down(piston_depth-OFFSET) fwd(20) bottomInsert(); 
        }
    // Top Mold
    if (mold_part !=  "bottom") 
        up(printable ? 0 : 60) fwd(printable ? fin_height+50 : 0) down(mold_base_height) mirror([0,0,printable ? 1 : 0 ])  {
            difference() {
                union() {
                    // case
                    case(mold_top_height,false); 
                    // Piston
                    mirror([1,0,0])        
                    down(mold_top_height+piston_depth-OFFSET) fwd(20) topPiston(); 
                };
                resin_escape(40);
            }
        }      
    */    
}
module buildMoldSkirt( height,offset,thickness ) {
    outside = expandPath(profile,offset);
    inside = expandPath(profile,offset-thickness);
    down(height) {
        // Skirt
        linear_extrude (height-thick/2) difference() {
            polygon(outside);
            polygon(inside);
        };
        difference() {
        //union() {
            intersection() {
                ycopies(30,10,sp=[-100,0,0]) 
                    cube([fin_width*1.3,thickness,height]);
                linear_extrude (height) polygon(outside);
            };
            up(height) buildCompletFin();    
        }
        
        
    }
}

/**
 * Mold Case 
 *
 * @param height - Height of the case
 * @param bottom - Is bottom or top 
 */
module case( height , bottom=true ) {
    difference() {
        diff() cuboid( [ mold_length, mold_width, height ],
            rounding=4,
            edges=[bottom ? BOTTOM: TOP],
            anchor=TOP ){
            // Drills
            align(BOTTOM,[FRONT+LEFT,FRONT+RIGHT,BACK+LEFT,BACK+RIGHT],inset=10,overlap=-OFFSET)
                color("orange") 
                    tag("remove") 
                        drilling();
        };
        // Material holes
        mirror([bottom ? 0:1,0,0])
        back(38) left(55)
                color("Red")
                    up(OFFSET)
                        rounded_triangle(radius=3, height=height+2*OFFSET);              
        // Length hole                    
        fwd(32) cuboid([120,10,50],rounding=5);
        // Side holes
        left(67) cuboid([10,40,50],rounding=5);
        right(67) cuboid([10,40,50],rounding=5);
   }
}

/**
 * Drill template
 * 
 */ 
module buildDrillTemplate() {
    
    spacing         = 38.5;
    drill_depth     = 15;
    drill_diameter  = 6;
    template_height  = 5;
    
    p1 = 20;
    p2 = p1-spacing;
    
    mirror([0,0,printable ? 1 : 0]) difference() {
    
        left(1) color ("Orange") cuboid([fin_width+7,15,10],rounding=3);
        down(OFFSET) {
            hull(){
                left(28) cylinder(h=template_height+2*OFFSET,r=drill_diameter/2);
                left(55) cylinder(h=template_height+2*OFFSET,r=drill_diameter/2);
            }
            hull(){
                left(12) cylinder(h=template_height+2*OFFSET,r=drill_diameter/2);
                left(-10) cylinder(h=template_height+2*OFFSET,r=drill_diameter/2);
            }
            hull(){
                left(-27) cylinder(h=template_height+2*OFFSET,r=drill_diameter/2);
                left(-53) cylinder(h=template_height+2*OFFSET,r=drill_diameter/2);
            }
        }
        xrot(-90) buildFin(fin_thickness + 2*CLEARING );
        //left(60) cylinder(h=20,r=6/2);
        up(template_height+OFFSET)
        {
            left(p1) 
                cylinder(h=drill_depth+template_height,r=drill_diameter/2,orient=DOWN);
            left(p2) 
                cylinder(h=drill_depth,r=drill_diameter/2,orient=DOWN);
        }
    }
}


// *****************
// * Bottom insert *
// *****************
module bottomInsert() {
    debug=true;
    layer_profile       = profile_curve_with_base; 
    skin( [ layer_profile,expandPath(layer_profile,3) ], z=[0,piston_depth], slices=0 )
       up(OFFSET) 
        buildFinSide(true)
        ;
}

// *****************
// * Top piston    *
// *****************
module topPiston() {
    debug=true;
    layer_profile       = profile_curve_with_base; 
    difference() {
        skin( [ layer_profile,expandPath(layer_profile,3) ], z=[0,piston_depth], slices=0 );
        down(OFFSET)
        buildFinSide(false);
    }
}

/**
 * Build fin
 *
 */ 
module buildFin( thickness ) {
    union() {
        if (parts != "bottom") buildFinSide( thickness );
        if (parts != "top")    buildFinSide( thickness , true );
    }
}

/**
 * Build fin side
 * 
 * @param flip - Define it it is top or bottom fin side
 */
module buildFinSide(thickness,flip=false) {

    //profile_curve_with_base1 = subdivide_path (profile_curve_with_base,500);
    
    //if (false)
    mirror([0,0,flip ? 1 : 0])
        color(flip ? "Gray" : "LightGray")
        ellipse_extrude( thickness / 2 /*,height=2*/,center=false,twist=-7) 
            //zrot(30)
            polygon( profile_curve_with_base);
} 

module resin_escape(length) { 
    color ("Blue") cylinder(h=length, r=resin_escape_diameter/2);
}

// *****************
// *  Drilling     *
// *****************
module drilling(){
    /*fwd(20)*/ color("Red") /*up(mold_piston_height+5)*/ up(-OFFSET)
    orient(DOWN)
        threaded_rod(
            d=merge_holes_diameter, 
            l=drilling_length, 
            pitch=1.5, 
            anchor=BOTTOM,
            //orient=BOTTOM

        );
}

function addBase(path) = concat(path,[[fin_base,-base_extra_thickness],[0,-base_extra_thickness]]);

    
// Function to create a parallel bezier curve offset by a given amount
function offset_bezier_points(points, offset_distance, normal=[0,0,1]) =
    [for (pt = points) pt + offset_distance * normal];

// Function to create multiple parallel bezier curves
function create_patch_points(base_points, width, steps=4) =
    [for (i = [0:1:steps]) 
        let(
            t = i/steps,
            offset = width * t
        )
        offset_bezier_points(base_points, offset)
    ];

// Create the bezier patch VNF
function make_bezier_patch_from_curve(base_points, width, length_steps=32, width_steps=8) =
    let(
        // Generate parallel curves to form patch
        patch_points = create_patch_points(base_points, width, width_steps),
        // Generate points for each curve
        curves_points = [for (curve_pts = patch_points)
            bezier_points(curve_pts, length_steps)
        ],
        // Create vertices for VNF
        vertices = flatten(curves_points),
        // Calculate indices for faces
        indices = [
            for (i=[0:1:width_steps-1], j=[0:1:length_steps-1])
            let(
                idx00 = i*(length_steps+1) + j,
                idx10 = (i+1)*(length_steps+1) + j,
                idx11 = (i+1)*(length_steps+1) + (j+1),
                idx01 = i*(length_steps+1) + (j+1)
            )
            each [[idx00, idx10, idx11, idx01]]
        ]
    )
    [vertices, indices];
    
    
if (false) {

    //vnf = bezier_vnf(profile_curve_with_base, splinesteps=10);
    
    /*
    vnf = make_bezier_patch_from_curve(points, width=10);
    back (100) {
        cube([20,20,20]);
        vnf_polyhedron(vnf);
    } 
  */  
  /*
  polygon(profile_curve_with_base);
        vnf2 =vnf_from_region(polygon(profile_curve_with_base));
    //vnf2 = vnf_from_polygons(profile_curve_with_base, fast=true);
    vnf_polyhedron(vnf2);    
    */
    
    //curve_points = bezier_curve(points,N=3, splinesteps=32); 
    curve_points = bezpath_curve(points,N=3, splinesteps=32); 
    curve_points2 = [ for(p = curve_points) [p.x, p.y, 0]]; // Offset by 1 in Z
    //echo ("curve_points",curve_points);
    curve_points_offset = [ for(p = curve_points) [p.x, p.y, (p.z != undef ? p.z : 0) + 1 ]]; // Offset by 1 in Z

// Combine original and offset points to form a 2D array for a simple patch
    patch_points = [
        curve_points,
        curve_points_offset
    ];
    //echo ("patch_points",patch_points);
    //echo ("curve_points_offset",curve_points_offset);
    echo ("curve_points2",curve_points2);
    echo ("length curve_points2",len(curve_points2));
   
    //vnf_5 = bezier_vnf(patch_points, caps=false, splinesteps=10);
    //vnf_5 = bezier_vnf(curve_points2, splinesteps=10);
    echo ("is_bezier_patch",is_bezier_patch(curve_points));
    echo ("is_bezier_patch",is_bezier_patch(curve_points2));
    
    //vnf_5 = bezier_vnf(curve_points_offset, splinesteps=10);
    

}    

/**
 * Draws a profile based on Bezier path points with optional debug visualization.
 * 
 * @param points - Array of points defining the Bezier path.
 * @param debug - Boolean flag to enable/disable debug visualization. Defaults to true.
 * 
 * This module:
 * - Calculates the closest point on the Bezier path to a fixed point.
 * - Draws the Bezier path with debug information if debug is true.
 * - Optionally shows spheres at specific points for debugging (currently commented out).
 */
module drawProfile( points,debug = true ){


    debugPoint (start,      "Start",    "Red"   );
    debugPoint (top_point,  "Top",      "Blue"  );
    debugPoint (edge_point, "Edge",     "Brown" );
    debugPoint (counter_edge_point,     "Counter Edge", "Brown" );
    debugPoint (tail_point, "Tail",     "Yellow" );
    debugPoint (end_point,  "End",      "Yellow" );
    
    //pt  = [100,0];
    //pos = bezpath_closest_point(points, pt);
    //xy  = bezpath_points(points,pos[0],pos[1]);
    
    debug_bezier(points, N=3,width=1.2);
}