# Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Contact information: Carbonite Inc., 756 N Pastoria Ave
# Sunnyvale, CA 94085, or: http://www.zmanda.com

package Amanda::Rest::Storages::Labels;
use strict;
use warnings;

use Amanda::Config qw( :init :getconf config_dir_relative );
use Amanda::Device qw( :constants );
use Amanda::Storage;
use Amanda::Changer;
use Amanda::Header;
use Amanda::MainLoop;
use Amanda::Tapelist;
use Amanda::Label;
use Amanda::Util qw( match_datestamp );
use Amanda::Rest::Configs;
use Symbol;
use Data::Dumper;
use Scalar::Util;
use vars qw(@ISA);

=head1 NAME

Amanda::Rest::Storages::Labels -- Rest interface to manage label

=head1 INTERFACE

=over

=item List labels of a storage

You can use the /amanda/v1.0/configs/:CONF/labels endpoint and filter with the storage.

 request:
  GET /amanda/v1.0/configs/:CONF/storages/:STORAGE/labels
  You can filter the labels listed with the following query arguments:
            config=CONF
            datestamp=datastamp_range
            storage=STORAGE
            meta=META
            pool=POOL
            reuse=0|1

 reply:
  HTTP status: 200 OK
  [
     {
        "code" : "1600001",
        "message" : "List of labels",
        "severity" : "16",
        "source_filename" : "/usr/lib/amanda/perl/Amanda/Rest/Storages/Labels.pm",
        "source_line" : "245",
        "tles" : [
           {
              "barcode" : null,
              "blocksize" : "32",
              "comment" : null,
              "config" : "test",
              "datestamp" : "20131118134146",
              "label" : "test-ORG-AA-vtapes-002",
              "meta" : "test-ORG-AA",
              "pool" : "my_vtapes",
              "position" : 45,
              "reuse" : "1",
              "storage" : "my_vtapes"
           }
	   ...
        ]
     }
  ]

=item List one label

 request:
  GET /amanda/v1.0/configs/:CONF/storages/:STORAGE/labels/:LABEL

 reply:
  HTTP status: 200 OK
  [
     {
        "code" : "1600001",
        "message" : "List of labels",
        "severity" : "16",
        "source_filename" : "/usr/lib/amanda/perl/Amanda/Rest/Storages/Labels.pm",
        "source_line" : "245",
        "tles" : [
           {
              "barcode" : null,
              "blocksize" : "32",
              "comment" : null,
              "config" : "test",
              "datestamp" : "20131118134146",
              "label" : "test-ORG-AA-vtapes-002",
              "meta" : "test-ORG-AA",
              "pool" : "my_vtapes",
              "position" : 45,
              "reuse" : "1",
              "storage" : "my_vtapes"
           }
        ]
     }
  ]

=item Modify label setting

 request:
  POST /amanda/v1.0/configs/:CONF/storages/:STORAGE/labels/:LABEL
    query arguments:
        force=0|1
        config=CONFIG
        storage=STORAGE
        meta=META
        barcode=BARCODE
        comment=COMMENT
        reuse=0|1

 reply:
  HTTP status: 200 OK
  [
     {
        "code" : "1000045",
        "label" : "test-ORG-AC-vtapes2-001",
        "message" : "marking tape 'test-ORG-AC-vtapes2-001' as reusable.",
        "severity" : "16",
        "source_filename" : "/amanda/h1/linux/lib/amanda/perl/Amanda/Label.pm",
        "source_line" : "1315"
     },
     {
        "code" : "1000006",
        "label" : "test-ORG-AC-vtapes2-001",
        "message" : "Setting test-ORG-AC-vtapes2-001",
        "severity" : "16",
        "source_filename" : "/amanda/h1/linux/lib/amanda/perl/Amanda/Label.pm",
        "source_line" : "408"
     }
  ]

