package Slic3r::Model;
use Moo;

use Slic3r::Geometry qw(X Y Z);

has 'materials' => (is => 'ro', default => sub { {} });
has 'objects'   => (is => 'ro', default => sub { [] });

sub read_from_file {
    my $class = shift;
    my ($input_file) = @_;
    
    my $model = $input_file =~ /\.stl$/i            ? Slic3r::Format::STL->read_file($input_file)
              : $input_file =~ /\.obj$/i            ? Slic3r::Format::OBJ->read_file($input_file)
              : $input_file =~ /\.amf(\.xml)?$/i    ? Slic3r::Format::AMF->read_file($input_file)
              : die "Input file must have .stl, .obj or .amf(.xml) extension\n";
    
    $_->input_file($input_file) for @{$model->objects};
    return $model;
}

sub add_object {
    my $self = shift;
    
    my $object = Slic3r::Model::Object->new(model => $self, @_);
    push @{$self->objects}, $object;
    return $object;
}

sub set_material {
    my $self = shift;
    my ($material_id, $attributes) = @_;
    
    return $self->materials->{$material_id} = Slic3r::Model::Region->new(
        model       => $self,
        attributes  => $attributes || {},
    );
}

sub scale {
    my $self = shift;
    
    $_->scale(@_) for @{$self->objects};
}

# flattens everything to a single mesh
sub mesh {
    my $self = shift;
    
    my @meshes = ();
    foreach my $object (@{$self->objects}) {
        my @instances = $object->instances ? @{$object->instances} : (undef);
        foreach my $instance (@instances) {
            my $mesh = $object->mesh->clone;
            if ($instance) {
                $mesh->rotate($instance->rotation);
                $mesh->align_to_origin;
                $mesh->move(@{$instance->offset});
            }
            push @meshes, $mesh;
        }
    }
    
    return Slic3r::TriangleMesh->merge(@meshes);
}

package Slic3r::Model::Region;
use Moo;

has 'model'         => (is => 'ro', weak_ref => 1, required => 1);
has 'attributes'    => (is => 'rw', default => sub { {} });

package Slic3r::Model::Object;
use Moo;

use List::Util qw(first);
use Slic3r::Geometry qw(X Y Z);
use Storable qw(dclone);

has 'input_file' => (is => 'rw');
has 'model'     => (is => 'ro', weak_ref => 1, required => 1);
has 'vertices'  => (is => 'ro', default => sub { [] });
has 'volumes'   => (is => 'ro', default => sub { [] });
has 'instances' => (is => 'rw');
has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]

sub add_volume {
    my $self = shift;
    my %args = @_;
    
    if (my $vertices = delete $args{vertices}) {
        my $v_offset = @{$self->vertices};
        push @{$self->vertices}, @$vertices;
        
        @{$args{facets}} = map {
            my $f = [@$_];
            $f->[$_] += $v_offset for -3..-1;
            $f;
        } @{$args{facets}};
    }
    
    my $volume = Slic3r::Model::Volume->new(object => $self, %args);
    push @{$self->volumes}, $volume;
    return $volume;
}

sub add_instance {
    my $self = shift;
    
    $self->instances([]) if !defined $self->instances;
    push @{$self->instances}, Slic3r::Model::Instance->new(object => $self, @_);
    return $self->instances->[-1];
}

sub mesh {
    my $self = shift;
    
    # this mesh won't be suitable for check_manifoldness as multiple
    # facets from different volumes may use the same vertices
    return Slic3r::TriangleMesh->new(
        vertices => $self->vertices,
        facets   => [ map @{$_->facets}, @{$self->volumes} ],
    );
}

sub scale {
    my $self = shift;
    my ($factor) = @_;
    return if $factor == 1;
    
    # transform vertex coordinates
    foreach my $vertex (@{$self->vertices}) {
        $vertex->[$_] *= $factor for X,Y,Z;
    }
}

sub materials_count {
    my $self = shift;
    
    my %materials = map { $_->material_id // '_default' => 1 } @{$self->volumes};
    return scalar keys %materials;
}

sub check_manifoldness {
    my $self = shift;
    return (first { !$_->mesh->check_manifoldness } @{$self->volumes}) ? 0 : 1;
}

sub clone { dclone($_[0]) }

package Slic3r::Model::Volume;
use Moo;

has 'object'        => (is => 'ro', weak_ref => 1, required => 1);
has 'material_id'   => (is => 'rw');
has 'facets'        => (is => 'rw', default => sub { [] });

sub mesh {
    my $self = shift;
    return Slic3r::TriangleMesh->new(
        vertices => $self->object->vertices,
        facets   => $self->facets,
    );
}

package Slic3r::Model::Instance;
use Moo;

has 'object'    => (is => 'ro', weak_ref => 1, required => 1);
has 'rotation'  => (is => 'rw', default => sub { 0 });
has 'offset'    => (is => 'rw');

1;