package NGCP::Panel::Controller::Rewrite;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;

use parent 'Catalyst::Controller';

use NGCP::Panel::Form;

use NGCP::Panel::Utils::Message;
use NGCP::Panel::Utils::Rewrite;
use NGCP::Panel::Utils::Navigation;

sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
    my ($self, $c) = @_;
    $c->log->debug(__PACKAGE__ . '::auto');
    NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c);
    return 1;
}

sub set_list :Chained('/') :PathPart('rewrite') :CaptureArgs(0) {
    my ( $self, $c ) = @_;

    $c->stash->{sets_rs} = $c->model('DB')->resultset('voip_rewrite_rule_sets');
    unless($c->user->roles eq "admin") {
        $c->stash->{sets_rs} = $c->stash->{sets_rs}->search({
            reseller_id => $c->user->reseller_id
        });
    }
    $c->stash->{set_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
        { name => 'id', search => 1, title => $c->loc('#') },
        { name => 'reseller.name', search => 1, title => $c->loc('Reseller') },
        { name => 'name', search => 1, title => $c->loc('Name') },
        { name => 'description', search => 1, title => $c->loc('Description') },
    ]);

    $c->stash(template => 'rewrite/set_list.tt');
}

sub set_root :Chained('set_list') :PathPart('') :Args(0) {
    my ($self, $c) = @_;
}

sub set_ajax :Chained('set_list') :PathPart('ajax') :Args(0) {
    my ($self, $c) = @_;
    my $rs = $c->stash->{sets_rs};
    NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{set_dt_columns});
    $c->detach( $c->view("JSON") );
}

sub set_base :Chained('set_list') :PathPart('') :CaptureArgs(1) {
    my ($self, $c, $set_id) = @_;

    unless($set_id && is_int($set_id)) {
        NGCP::Panel::Utils::Message::error(
            c     => $c,
            log   => 'Invalid rewrite rule set id detected',
            desc  => $c->loc('Invalid rewrite rule set id detected'),
        );
        NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/rewrite'));
    }

    my $res = $c->stash->{sets_rs}->find($set_id);
    unless(defined($res)) {
        NGCP::Panel::Utils::Message::error(
            c     => $c,
            log   => 'Rewrite rule set does not exist',
            desc  => $c->loc('Rewrite rule set does not exist'),
        );
        NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/rewrite'));
    }
    $c->stash(set_result => $res);
}

sub set_edit :Chained('set_base') :PathPart('edit') {
    my ($self, $c) = @_;

    my $posted = ($c->request->method eq 'POST');
    my $params = { $c->stash->{set_result}->get_inflated_columns };
    $params->{reseller}{id} = delete $params->{reseller_id};
    $params = merge($params, $c->session->{created_objects});
    my $form;
    if($c->user->roles eq "admin") {
        $form = NGCP::Panel::Form::get("NGCP::Panel::Form::RewriteRule::AdminSet", $c);
    } else {
        $form = NGCP::Panel::Form::get("NGCP::Panel::Form::RewriteRule::ResellerSet", $c);
    }
    $form->process(
        posted => $posted,
        params => $c->request->params,
        item   => $params,
    );
    NGCP::Panel::Utils::Navigation::check_form_buttons(
        c => $c,
        form => $form,
        fields => {
            'reseller.create' => $c->uri_for('/reseller/create'),
        },
        back_uri => $c->req->uri,
    );
    if($posted && $form->validated) {
        try {
            if($c->user->roles eq "admin") {
                $form->values->{reseller_id} = $form->values->{reseller}{id};
                delete $form->values->{reseller};
            }
            $c->stash->{set_result}->update($form->values);
            delete $c->session->{created_objects}->{reseller};
            NGCP::Panel::Utils::Message::info(
                c    => $c,
                desc => $c->loc('Rewrite rule set successfully updated'),
            );
        } catch($e) {
            NGCP::Panel::Utils::Message::error(
                c => $c,
                error => $e,
                desc  => $c->loc('Failed to update rewrite rule set'),
            );
        }
        NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/rewrite'));
    }

    $c->stash(form => $form);
    $c->stash(edit_flag => 1);
}

sub set_delete :Chained('set_base') :PathPart('delete') {
    my ($self, $c) = @_;

    try {
        $c->stash->{set_result}->delete;
        NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
        NGCP::Panel::Utils::Message::info(
            c    => $c,
            data => { $c->stash->{set_result}->get_inflated_columns },
            desc => $c->loc('Rewrite rule set successfully deleted'),
        );
    } catch($e) {
        NGCP::Panel::Utils::Message::error(
            c => $c,
            error => $e,
            desc  => $c->loc('Failed to delete rewrite rule set'),
        );
    }
    NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/rewrite'));
}