=item Label a volume

 request:
  POST /amanda/v1.0/configs/:CONF/storages/:STORAGE/labels

 reply:
  HTTP status: 200 OK
  [
     {
        "code" : "1000008",
        "message" : "Reading label...",
        "severity" : "16",
        "source_filename" : "/usr/lib/amanda/perl/Amanda/Label.pm",
        "source_line" : "513"
     },
     {
        "code" : "1000009",
        "message" : "Found an empty tape.",
        "severity" : "16",
        "source_filename" : "/usr/lib/amanda/perl/Amanda/Label.pm",
        "source_line" : "574"
     },
     {
        "code" : "1000020",
        "label" : "test-ORG-AA-vtapes-002",
        "message" : "Writing label 'test-ORG-AA-vtapes-002'...",
        "severity" : "16",
        "source_filename" : "/usr/lib/amanda/perl/Amanda/Label.pm",
        "source_line" : "707"
     },
     {
        "code" : "1000021",
        "message" : "Checking label...",
        "severity" : "16",
        "source_filename" : "/usr/lib/amanda/perl/Amanda/Label.pm",
        "source_line" : "727"
     },
     {
        "code" : "1000022",
        "message" : "Success!",
        "severity" : "16",
        "source_filename" : "/usr/lib/amanda/perl/Amanda/Label.pm",
        "source_line" : "769"
     }
  ]

=item Remove a label

 request:
  DELETE /amanda/v1.0/configs/:CONF/storages/:STORAGE/labels
  DELETE /amanda/v1.0/configs/:CONF/storages/:STORAGE/labels/:LABEL
    query arguments:
        remove_no_retention
        labels=LABEL1   # can be repeated
        cleanup
        dry_run
        erase
        external_copy
        keep_label

 reply:
  HTTP status: 200 OK
  [
    {
        "code" : "1000052",
        "label" : "test-ORG-AA-vtapes-002",
        "message" : "Removed label 'test-ORG-AA-vtapes-002 from tapelist file.",
        "severity" : "16",
        "source_filename" : "/usr/lib/amanda/perl/Amanda/Label.pm",
        "source_line" : "1070",
        "tapelist_filename" : "/usr/amanda/test/tapelist"
     }
  ]

=back

=cut

sub init {
    my %params = @_;

    my $filename = config_dir_relative(getconf($CNF_TAPELIST));

    my ($tl, $message) = Amanda::Tapelist->new($filename);
    if (defined $message) {
	return (404, $message);
    } elsif (!defined $tl) {
	return (404,
		Amanda::Tapelist::Message->new(
				source_filename => __FILE__,
				source_line     => __LINE__,
				code => 1600000,
				severity => $Amanda::Message::ERROR,
				tapefile => $filename))
    }
    return (-1, $tl);
}

sub label {
    my %params = @_;

    Amanda::Util::set_pname("Amanda::Rest::Storages::Labels");
    my ($status, @result_messages) = Amanda::Rest::Configs::config_init(@_);
    return ($status, @result_messages) if @result_messages;

    my $tl = Amanda::Rest::Labels::init();
    if ($tl->isa("Amanda::Message")) {
	push @result_messages, $tl;
	return (-1, \@result_messages);
    }

    my @tles = grep {$_->{'label'} eq $params{'LABEL'}} @{$tl->{'tles'}};
    @tles = grep {$_->{'storage'} eq $params{'STORAGE'}} @tles if $params{'storage'};
    @tles = grep {$_->{'config'}  eq $params{'config'}}  @tles if $params{'config'};
    @tles = grep {$_->{'storage'} eq $params{'storage'}} @tles if $params{'storage'};
    @tles = grep {$_->{'pool'}    eq $params{'pool'}}    @tles if $params{'pool'};
    @tles = grep {$_->{'meta'}    eq $params{'meta'}}    @tles if $params{'meta'};
    @tles = grep {$_->{'reuse'}   eq $params{'reuse'}}   @tles if $params{'reuse'};
    push @result_messages, Amanda::Tapelist::Message->new(
				source_filename => __FILE__,
				source_line     => __LINE__,
				code => 1600001,
				severity => $Amanda::Message::SUCCESS,
				tles => \@tles);
    return ($status, \@result_messages);
}

