package IO::Moose::FileOpenTest;

use strict;
use warnings;

use Test::Unit::Lite;
use parent 'Test::Unit::TestCase';

use Test::Assert ':all';

use IO::Moose::Handle;

use File::Spec;
use File::Temp;
use IO::File;

use Scalar::Util 'reftype', 'openhandle', 'tainted';

my ($filename_in, $filename_out, $obj, @filenames_out);

sub set_up {
    $filename_in = __FILE__;
    (undef, $filename_out) = File::Temp::tempfile( 'XXXXXXXX', DIR => File::Spec->tmpdir );
    push @filenames_out, $filename_out;

    $obj = IO::Moose::File->new;
    assert_isa('IO::Moose::Handle', $obj);
    assert_equals('GLOB', reftype $obj);
    assert_null(openhandle $obj->fh);
};

sub DESTROY {
    unlink foreach @filenames_out;
};

sub tear_down {
    unlink $filename_out;
};

sub test_open_default {
    $obj->open($filename_in);
    assert_not_null(openhandle $obj->fh);
    assert_equals($filename_in, $obj->file);

    assert_equals("package IO::Moose::FileOpenTest;\n", $obj->readline);
};

sub test_open_default_tied {
    open $obj, $filename_in;
    assert_not_null(openhandle $obj->fh);
    assert_equals($filename_in, $obj->file);

    assert_equals("package IO::Moose::FileOpenTest;\n", $obj->readline);
};

sub test_open_sysmode_tied {
    open $obj, $filename_in, 0, 0600;
    assert_not_null(openhandle $obj->fh);
    assert_equals($filename_in, $obj->file);

    assert_equals("package IO::Moose::FileOpenTest;\n", $obj->readline);
};

sub test_open_write {
    $obj->open($filename_out, '+>');
    assert_not_null(openhandle $obj->fh);
    assert_equals($filename_out, $obj->file);
    assert_equals("+>", $obj->mode);

    $obj->print("test_new_open_write\n");

    assert_true($obj->seek(0, 0));
    assert_equals("test_new_open_write\n", $obj->readline);
};

sub test_open_write_tied {
    open $obj, $filename_out, '+>';
    assert_not_null(openhandle $obj->fh);
    assert_equals($filename_out, $obj->file);
    assert_equals("+>", $obj->mode);

    $obj->print("test_new_open_write");

    assert_not_null($obj->seek(0, 0));
    assert_equals("test_new_open_write", $obj->readline);
};

sub test_open_layer {
    $obj->open($filename_out, '+>:crlf');
    assert_not_null(openhandle $obj->fh);
    assert_equals($filename_out, $obj->file);
    assert_equals("+>", $obj->mode);
    assert_equals(":crlf", $obj->layer);
};

sub test_open_error_io {
    assert_raises( ['Exception::IO'], sub {
        $obj->open('nosuchfile_abcdef'.$$);
    } );
};

sub test_open_error_args {
    assert_raises( ['Exception::Argument'], sub {
        IO::Moose::File->open($filename_in);
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->open;
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->open($filename_in, ':perlio', 3);
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->open($filename_in, 0, 'string')
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->open(\123);
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->open($obj);
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->open(\*STDIN);
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->open($filename_in, 'r', 3);
    } );

    assert_raises( qr/does not pass the type constraint/, sub {
        $obj->open($filename_in, 0);
    } );

    assert_raises( qr/does not pass the type constraint/, sub {
        $obj->open($filename_in, 'badmode');
    } );
};

sub test_sysopen {
    $obj->sysopen($filename_in, 0);
    assert_not_null(openhandle $obj->fh);
    assert_equals($filename_in, $obj->file);
    assert_num_equals(0, $obj->mode);

    assert_equals("package IO::Moose::FileOpenTest;\n", $obj->readline);
};

sub test_sysopen_perms {
    $obj->sysopen($filename_in, 0, 0111);
    assert_not_null(openhandle $obj->fh);
    assert_equals($filename_in, $obj->file);
    assert_num_equals(0, $obj->mode);
    assert_num_equals(0111, $obj->perms);
};