sub set_clone :Chained('set_base') :PathPart('clone') {
    my ($self, $c) = @_;

    my $posted = ($c->request->method eq 'POST');
    my $params = { $c->stash->{set_result}->get_inflated_columns };
    $params = merge($params, $c->session->{created_objects});
    my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::RewriteRule::CloneSet", $c);
    $form->process(
        posted => $posted,
        params => $c->request->params,
        item   => $params,
    );
    NGCP::Panel::Utils::Navigation::check_form_buttons(
        c => $c,
        form => $form,
        fields => {},
        back_uri => $c->req->uri,
    );
    if($posted && $form->validated) {
        try {
            my $schema = $c->model('DB');
            $schema->txn_do(sub {
                my $new_set = $c->stash->{sets_rs}->create({
                    %{ $form->values },  ## no critic (ProhibitCommaSeparatedStatements)
                    reseller_id => $c->stash->{set_result}->reseller_id,
                });
                my @old_rules = $c->stash->{set_result}->voip_rewrite_rules->all;
                for my $rule (@old_rules) {
                    $new_set->voip_rewrite_rules->create({
                        match_pattern => $rule->match_pattern,
                        replace_pattern => $rule->replace_pattern,
                        description => $rule->description,
                        direction => $rule->direction,
                        field => $rule->field,
                        priority => $rule->priority,
                        enabled => $rule->enabled,
                    });
                }
            });
            NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
            NGCP::Panel::Utils::Message::info(
                c    => $c,
                desc => $c->loc('Rewrite rule set successfully cloned'),
            );
        } catch($e) {
            NGCP::Panel::Utils::Message::error(
                c => $c,
                error => $e,
                desc  => $c->loc('Failed to clone rewrite rule set.'),
            );
        }
        NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/rewrite'));
    }

    $c->stash(form => $form);
    $c->stash(create_flag => 1);
    $c->stash(clone_flag => 1);
}

sub set_create :Chained('set_list') :PathPart('create') :Args(0) {
    my ($self, $c) = @_;

    my $posted = ($c->request->method eq 'POST');
    my $params = {};
    $params = merge($params, $c->session->{created_objects});
    my $form;
    if($c->user->roles eq "admin") {
        $form = NGCP::Panel::Form::get("NGCP::Panel::Form::RewriteRule::AdminSet", $c);
    } else {
        $form = NGCP::Panel::Form::get("NGCP::Panel::Form::RewriteRule::ResellerSet", $c);
    }
    $form->process(
        posted => $posted,
        params => $c->request->params,
        item   => $params,
    );
    NGCP::Panel::Utils::Navigation::check_form_buttons(
        c => $c,
        form => $form,
        fields => {
            'reseller.create' => $c->uri_for('/reseller/create'),
        },
        back_uri => $c->req->uri,
    );
    if($posted && $form->validated) {
        try {
            if($c->user->roles eq "admin") {
                $form->values->{reseller_id} = $form->values->{reseller}{id};
                delete $form->values->{reseller};
            } else {
                $form->values->{reseller_id} = $c->user->reseller_id;
            }
            $c->stash->{sets_rs}->create($form->values);
            delete $c->session->{created_objects}->{reseller};
            NGCP::Panel::Utils::Message::info(
                c    => $c,
                desc => $c->loc('Rewrite rule set successfully created'),
            );
        } catch($e) {
            NGCP::Panel::Utils::Message::error(
                c => $c,
                error => $e,
                desc  => $c->loc('Failed to create rewrite rule set'),
            );
        }
        NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/rewrite'));
    }

    $c->stash(form => $form);
    $c->stash(create_flag => 1);
}

sub rules_list :Chained('set_base') :PathPart('rules') :CaptureArgs(0) {
    my ( $self, $c ) = @_;

    my $rules_rs = $c->stash->{set_result}->voip_rewrite_rules;
    $c->stash(rules_rs => $rules_rs);
    $c->stash(rules_uri => $c->uri_for_action("/rewrite/rules_root", [$c->req->captures->[0]]));

    $c->stash(template => 'rewrite/rules_list.tt');
    return;
}