sub erase {
    my %params = @_;
    Amanda::Util::set_pname("Amanda::Rest::Storages::Labels");
    my ($status, @result_messages) = Amanda::Rest::Configs::config_init(@_);
    return ($status, @result_messages) if @result_messages;

    my $user_msg = sub {
	my $msg = shift;
	push @result_messages, $msg;
    };

    my $main = sub {
	my $finished_cb = shift;

	my $steps = define_steps
	    cb_ref => \$finished_cb;

	step start => sub {

	    # amadmin may later try to load this and will die if it has errors
	    # load it now to catch the problem sooner (before we might erase data)
	    my $diskfile = config_dir_relative(getconf($CNF_DISKFILE));
	    my $cfgerr_level = Amanda::Disklist::read_disklist('filename' => $diskfile);
	    if ($cfgerr_level >= $CFGERR_ERRORS) {
		push @result_messages, Amanda::Message->new(
				source_filename => __FILE__,
				source_line     => __LINE__,
				code		=> 1,
				severity => $Amanda::Message::ERROR,
				message => "Errors processing disklist");
		$steps->{'done'}->();
	    }

            my $tl = Amanda::Rest::Labels::init();
	    if ($tl->isa("Amanda::Message")) {
		return $steps->{'done'}->($tl);
	    }

	    my $Label = Amanda::Label->new(tapelist => $tl,
					   user_msg => $user_msg);

	    my @labels;
	    if (exists $params{'remove_no_retention'}) {
		@labels = Amanda::Tapelist::list_no_retention();
	    } elsif ($params{'labels'}){
		my $type = Scalar::Util::reftype($params{'labels'});
		if (defined $type and $type eq "ARRAY") {
		    @labels = @{$params{'labels'}};
		} elsif (!defined $type and defined $params{'labels'} and $params{'labels'} ne '') {
		    @labels = ($params{'labels'});
		}
	    } elsif ($params{'LABEL'}){
		@labels = ($params{'LABEL'});
	    }
	    if (!@labels) {
		push @result_messages, Amanda::Config::Message->new(
				source_filename => __FILE__,
				source_line     => __LINE__,
				code   => 1500015,
				severity => $Amanda::Message::ERROR);
		return $steps->{'done'}->();
	    }

	    my $storage = $params{'storage'} || $params{'STORAGE'};
	    $tl->reload(1);
	    $Label->erase(labels        => \@labels,
			  storage       => $storage,
			  cleanup       => $params{'cleanup'},
			  dry_run       => $params{'dry_run'},
			  erase         => $params{'erase'},
			  external_copy => $params{'external_copy'},
			  keep_label    => $params{'keep_label'},
			  finished_cb   => $steps->{'done'});
	};

	step done => sub {
	    my $err = shift;

	    push @result_messages, $err if $err;

	    $finished_cb->();
	};

    };


    $main->(\&Amanda::MainLoop::quit);
    Amanda::MainLoop::run();
    $main = undef;

    return ($status, \@result_messages);
}

sub add_label {
    my %params = @_;
    Amanda::Util::set_pname("Amanda::Rest::Storages::Labels");
    my ($status, @result_messages) = Amanda::Rest::Configs::config_init(@_);
    return ($status, @result_messages) if @result_messages;

    my $user_msg = sub {
        my $msg = shift;
        push @result_messages, $msg;
    };

    my $main = sub {
	my $finished_cb = shift;
	my $storage;
	my $chg;

	my $steps = define_steps
	    cb_ref => \$finished_cb,
	    finalize => sub { $storage->quit() if defined $storage;
			      $chg->quit() if defined $chg };

	step start => sub {
	    my $tl = Amanda::Rest::Labels::init();
	    if ($tl->isa("Amanda::Message")) {
		return $steps->{'done'}->($tl);
	    }
	    $storage = Amanda::Storage->new(tapelist => $tl,
					    storage_name => $params{'STORAGE'});
	    if ($storage->isa("Amanda::Changer::Error")) {
		return $steps->{'done'}->($storage);
	    }

	    $chg = $storage->{'chg'};
	    if ($chg->isa("Amanda::Changer::Error")) {
		return $steps->{'done'}->($chg);
	    }

	    my $Label = Amanda::Label->new(storage  => $storage,
					   tapelist => $tl,
					   user_msg => $user_msg);

	    $Label->label(slot    => $params{'slot'},
			  label   => $params{'label'},
			  meta    => $params{'meta'},
			  force   => $params{'force'},
			  barcode => $params{'barcode'},
			  storage => $params{'STORAGE'},
			  finished_cb => $steps->{'done'});
	};

	step done => sub {
	    my $err = shift;

	    $finished_cb->();
	};

    };

    $main->(\&Amanda::MainLoop::quit);
    Amanda::MainLoop::run();
    $main = undef;

    return ($status, \@result_messages);
}

