package Slic3r::Layer::Region;
use strict;
use warnings;

use Slic3r::ExtrusionPath ':roles';
use Slic3r::Flow ':roles';
use Slic3r::Geometry qw(scale);
use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex 
    );
use Slic3r::Surface ':types';


# TODO: lazy
sub infill_area_threshold {
    my $self = shift;
    return $self->flow(FLOW_ROLE_SOLID_INFILL)->scaled_spacing ** 2;
}

sub id      { return $_[0]->layer->id; }
sub slice_z { return $_[0]->layer->slice_z; }
sub print_z { return $_[0]->layer->print_z; }
sub height  { return $_[0]->layer->height; }
sub object  { return $_[0]->layer->object; }
sub print   { return $_[0]->layer->print; }

sub config  { return $_[0]->region->config; }

sub make_perimeters {
    my ($self, $slices, $fill_surfaces) = @_;
    
    $self->perimeters->clear;
    $self->thin_fills->clear;
    
    my $generator = Slic3r::Layer::PerimeterGenerator->new(
        # input:
        config              => $self->config,
        print_config        => $self->layer->print->config,
        layer_height        => $self->height,
        layer_id            => $self->layer->id,
        slices              => $slices,
        lower_slices        => defined($self->layer->lower_layer) ? $self->layer->lower_layer->slices : undef,
        perimeter_flow      => $self->flow(FLOW_ROLE_PERIMETER),
        ext_perimeter_flow  => $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER),
        overhang_flow       => $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object),
        solid_infill_flow   => $self->flow(FLOW_ROLE_SOLID_INFILL),
        
        # output:
        loops               => $self->perimeters,
        gap_fill            => $self->thin_fills,
        fill_surfaces       => $fill_surfaces,
    );
    $generator->process;
}

sub prepare_fill_surfaces {
    my $self = shift;
    
    # Note: in order to make the psPrepareInfill step idempotent, we should never
    # alter fill_surfaces boundaries on which our idempotency relies since that's
    # the only meaningful information returned by psPerimeters.
    
    # if no solid layers are requested, turn top/bottom surfaces to internal
    if ($self->config->top_solid_layers == 0) {
        $_->surface_type(S_TYPE_INTERNAL) for @{$self->fill_surfaces->filter_by_type(S_TYPE_TOP)};
    }
    if ($self->config->bottom_solid_layers == 0) {
        $_->surface_type(S_TYPE_INTERNAL)
            for @{$self->fill_surfaces->filter_by_type(S_TYPE_BOTTOM)}, @{$self->fill_surfaces->filter_by_type(S_TYPE_BOTTOMBRIDGE)};
    }
        
    # turn too small internal regions into solid regions according to the user setting
    if ($self->config->fill_density > 0) {
        my $min_area = scale scale $self->config->solid_infill_below_area; # scaling an area requires two calls!
        $_->surface_type(S_TYPE_INTERNALSOLID)
            for grep { $_->area <= $min_area } @{$self->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)};
    }
}

sub process_external_surfaces {
    my ($self, $lower_layer) = @_;
    
    my @surfaces = @{$self->fill_surfaces};
    my $margin = scale &Slic3r::EXTERNAL_INFILL_MARGIN;
    
    my @bottom = ();
    foreach my $surface (grep $_->is_bottom, @surfaces) {
        my $grown = $surface->expolygon->offset_ex(+$margin);
        
        # detect bridge direction before merging grown surfaces otherwise adjacent bridges
        # would get merged into a single one while they need different directions
        # also, supply the original expolygon instead of the grown one, because in case
        # of very thin (but still working) anchors, the grown expolygon would go beyond them
        my $angle;
        if ($lower_layer) {
            my $bridge_detector = Slic3r::BridgeDetector->new(
                $surface->expolygon,
                $lower_layer->slices,
                $self->flow(FLOW_ROLE_INFILL, $self->height, 1)->scaled_width,
            );
            Slic3r::debugf "Processing bridge at layer %d:\n", $self->id;
            $bridge_detector->detect_angle;
            $angle = $bridge_detector->angle;
            
            if (defined $angle && $self->object->config->support_material) {
                $self->bridged->append(Slic3r::ExPolygon->new($_))
                    for @{ $bridge_detector->coverage_by_angle($angle) };
                $self->unsupported_bridge_edges->append($_) for @{ $bridge_detector->unsupported_edges }; 
            }
        }
        
        push @bottom, map $surface->clone(expolygon => $_, bridge_angle => $angle), @$grown;
    }
    
    my @top = ();
    foreach my $surface (grep $_->surface_type == S_TYPE_TOP, @surfaces) {
        # give priority to bottom surfaces
        my $grown = diff_ex(
            $surface->expolygon->offset(+$margin),
            [ map $_->p, @bottom ],
        );
        push @top, map $surface->clone(expolygon => $_), @$grown;
    }
    
    # if we're slicing with no infill, we can't extend external surfaces
    # over non-existent infill
    my @fill_boundaries = $self->config->fill_density > 0
        ? @surfaces
        : grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
    
    # intersect the grown surfaces with the actual fill boundaries
    my @new_surfaces = ();
    foreach my $group (@{Slic3r::Surface::Collection->new(@top, @bottom)->group}) {
        push @new_surfaces,
            map $group->[0]->clone(expolygon => $_),
            @{intersection_ex(
                [ map $_->p, @$group ],
                [ map $_->p, @fill_boundaries ],
                1,  # to ensure adjacent expolygons are unified
            )};
    }
    
    # subtract the new top surfaces from the other non-top surfaces and re-add them
    my @other = grep $_->surface_type != S_TYPE_TOP && !$_->is_bottom, @surfaces;
    foreach my $group (@{Slic3r::Surface::Collection->new(@other)->group}) {
        push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex(
            [ map $_->p, @$group ],
            [ map $_->p, @new_surfaces ],
        )};
    }
    $self->fill_surfaces->clear;
    $self->fill_surfaces->append($_) for @new_surfaces;
}

1;