sub test_open_syserror_io {
    assert_raises( ['Exception::IO'], sub {
        $obj->sysopen('nosuchfile_abcdef'.$$, 0);
    } );
};

sub test_sysopen_error_args {
    assert_raises( ['Exception::Argument'], sub {
        IO::Moose::File->sysopen($filename_in, 0);
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->sysopen;
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->sysopen(\*STDIN, 0);
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->open($filename_in, 0, 0600, 4);
    } );

    assert_raises( qr/does not pass the type constraint/, sub {
        $obj->sysopen($filename_in, 'r');
    } );

    assert_raises( qr/does not pass the type constraint/, sub {
        $obj->sysopen($filename_in, 0, 'STRING');
    } );
};

sub test_binmode {
    $obj->open($filename_out, '+>');
    assert_not_null(openhandle $obj->fh);

    $obj->binmode;

    $obj->print("\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020");
    assert_true($obj->seek(0, 0));

    my $c;
    $obj->read($c, 17);
    assert_equals("\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020", $c);
};

sub test_binmode_tied {
    $obj->open($filename_out, '+>');
    assert_not_null(openhandle $obj->fh);

    binmode $obj;

    $obj->print("\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020");
    assert_not_null($obj->seek(0, 0));

    my $c;
    $obj->read($c, 17);
    assert_equals("\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020", $c);
};

sub test_binmode_layer {
    $obj->open($filename_out, '+>');
    assert_not_null(openhandle $obj->fh);

    $obj->binmode(':crlf');
    assert_equals(":crlf", $obj->layer);

    $obj->print("\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020");
    assert_not_null($obj->seek(0, 0));

    my $c;
    $obj->read($c, 17);
    assert_equals("\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020", $c);
};

sub test_binmode_layer_tied {
    $obj->open($filename_out, '+>');
    assert_not_null(openhandle $obj->fh);

    binmode $obj, ':crlf';
    assert_equals(":crlf", $obj->layer);

    $obj->print("\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020");
    assert_not_null($obj->seek(0, 0));

    my $c;
    $obj->read($c, 17);
    assert_equals("\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020", $c);
};

sub test_binmode_error_io {
    $obj->open($filename_out, '+>');
    assert_not_null(openhandle $obj->fh);

    $obj->close;

    assert_raises( ['Exception::Fatal'], sub {
        $obj->binmode(':crlf');
    } );
};

sub test_binmode_error_args {
    assert_raises( ['Exception::Argument'], sub {
        IO::Moose::File->binmode(':crlf');
    } );

    assert_raises( ['Exception::Argument'], sub {
        $obj->binmode(':crlf', 2);
    } );

    assert_raises( qr/does not pass the type constraint/, sub {
        $obj->binmode('badlayer');
    } );
};

sub test_slurp_wantscalar_object {
    $obj->open($filename_in);
    assert_equals($filename_in, $obj->file);
    assert_not_null(openhandle $obj->fh);

    if (${^TAINT}) {
        $obj->untaint;
    };

    my $c = $obj->slurp;
    assert_true(length $c > 1, 'length $c > 1');
    assert_true($c =~ tr/\n// > 1, '$c =~ tr/\n// > 1');

    if (${^TAINT}) {
        assert_false(tainted $c);
    };

    $obj->close;
};

sub test_slurp_wantarray_object {
    $obj->open($filename_in);
    assert_equals($filename_in, $obj->file);
    assert_not_null(openhandle $obj->fh);

    if (${^TAINT}) {
        $obj->untaint;
    };

    my @c = $obj->slurp;
    assert_true(@c > 1, '@c > 1');
    assert_true($c[0] =~ tr/\n// == 1, '$c[0] =~ tr/\n// == 1');

    if (${^TAINT}) {
        assert_false(tainted $c[0]);
    };

    $obj->close;
};

1;