sub update_label {
    my %params = @_;
    Amanda::Util::set_pname("Amanda::Rest::Storages::Labels");
    my ($status, @result_messages) = Amanda::Rest::Configs::config_init(@_);
    return ($status, @result_messages) if @result_messages;

    my $user_msg = sub {
	my $msg = shift;
	push @result_messages, $msg;
    };

    my $main = sub {
	my $finished_cb = shift;
	my $storage;
	my $chg;
	my $Label;

	my $steps = define_steps
	    cb_ref => \$finished_cb,
	    finalize => sub { $storage->quit() if defined $storage;
			      $chg->quit() if defined $chg };

	step start => sub {
            my $tl = Amanda::Rest::Labels::init();
	    if ($tl->isa("Amanda::Message")) {
		return $steps->{'done'}->($tl);
	    }

	    $storage = Amanda::Storage->new(storage_name => $params{'STORAGE'},
					    tapelist     => $tl);
	    if ($storage->isa("Amanda::Changer::Error")) {
		return $steps->{'done'}->($storage);
	    }

	    $chg = $storage->{'chg'};
	    if ($chg->isa("Amanda::Changer::Error")) {
		return $steps->{'done'}->($chg);
	    }

	    $Label = Amanda::Label->new(storage  => $storage,
					tapelist => $tl,
					user_msg => $user_msg);


	    if (defined $params{'reuse'}) {
		my @labels = ($params{'LABEL'});
		if ($params{'reuse'}) {
		    $Label->reuse(labels      => \@labels,
				  finished_cb => $steps->{'assign'});
		} else {
		    $Label->no_reuse(labels      => \@labels,
				     finished_cb => $steps->{'assign'});
		}
	    } else {
		$steps->{'assign'}->();
	    }
	};

	step assign => sub {
	    if (defined $params{'meta'} || defined $params{'barcode'} ||
		defined $params{'pool'} || defined $params{'storage'}) {
		$Label->assign(label   => $params{'LABEL'},
			       meta    => $params{'meta'},
			       force   => $params{'force'},
			       barcode => $params{'barcode'},
			       pool    => $params{'pool'},
			       storage => $params{'storage'},
			       comment => $params{'comment'},
			       finished_cb => $steps->{'done'});
	    } else {
		$steps->{'done'}->();
	    }
	};

	step done => sub {
	    my $err = shift;

	    push @result_messages, $err if $err;

	    $finished_cb->();
	};

    };


    $main->(\&Amanda::MainLoop::quit);
    Amanda::MainLoop::run();
    $main = undef;

    return ($status, \@result_messages);
}

sub list {
    my %params = @_;

    Amanda::Util::set_pname("Amanda::Rest::Storages::Labels");
    my ($status, @result_messages) = Amanda::Rest::Configs::config_init(@_);
    return ($status, @result_messages) if @result_messages;

    my $tl = Amanda::Rest::Labels::init();
    if ($tl->isa("Amanda::Message")) {
	push @result_messages, $tl;
	return (-1, \@result_messages);
    }
    my @tles = @{$tl->{'tles'}};
    @tles = grep {defined $_->{'storage'}   and $_->{'storage'} eq $params{'STORAGE'}}                    @tles if $params{'STORAGE'};
    @tles = grep {                              $_->{'label'}   eq $params{'LABEL'}}                      @tles if $params{'LABEL'};
    @tles = grep {defined $_->{'config'}    and $_->{'config'}  eq $params{'config'}}                     @tles if $params{'config'};
    @tles = grep {defined $_->{'storage'}   and $_->{'storage'} eq $params{'storage'}}                    @tles if $params{'storage'};
    @tles = grep {defined $_->{'pool'}      and $_->{'pool'}    eq $params{'pool'}}                       @tles if $params{'pool'};
    @tles = grep {defined $_->{'meta'}      and $_->{'meta'}    eq $params{'meta'}}                       @tles if $params{'meta'};
    @tles = grep {                              $_->{'reuse'}   eq $params{'reuse'}}                      @tles if $params{'reuse'};
    @tles = grep {defined $_->{'datestamp'} and match_datestamp($params{'datestamp'}, $_->{'datestamp'})} @tles if defined $params{'datestamp'};
    push @result_messages, Amanda::Tapelist::Message->new(
				source_filename => __FILE__,
				source_line     => __LINE__,
				code => 1600001,
				severity => $Amanda::Message::SUCCESS,
				tles => \@tles);
    return ($status, \@result_messages);
}

1;