sub rules_root :Chained('rules_list') :PathPart('') :Args(0) {
    my ($self, $c) = @_;

    my $rules_rs    = $c->stash->{rules_rs};
    my $param_move  = $c->req->params->{move};
    my $param_where = $c->req->params->{where};

    if ($param_move && is_int($param_move) && $param_where &&
            (my $elem = $rules_rs->find($param_move))) {

        my $use_next = ($param_where eq "down") ? 1 : 0;
        my $swap_elem = $rules_rs->search({
            field => $elem->field,
            direction => $elem->direction,
            priority => { ($use_next ? '>' : '<') => $elem->priority },
        },{
            order_by => {($use_next ? '-asc' : '-desc') => 'priority'},
        })->first;
        try {
            if ($swap_elem) {
                #3way swap required because trigger relies on natural key of kamailio.dialplan.
                my @tmp_priority = (
                    $c->stash->{rules_rs}->get_column('priority')->max() + 1,
                    $swap_elem->priority,
                    $elem->priority
                );
                $swap_elem->priority($tmp_priority[0]);
                $swap_elem->update;
                $elem->priority($tmp_priority[1]);
                $elem->update;
                $swap_elem->priority($tmp_priority[2]);
                $swap_elem->update;
            } elsif($use_next) {
                my $last_priority = $c->stash->{rules_rs}->get_column('priority')->max() || 49;
                $elem->priority(int($last_priority) + 1);
                $elem->update;
            } else {
                my $last_priority = $c->stash->{rules_rs}->get_column('priority')->min() || 1;
                $elem->priority(int($last_priority) - 1);
                $elem->update;
            }
            NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
        } catch($e) {
            NGCP::Panel::Utils::Message::error(
                c => $c,
                error => $e,
                desc  => $c->loc('Failed to move rewrite rule.'),
            );
        }
    }

    my @caller_in = $rules_rs->search({
        field => 'caller',
        direction => 'in',
    },{
        order_by => { -asc => 'priority' },
    })->all;

    my @callee_in = $rules_rs->search({
        field => 'callee',
        direction => 'in',
    },{
        order_by => { -asc => 'priority' },
    })->all;

    my @caller_out = $rules_rs->search({
        field => 'caller',
        direction => 'out',
    },{
        order_by => { -asc => 'priority' },
    })->all;

    my @callee_out = $rules_rs->search({
        field => 'callee',
        direction => 'out',
    },{
        order_by => { -asc => 'priority' },
    })->all;

    my @caller_lnp = $rules_rs->search({
        field => 'caller',
        direction => 'lnp',
    },{
        order_by => { -asc => 'priority' },
    })->all;

    my @callee_lnp = $rules_rs->search({
        field => 'callee',
        direction => 'lnp',
    },{
        order_by => { -asc => 'priority' },
    })->all;

    for my $row (@caller_in, @callee_in, @caller_out, @callee_out, @caller_lnp, @callee_lnp) {
        my $mp = $row->match_pattern;
        my $rp = $row->replace_pattern;
        $mp =~ s/\$avp\(s\:(\w+)\)/\${$1}/g;
        $mp =~ s/\$\(avp\(s\:(\w+)\)\[\+\]\)/\@{$1}/g;
        $rp =~ s/\$avp\(s\:(\w+)\)/\${$1}/g;
        $row->match_pattern($mp);
        $row->replace_pattern($rp);
    }

    $c->stash(rules => {
        caller_in  => \@caller_in,
        callee_in  => \@callee_in,
        caller_out => \@caller_out,
        callee_out => \@callee_out,
        caller_lnp => \@caller_lnp,
        callee_lnp => \@callee_lnp,
    });
    return;
}

sub rules_base :Chained('rules_list') :PathPart('') :CaptureArgs(1) {
    my ($self, $c, $rule_id) = @_;

    unless($rule_id && is_int($rule_id)) {
        NGCP::Panel::Utils::Message::error(
            c     => $c,
            log   => 'Invalid rewrite rule id detected',
            desc  => $c->loc('Invalid rewrite rule id detected'),
        );
        NGCP::Panel::Utils::Navigation::back_or($c, $c->stash->{rules_uri});
    }

    my $res = $c->stash->{rules_rs}->find($rule_id);
    unless(defined($res)) {
        NGCP::Panel::Utils::Message::error(
            c     => $c,
            log   => 'Rewrite rule does not exist',
            desc  => $c->loc('Rewrite rule does not exist'),
        );
        NGCP::Panel::Utils::Navigation::back_or($c, $c->stash->{rules_uri});
    }
    $c->stash(rule_result => $res);
    return;
}

sub rules_edit :Chained('rules_base') :PathPart('edit') {
    my ($self, $c) = @_;

    my $posted = ($c->request->method eq 'POST');
    my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::RewriteRule::Rule", $c);
    $form->process(
        posted => $posted,
        params => $c->request->params,
        item   => $c->stash->{rule_result},
    );
    NGCP::Panel::Utils::Navigation::check_form_buttons(
        c => $c,
        form => $form,
        fields => {},
        back_uri => $c->req->uri,
    );
    if($posted && $form->validated) {
        try {
            $c->stash->{rule_result}->update($form->values);
            NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
            NGCP::Panel::Utils::Message::info(
                c    => $c,
                desc => $c->loc('Rewrite rule successfully updated'),
            );
        } catch($e) {
            NGCP::Panel::Utils::Message::error(
                c => $c,
                error => $e,
                desc  => $c->loc('Failed to update rewrite rule'),
            );
        }
        NGCP::Panel::Utils::Navigation::back_or($c, $c->stash->{rules_uri});
    }

    $c->stash(form => $form);
    $c->stash(edit_flag => 1);
}

