# Copyright (C) 2007 Warp Networks S.L.
# Copyright (C) 2008-2013 Zentyal S.L.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2, as
# published by the Free Software Foundation.
#
# 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

# Class: EBox::Types::PortRange
#
#   Class to represent a type to store port ranges.
#   It has three possible values, the type of value stored is
#   stored in "range_type" and it can be:
#
#   "any" - it means all ports
#
#   "single_port" - just one port stored in single_port
#
#   "port_range" - two ports to indicate a range "from_port" and "to_port"
#
#   Use portRange() to check which type of port it has.
#   Use from(), to() to fetch port range.
#   Use single() to fetch single port.

#   Or you can use printableValue() to fecth an output which is nicely accepted
#   by iptables.
use strict;
use warnings;

package EBox::Types::PortRange;

use base 'EBox::Types::Abstract';

use EBox;
use EBox::Validate qw(checkPort);
use EBox::Exceptions::MissingArgument;
use EBox::Exceptions::External;
use EBox::Exceptions::Internal;
use EBox::Exceptions::InvalidData;
use EBox::Gettext;

# Constructor: new
#
#     Create a type which includes the protocol and the port from
#     Internet as a service
#
# Returns:
#
#     a <EBox::Types::PortRange> object
#
sub new
{
    my $class = shift;
    my %opts = @_;

    unless (exists $opts{'HTMLSetter'}) {
        $opts{'HTMLSetter'} ='/ajax/setter/portRangeSetter.mas';
    }
    unless (exists $opts{'HTMLViewer'}) {
        $opts{'HTMLViewer'} ='/ajax/viewer/textViewer.mas';
    }

    if ( defined ( $opts{'optional'} )
                    and $opts{'optional'} ) {
        throw EBox::Exceptions::Internal(
          q{PortRange type cannot be optional. You should select 'any' value}
                                        );
    }

    $opts{'type'} = 'portrange';

    my $self = $class->SUPER::new(%opts);

    my $selectedDefault = $opts{'defaultSelectedType'};
    $selectedDefault = 'any' unless defined ($selectedDefault);
    $self->{'range_type'} = $selectedDefault unless defined ($self->{'range_type'});
    bless($self, $class);
    return $self;
}

# Method: paramExist
#
#    Overrides <EBox::Types::Abstract::paramExist> method
#
sub paramExist
{
    my ($self, $params) = @_;

    my $name = $self->fieldName();
    my $type = $params->{$name . '_range_type'};

    return undef unless($type);

    return 1 if ($type eq 'any');

    if ($type eq 'range') {
        return undef unless(exists $params->{$name . '_from_port'}
                            and exists $params->{$name . '_to_port'});
    } else {
        return undef unless (exists $params->{$name . '_single_port'});
    }
}

# Method: value
#
#    Overrides <EBox::Types::Abstract::Value> method
#
sub value
{
    my ($self) = @_;

    my $type = $self->rangeType();
    if ($type eq 'range') {
        return $self->from() . ':' . $self->to() ;
    } elsif ($type eq 'single') {
        return $self->single();
    } else {
        return 'any';
    }
}

# Method: printableValue
#
#    Overrides <EBox::Types::Abstract::printableValue> method
#
sub printableValue
{
    my ($self) = @_;

    my $value = $self->value();

    if ($value eq 'any') {
        return __('any');
    }

    return $value;
}

# Method: compareToHash
#
#    Overrides <EBox::Types::Abstract::compareToHash> method
#
sub compareToHash
{
    my ($self, $hash) = @_;

    my $name = $self->fieldName();
    my $type = $hash->{$name . '_range_type'};
    my $from = $hash->{$name . '_from_port'};
    my $to = $hash->{$name . '_to_port'};
    my $single = $hash->{$name . '_single_port'};

    if ($self->rangeType() ne $type) {
        return 0;
    }

    if ($self->rangeType() eq 'single') {
        return 0 if ($single ne $self->single());
    }

    if ($self->rangeType() eq 'range') {
        return 0 if (($from ne $self->from()) or ($to ne $self->to()));
    }

    return 1;
}

# Method: cmp
#
#    Overrides <EBox::Types::Abstract::cmp> method
#
sub cmp
{
    my ($self, $other) = @_;

    if ((ref $self) ne (ref $other)) {
        return undef;
    }

    return ($self->printableValue() cmp $other->printableValue());
}

# Method: fields
#
#    Overrides <EBox::Types::Abstract::fields> method
#
sub fields
{
    my ($self) = @_;

    my $name = $self->fieldName();
    my $type = $name . '_range_type';
    my $from = $name . '_from_port';
    my $to = $name . '_to_port';
    my $single = $name . '_single_port';

    return ($type, $from, $to, $single);
}

###
# Own methods
###

# Method: rangeTtype
#
#   Return the port range type which can be:
#
#       any
#       single
#       range
#
# Returns:
#
#   string containing the type
sub rangeType
{
    my ($self) = @_;

    return $self->{'range_type'};
}

# Method: from
#
#   Return the "from" port
#
# Returns:
#
#   string - containing the port
sub from
{
    my ($self) = @_;

    return $self->{'from'};
}

# Method: to
#
#   Return the "to" port
#
# Returns:
#
#   string - containing the port
sub to
{
    my ($self) = @_;

    return $self->{'to'};
}

# Method: single
#
#   Return the single port
#
# Returns:
#
#   string - containing the port
sub single
{
    my ($self) = @_;

    return $self->{'single'};
}

# Method: rangeTypes
#
#     Get the range available (Static method)
#
# Returns:
#
#     array ref - the range types in a hash with the following elements
#              - value - the  range name
#              - printableValue - the protocol printable name
#
sub rangeTypes
{
    my ($self) = @_;

    my @rangeTypes = (
                      {
                       value => 'any',
                       printableValue => __('Any'),
                      },
                      {
                       value => 'single',
                       printableValue => __('Single port'),
                      },
                      {
                       value => 'range',
                       printableValue => __('Port range'),
                      },
                     );

    return \@rangeTypes;
}

# Group: Protected methods

# Method: _setMemValue
#
# Overrides:
#
#       <EBox::Types::Abstract::_setMemValue>
#
sub _setMemValue
{
    my ($self, $params) = @_;

    my $name = $self->fieldName();
    $self->{'range_type'} = $params->{$name . '_range_type'};
    $self->{'from'} = $params->{$name . '_from_port'};
    $self->{'to'} = $params->{$name . '_to_port'};
    $self->{'single'} = $params->{$name . '_single_port'};
}

# Method: _storeInHash
#
# Overrides:
#
#       <EBox::Types::Abstract::_storeInHash>
#
sub _storeInHash
{
    my ($self, $hash) = @_;

    my $type = $self->fieldName() . '_range_type';
    my $from = $self->fieldName() . '_from_port';
    my $to = $self->fieldName() . '_to_port';
    my $single = $self->fieldName() . '_single_port';

    map { delete $hash->{$_} } ($from, $to, $single);

    my $rangeType = $self->rangeType();
    $hash->{$type} = $rangeType;

    if ($rangeType eq 'range') {
        $hash->{$from} = $self->from();
        $hash->{$to} = $self->to();
    } elsif ($rangeType eq 'single') {
        $hash->{$single} = $self->single();
    }
}

# Method: _restoreFromHash
#
# Overrides:
#
#       <EBox::Types::Abstract::_restoreFromHash>
#
sub _restoreFromHash
{
    my ($self, $hash) = @_;

    return unless ($self->row());
    my $range = $self->fieldName() . '_range_type';
    my $from = $self->fieldName() . '_from_port';
    my $to = $self->fieldName() . '_to_port';
    my $single = $self->fieldName() . '_single_port';

    $self->{'range_type'} = $hash->{$range};
    $self->{'from'} = $hash->{$from};
    $self->{'to'} = $hash->{$to};
    $self->{'single'} = $hash->{$single};
}

# Method: _paramIsValid
#
# Overrides:
#
#       <EBox::Types::Abstract::_paramIsValid>
#
sub _paramIsValid
{
    my ($self, $params) = @_;

    my $name = $self->fieldName();
    my $type = $params->{$name . '_range_type'};

    return 1 if ($type eq 'any');

    if ($type eq 'range') {
        my $from = $params->{$name . '_from_port'};
        my $to = $params->{$name . '_to_port'};
        checkPort($from, $self->printableName());
        checkPort($to, $self->printableName());
        if ($to < $from) {
            throw EBox::Exceptions::InvalidData( data => $self->printableName(),
                      value => $from . ':' . $to,
                      advice => __x('"From" {from} must be greater than "To" {to}',
                                    from => $from, to => $to));
        }
    } else {
      checkPort($params->{$name . '_single_port'}, $self->printableName());
    }

    return 1;
}

# Method: _paramIsSet
#
# Overrides:
#
#       <EBox::Types::Abstract::_paramIsSet>
#
sub _paramIsSet
{
    my ($self, $params) = @_;

    my $name = $self->fieldName();
    my $type = $params->{$name . '_range_type'};

    return undef unless($type);

    return 1 if ($type eq 'any');

    if ($type eq 'range') {
        return undef unless(exists $params->{$name . '_from_port'}
                and ($params->{$name . '_from_port'} ne '')
                and exists $params->{$name . '_to_port'}
                and ($params->{$name . '_to_port'}) ne '');
    } else {
        return undef unless (exists $params->{$name . '_single_port'}
                and ($params->{$name . '_single_port'} ne ''));
    }

    return 1;
}

# Method: _setValue
#
#     Set the value defined as a string in the
#     printableValue. That is, to define a port range, you can choose
#     one of following:
#
#     any - any range
#     [0-9]+ - single port
#     [0-9]+:[0-9]+ - from:to port range
#
# Overrides:
#
#     <EBox::Types::Abstract::_setValue>
#
# Parameters:
#
#     value - String as defined above
#
sub _setValue # (defaultValue)
{
    my ($self, $value) = @_;

    my $params = {};
    if ( $value eq 'any' ) {
        $params->{$self->fieldName() . '_range_type'} = 'any';
    } elsif ( $value =~ m/^[0-9]+$/g ) {
        $params->{$self->fieldName() . '_range_type'} = 'single';
        $params->{$self->fieldName() . '_single_port'} = $value;
    } else {
        my ($from, $to) = split ( ':', $value);
        $params->{$self->fieldName() . '_range_type'} = 'range';
        $params->{$self->fieldName() . '_from_port'} = $from;
        $params->{$self->fieldName() . '_to_port'} = $to;
    }

    $self->setMemValue($params);
}

1;