sub rules_delete :Chained('rules_base') :PathPart('delete') {
    my ($self, $c) = @_;

    try {
        $c->stash->{rule_result}->delete;
        NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
        NGCP::Panel::Utils::Message::info(
            c    => $c,
            data => { $c->stash->{rule_result}->get_inflated_columns },
            desc => $c->loc('Rewrite rule successfully deleted'),
        );
    } catch($e) {
        NGCP::Panel::Utils::Message::error(
            c => $c,
            error => $e,
            desc  => $c->loc('Failed to delete rewrite rule'),
        );
    };
    NGCP::Panel::Utils::Navigation::back_or($c, $c->stash->{rules_uri});
}

sub rules_create :Chained('rules_list') :PathPart('create') :Args(0) {
    my ($self, $c) = @_;

    my $posted = ($c->request->method eq 'POST');
    my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::RewriteRule::Rule", $c);
    $form->process(
        posted => $posted,
        params => $c->request->params,
    );
    NGCP::Panel::Utils::Navigation::check_form_buttons(
        c => $c,
        form => $form,
        fields => {},
        back_uri => $c->req->uri,
    );
    if($posted && $form->validated) {
        try {
            my $last_priority = $c->stash->{rules_rs}->get_column('priority')->max() || 49;
            $form->values->{priority} = int($last_priority) + 1;
            $c->stash->{rules_rs}->create($form->values);
            NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
            NGCP::Panel::Utils::Message::info(
                c    => $c,
                desc => $c->loc('Rewrite rule successfully created'),
            );
        } catch($e) {
            NGCP::Panel::Utils::Message::error(
                c => $c,
                error => $e,
                desc  => $c->loc('Failed to create rewrite rule'),
            );
        }
        NGCP::Panel::Utils::Navigation::back_or($c, $c->stash->{rules_uri});
    }

    $c->stash(form => $form);
    $c->stash(create_flag => 1);
}


1;

=head1 NAME

NGCP::Panel::Controller::Rewrite - Manage Rewrite Rules

=head1 DESCRIPTION

Show/Edit/Create/Delete Rewrite Rule Sets.

Show/Edit/Create/Delete Rewrite Rules within Rewrite Rule Sets.

=head1 METHODS

=head2 set_list

Basis for provisioning.voip_rewrite_rule_sets.

=head2 set_root

Display rewrite rule sets through F<rewrite/set_list.tt> template.

=head2 set_ajax

Get provisioning.voip_rewrite_rule_sets from the database and
output them as JSON.
The format is meant for parsing with datatables.

=head2 set_base

Fetch a provisioning.voip_rewrite_rule_set from the database by its id.

=head2 set_edit

Show a modal to edit a rewrite rule set determined by L</set_base>.
The form used is L<NGCP::Panel::Form::RewriteRuleSet>.

=head2 set_delete

Delete a rewrite rule set determined by L</set_base>.

=head2 set_clone

Deep copy a rewrite rule set determined by L</set_base>. The user can enter
a new name and description. The reseller is not configurable, but set by the
original rewrite rule set. The rewrite rules of the original rwrs are then
cloned and assigned to the new rwrs.

=head2 set_create

Show a modal to create a new rewrite rule set using the form
L<NGCP::Panel::Form::RewriteRuleSet>.

=head2 rules_list

Basis for provisioning.voip_rewrite_rules. Chained from L</set_base> and
therefore handles only rules for a certain rewrite rule set.

=head2 rules_root

Display rewrite rule sets through F<rewrite/rules_list.tt> template.
The rules are stashed to rules hashref which contains the keys
"caller_in", "callee_in", "caller_out", "callee_out".

It swaps priority of two elements if "move" and "where" GET params are set.

It modifies match_pattern and replace_pattern field to a certain output format
using regex.

=head2 rules_base

Fetch a rewrite rule from the database by its id. Will only find rules under
the current rule_set.

=head2 rules_edit

Show a modal to edit a rewrite rule determined by L</rules_base>.
The form used is L<NGCP::Panel::Form::RewriteRule>.

=head2 rules_delete

Delete a rewrite rule determined by L</rules_base>.

=head2 rules_create

Show a modal to create a new rewrite rule using the form
L<NGCP::Panel::Form::RewriteRule>.

=head2 sip_dialplan_reload

This is ported from ossbss.

Reloads dialplan cache of sip proxies.

=head1 AUTHOR

Gerhard Jungwirth C<< <gjungwirth@sipwise.com> >>

=head1 LICENSE

This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

# vim: set tabstop=4 expandtab:
