#!/usr/bin/perl
use warnings;
use strict;
#####
use locale ':not_characters';
#####

use Carp qw(confess cluck);
use Data::Dumper;
use Digest::SHA qw(sha256_hex);
use Hash::Util qw(lock_hash);
use Mojolicious::Lite 'Ravada::I18N';
use Mojo::JSON qw(decode_json encode_json);
use Time::Piece;
#use Mojolicious::Plugin::I18N;
use Mojo::Home;

use utf8;

use I18N::LangTags::Detect;
#####
#my $self->plugin('I18N');
#package Ravada::I18N:en;
#####
#
no warnings "experimental::signatures";
use feature qw(signatures);

use Ravada::Auth;
use Ravada::Auth::OpenID;
use Ravada::Booking;
use Ravada::Front;
use Ravada::Front::Domain;
use Ravada::HostDevice::Templates;
use Ravada::Route;
use Ravada::WebSocket;
use POSIX qw(locale_h strftime);

my $help;

my $FILE_CONFIG = "/etc/rvd_front.conf";

my $error_file_duplicated = 0;
for my $file ( "/etc/rvd_front.conf" , ($ENV{HOME} or '')."/rvd_front.conf") {
    warn "WARNING: Found config file at $file and at $FILE_CONFIG\n"
        if -e $file && $FILE_CONFIG && $file ne $FILE_CONFIG;
    $FILE_CONFIG = $file if -e $file;
    $error_file_duplicated++;
}
warn "WARNING: using $FILE_CONFIG\n"    if$error_file_duplicated>2;

my $FILE_CONFIG_RAVADA;
for my $file ( "/etc/ravada.conf" , ($ENV{HOME} or '')."/ravada.conf") {
    warn "WARNING: Found config file at $file and at $FILE_CONFIG_RAVADA\n"
        if -e $file && $FILE_CONFIG_RAVADA;
    $FILE_CONFIG_RAVADA = $file if -e $file;
}

my $CONFIG_FRONT = plugin Config => { default => {
                                                hypnotoad => {
                                                pid_file => 'log/rvd_front.pid'
                                                ,listen => ['http://*:8081']
                                                }
                                              ,login_bg_file => '/img/intro-bg.jpg'
                                              ,login_header => 'Welcome'
                                              ,login_message => ''
                                              ,secrets => ['changeme0']
                                              ,guide => 0
                                              ,login_custom => ''
                                              ,footer => 'bootstrap/footer'
                                              ,monitoring => 0
                                              ,fallback => 0
                                              ,guide_custom => ''
                                              ,widget => ''
                                              ,admin => {
                                                    hide_clones => 15
                                                    ,autostart => 0
                                              }
                                              ,config => $FILE_CONFIG_RAVADA
                                              ,auto_view => 0
                                              ,bookings => 1
                                              ,log => {
                                                log => 0
                                                ,file => '/var/log/ravada/rvd_front.log'
                                                ,level => 'debug'
                                              }
                                              }
                                      ,file => $FILE_CONFIG
};

$CONFIG_FRONT->{login_bg_file} = '/'.$CONFIG_FRONT->{login_bg_file}
if $CONFIG_FRONT->{login_bg_file} !~ m{^/};

delete $CONFIG_FRONT->{login_custom} if $ENV{MOJO_MODE} && $ENV{MOJO_MODE} eq 'development';
#####
#####
#####
# Import locale-handling tool set from POSIX module.
# This example uses: setlocale -- the function call
#                    LC_CTYPE -- explained below

# query and save the old locale
my $old_locale = setlocale(LC_CTYPE);

setlocale(LC_CTYPE, "en_US.ISO8859-1");
# LC_CTYPE now in locale "English, US, codeset ISO 8859-1"

setlocale(LC_CTYPE, "");
# LC_CTYPE now reset to default defined by LC_ALL/LC_CTYPE/LANG
# environment variables.  See below for documentation.

# restore the old locale
setlocale(LC_CTYPE, $old_locale);
#####
#####
#####
plugin I18N => {namespace => 'Ravada::I18N', default => 'en'};

my %config;
%config = (config => $CONFIG_FRONT->{config}) if $CONFIG_FRONT->{config};

our $RAVADA = Ravada::Front->new(%config);

our $USER;

# TODO: get those from the config file
our $DOCUMENT_ROOT = "/var/www";

# session times out in 5 minutes
our $SESSION_TIMEOUT = ($CONFIG_FRONT->{session_timeout} or 5 * 60);
# session times out in 15 minutes for admin users
our $SESSION_TIMEOUT_ADMIN = ($CONFIG_FRONT->{session_timeout_admin} or 15 * 60);
our $SESSION_TIMEOUT_ADMIN2 = ($CONFIG_FRONT->{session_timeout_admin2} or 60 * 60);

my $WS = Ravada::WebSocket->new(ravada => $RAVADA);
my %ALLOWED_ANONYMOUS_WS = map { $_ => 1 } qw(list_bases_anonymous list_alerts);
my %LDAP_ATTRIBUTES;
my %LDAP_USERS;


# TOODO: config this variable
my $LIMIT_SHOW_USERS = 25;

init();
############################################################################3

sub _time() {
    return strftime('%Y/%m/%d:%H:%M:%S %z',localtime);
}

sub _security_policy() {

    my $config=$RAVADA->_settings_by_parent("/frontend/content_security_policy");
    my $all = ($config->{all} or '');
    my %src = (
    "default-src" => "'self' sha256- sha384 http: https: data:"
    ,"style-src" => "'self' cdnjs.cloudflare.com stackpath.bootstrapcdn.com cdn.ckeditor.com cdn.jsdelivr.net use.fontawesome.com 'unsafe-inline'"
    ,"script-src" => "'self' code.jquery.com cdn.ckeditor.com cdnjs.cloudflare.com stackpath.bootstrapcdn.com ajax.googleapis.com cdn.jsdelivr.net 'unsafe-inline' 'unsafe-eval'"
    ,"object-src" => " 'self'"
    ,"media-src" => "'self'"
    ,"frame-src" => "'self'"
    ,"font-src" => "'self' data: use.fontawesome.com"
    ,"connect-src" => "'self'"
    );
    my $sec = '';
    for my $field (sort keys %src) {
        $sec .= " " if $sec;
        $sec .= $field." ".$src{$field};
        $sec .= " $all" if $all;
        if ( exists $config->{$field} ) {
                $sec .= " ".${config}->{$field};
        }
        $field =~ s/-/_/g;
        if ( exists $config->{$field} ) {
                $sec .= " ".$config->{$field};
        }

        $sec .= ";";
    }
    return $sec;
}

hook before_routes => sub {
  my $c = shift;

  my $sec = _security_policy();
  $c ->res->headers->content_security_policy($sec);

  $USER = undef;


  $c->stash(version => $RAVADA->version);
  my $url = $c->req->url->to_abs->path;
  my $host = $c->req->url->to_abs->host;
  my $widget=( $CONFIG_FRONT->{widget} or $RAVADA->setting('/frontend/widget'));
  $c->stash(css=>['/css/sb-admin.css']
            ,js_mod=>[ ## angular modules
                  '/js/booking/booking.module.js?v='.$RAVADA->version
                ]
            ,js=>[
                '/js/ravada.js?v='.$RAVADA->version
                ]
            ,csssnippets => []
            ,navbar_custom => 0
            ,url => undef
            ,_logged_in => undef
            ,_anonymous => undef
            ,forcing_change_password => undef
            ,_user => undef
            ,footer=> $CONFIG_FRONT->{footer}
            ,monitoring => 0
            ,fallback => $CONFIG_FRONT->{fallback}
            ,check_netdata => 0
            ,guide => $CONFIG_FRONT->{guide}
            ,host => $host
            ,bookings => $RAVADA->setting('/backend/bookings')
            ,FEATURE => {}
            ,widget => $widget
            );

    $USER = _logged_in($c);

    my $user = '-';
    $user = $USER->name if defined $USER;
    app->log->info(_remote_ip($c)." $user ["._time()."] ".$c->req->method." ".$url)
    if $CONFIG_FRONT->{log}->{log};

    return access_denied_json($c) if !$USER && $url eq '/user.json';

    return if $url =~ m{^/(css|font|img|js)}
        || $url =~ m{^/fallback/.*\.(css|js|map)$}
        || $url =~ m{^/fallback/font}
       ;

    return if $url =~ m{^/(anonymous_logout|login|logout|requirements|robots.txt|favicon.ico|status\.)};

    return if $url =~ m{^/(connect|profile)};

    my $bases_anonymous = $RAVADA->list_bases_anonymous(_remote_ip($c));
    return access_denied($c) if $url =~ m{^/anonymous} && !@$bases_anonymous;

    if ( $RAVADA->is_in_maintenance ) {
        return login($c) if !$USER && $url =~ m{^/$};
        return maintenance($c) if !$USER || !$USER->is_operator;
    }

    if ((defined $USER) && (_logged_in($c)) && (! $USER->is_temporary) && (! $USER->is_external) && ($USER->password_will_be_changed())) {
        return change_password($c, 1);        
    }

    if (($url =~ m{^/machine/(clone|display|info|view)/}
        || $url =~ m{^/(list_bases_anonymous|request/)}i
        || $url =~ m{^/ws/subscribe}
        || $url =~ m{^/execution_machines_limit$}
        || $url =~ m{^/anonymous}
        ) && !_logged_in($c)) {
        $USER = _anonymous_user($c);
        return if (! $USER) || ($USER->is_temporary);
    }
    return access_denied($c)
        if $url =~ /(screenshot|\.json)/
        && !_logged_in($c);
    return login($c,401) if !_logged_in($c);

    if ($USER && $USER->is_admin && $CONFIG_FRONT->{monitoring}) {
        if (!defined $c->session('monitoring')) {
            $c->stash(check_netdata => "https://$host:19999/index.html");
        }
        $c->stash( monitoring => 1) if $c->session('monitoring');
    }
        $c->stash( fallback => 1) if $c->session('fallback');
    $c->stash(FEATURE => $RAVADA->feature());
};


############################################################################3

any '/robots.txt' => sub {
    my $c = shift;
    return $c->render(text => "User-agent: *\nDisallow: /\n", format => 'text');
};

any '/' => sub {
    my $c = shift;

my %header;

    return quick_start($c);
};

any '/index.html' => sub {
    my $c = shift;
    return quick_start($c);
};

get '/user.json' => sub($c) {
    return $c->render( json => {id => $USER->id , name => $USER->name});
};

any '/login' => sub {
    my $c = shift;
    return login($c);
};

any '/login_openid' => sub($c) {
    my %header;
    for my $name (@{$c->req->headers->names}) {
        $header{$name} = $c->req->headers->header($name)
        if $name =~ /OIDC/;
    }

    my $auth_ok;
    my $username_oidc = $header{OIDC_CLAIM_preferred_username};
    if ($username_oidc) {
        $c->session('logoutURL' => $RAVADA->setting('/frontend/openid/logout_url'));
        my $oidc_at_hash = $header{OIDC_CLAIM_at_hash};
        if (!$oidc_at_hash) {
            warn "Error: no OIDC_CLAIM_at_hash in header ".join(" ",%header);
            return access_denied($c);
        }
        $c->session('oidc_at_hash' => $oidc_at_hash);
        eval {
        $auth_ok = Ravada::Auth::OpenID::login_external($username_oidc, \%header);
        };
        warn $@ if $@;
    }
    my $error = ($@ or '');
    if (!$username_oidc || !$auth_ok) {
        if ( $CONFIG_FRONT->{log}->{log} ) {
            app->log->error("Access denied with openid from "._remote_ip($c));
            app->log->error("Access denied error: ".($error or 'unknown')._remote_ip($c));
        }
        return access_denied($c);
    }
    $c->session('logoutURL' => $RAVADA->setting('/frontend/openid/logout_url'));
    $c->session('OIDC_CLAIM_at_hash' => $header{'OIDC_CLAIM_at_hash'});

    _login_ok($c, $auth_ok);
};

sub _session_error($c) {
    $c->render("text" => "Session error, please close the web browser and try again");
}

any '/login_openid/redirect_uri' => sub($c) {
    return $c->render("text" => "protected redirect");
};

any '/test' => sub {
    my $c = shift;
    my $logged = _logged_in($c);
    my $count = $c->session('count');
    $c->session(count => ++$count);

    my $name_mojo = $c->signed_cookie('mojolicious');

    my $dump_log = ''.(Dumper($logged) or '');
    return $c->render(text => "$count ".($name_mojo or '')."<br> "
        ."<pre>$dump_log</pre>"
        ."<br>"
        ."<h2>session</h2>"
        ."<pre>".Dumper($c->session)."</pre>"
        #        ."<script>alert(window.screen.availWidth"
        #        ."+\" \"+window.screen.availHeight)</script>"
    );
};

any '/logout.json' => sub($c) {
    logout($c);
    return $c->render(json => {logout => '1'});
};

any '/logout' => sub {
    my $c = shift;
    my $redirect_to = logout($c);
    $c->redirect_to($redirect_to ? $redirect_to : '/');
};

get '/anonymous' => sub {
    my $c = shift;
#    $c->render(template => 'bases', base => list_bases());
    $USER = _anonymous_user($c);
    return list_bases_anonymous($c) if ($USER);
    return access_denied($c);
};

get '/anonymous_logout.html' => sub {
    my $c = shift;
    $c->session('anonymous_user' => '');
    return $c->redirect_to('/');
};

get '/anonymous/(#base_id).html' => sub {
    my $c = shift;

    $c->stash(_anonymous => 1 , _logged_in => 0);
    _init_error($c);
    my $base_id = $c->stash('base_id');
    my $base = $RAVADA->search_domain_by_id($base_id);

    $USER = _anonymous_user($c);
    return quick_start_domain($c,$base->id, $USER->name) if ($USER);
};

get '/settings_global.json' => sub($c) {
    $RAVADA->is_in_maintenance();
    return $c->render(json => $RAVADA->settings_global );
};

post '/settings_global' => sub($c) {
    my $arg = decode_json($c->req->body);

    my $reload=0;
    $RAVADA->update_settings_global($arg, $USER, \$reload);

    return $c->render(json => { ok => 1, reload => $reload });
};

any '/admin/group/#type/#name' => sub($c) {
    my $name = $c->stash('name');
    my $type= $c->stash('type');
    return _admin_group($c, $name, $type);
};

sub _upload_members($c, $name) {
    my $csv = $c->req->upload('members');
    my $strict = $c->req->param('strict');

    if($csv->headers->content_type !~ m{text/(csv|plain)}) {
        if ($csv->headers->content_type =~ m{application/octet-stream}) {
            $USER->send_message("Error processing batch file, probably empty file");
            return;
        }
        $USER->send_message("Wrong content type ".$csv->headers->content_type
                    ." , it should be text/csv or plain"
        );
        return;
    }

    my ($found, $count, $error) = $RAVADA->upload_group_members(
        $name, $csv->slurp, $strict
    );

    $USER->send_message("$found entries found in file, $count users added to group $name");
    my $n_msg=0;
    for my $msg (@$error) {
        $USER->send_message($msg);
        last if $n_msg++>10;
    }
}

sub _admin_group($c,$name, $type) {
    return access_denied($c)    unless $USER->can_manage_groups
        || $USER->can_view_groups;

    _add_admin_libs($c);

    if ( $c->req->method eq 'POST') {
        _upload_members($c, $name);
    }


    my ($group, $id_group);
    if ($type eq 'ldap') {
        $group = Ravada::Auth::LDAP::search_group(name => $name);
        return not_found($c) if !$group;
        $c->stash(object_class => [ grep !/^top$/,$group->get_value('objectClass')]);
        $c->stash(group_name => $name);
    } elsif ($type eq 'local') {
        if ($name =~ /^\d+$/) {
            $group = Ravada::Auth::Group->open($name) if Ravada::Auth::Group::exists_id($name);
        } else {
            $group = Ravada::Auth::Group->new(name => $name);
        }
        return not_found($c) if !$group || !$group->id;
        $c->stash(object_class => [ ]);
        $c->stash(group_name => $group->name);
        $id_group = $group->id
    } else {
        die "Error group type '$type' unknown for group $name";
    }

    return $c->render( template => "/main/admin_group"
        ,group => $group
        ,type => $type
        ,group_id => $id_group
    );
};

get '/admin/storage' => sub($c) {

    _add_admin_libs($c);
    _select_vm($c);

    $c->stash(tab => ($c->param('tab') or ''));
    $c->stash('item' => 'storage');
    $c->stash('id' => $c->stash('id_vm'));

    return $c->render( template => '/main/settings_generic' );

};

get '/admin/hostdev/(#vm_type)' => sub($c) {
    _admin_host_devices($c);
};

sub _select_vm_by_type($c) {

    my $type = $c->stash('vm_type');

    my $sql = "SELECT id,name,vm_type FROM vms "
        ." WHERE hostname='localhost'";
    $sql .= " AND vm_type=?" if defined $type;

    my $sth = $RAVADA->_dbh->prepare($sql);

    if (defined ($type)) {
        $sth->execute($type);
    } else {
        $sth->execute();
    }
    my ($id,$name,$vm_type) = $sth->fetchrow;

    return not_found($c, 404, "vm type ".($type or 'UNDEF')." not found")
    if !$id;

    $c->stash('vm_type' => $vm_type);
    $c->stash('id_vm' => $id);

}

sub _admin_host_devices($c) {
    _add_admin_libs($c);

    _select_vm_by_type($c);

    my $req = Ravada::Request->list_host_devices(
        uid => Ravada::Utils::user_daemon->id
    );
    $RAVADA->wait_request($req);

    $c->stash(tab => ($c->param('tab') or ''));
    $c->stash('id' => $c->stash('id_vm'));

    return $c->render( template => '/main/admin_hostdev' );

};


get '/admin/networks/*id_vm' => { id_vm => undef} => sub($c) {

    return access_denied($c) unless $USER->is_admin
    || $USER->can_create_networks || $USER->can_manage_all_networks;

    _add_admin_libs($c);

    _select_vm($c) if !defined $c->stash('id_vm');

    Ravada::Request->list_networks(
        uid => $USER->id
        ,id_vm => $c->stash('id_vm')
    );

    return $c->render( template => '/main/admin_networks' );

};

any '/admin/#type' => sub {
  my $c = shift;

  return admin($c)  if $c->stash('type') eq 'machines'
                        && $USER->is_operator;

  return access_denied($c)    if !$USER->is_operator;

  return admin($c);
};

any '/new_machine.html' => sub {
    my $c = shift;
    return access_denied($c)    if !$USER->can_create_machine;
    return new_machine($c);
};

any '/copy_machine' => sub {
    my $c = shift;
    return new_machine_copy($c);
};

get '/domain/new.html' => sub {
    my $c = shift;

    return access_denied($c) if !_logged_in($c) || !$USER->is_admin();
    $c->stash(error => []);
    return $c->render(template => "main/new_machine");

};

get '/list_vm_types.json' => sub {
    my $c = shift;
    $c->render(json => $RAVADA->list_vm_types);
};

get '/list_storage_pools/(:id_vm)' => sub($c) {

    my $active = $c->param('active');
    $c->render(json => $RAVADA->list_storage_pools($USER->id, $c->stash('id_vm'),$active));
};

get '/list_nodes.json' => sub {
    my $c = shift;
    $c->render(json => [$RAVADA->list_vms]);
};

get '/list_nodes_by_id.json' => sub {
    my $c = shift;
    return _access_denied($c) if !$USER || !$USER->is_admin;

    $c->render(json => $RAVADA->list_nodes_by_id);
};

get '/list_host_devices/(:id_vm)' => sub($c) {
    $c->render(json => Ravada::WebSocket::_list_host_devices($RAVADA
            , {
                channel => '/'.$c->stash('id_vm')
                ,login => $USER->name
            }
        )
    );
};

get '/machine/host_device/add/(#id_domain)/(#id_hd)' => sub($c) {

    return access_denied($c) unless $USER->is_admin;

    my $domain = Ravada::Front::Domain->open($c->stash('id_domain'));
    my $ok = 0;
    eval {
        $domain->add_host_device($c->stash('id_hd'));
        $ok = 1;
    };
    return $c->render(json => { ok => $ok, error => ($@ or '') } );
};

post '/node/host_device/add' => sub($c) {
    my $arg = decode_json($c->req->body);
    my $id_vm = $arg->{id_vm} or die "Error: missing id_vm ".Dumper($arg);
    my $name = $arg->{name};

    my $vm = Ravada::VM->open( readonly => 1
        ,id => $id_vm
    );
    my @args = ( template => $arg->{template} );
    push @args, ( name => $name ) if $name;

    my $id;
    eval { $id = $vm->add_host_device(@args) };
    if ($@) {
        return $c->render( json => { ok => 0, error => ''.$@ } );
    }
    Ravada::Request->list_host_devices(
        uid => Ravada::Utils::user_daemon->id
        ,id_host_device => $id
        ,_force => 1
    ) if $id;

    return $c->render( json => { ok => $id } );
};

post '/node/host_device/update' => sub($c) {
    my $arg = decode_json($c->req->body);
    for my $key ( keys %$arg ) {
        delete $arg->{$key} if $key =~ /^_/ || $key =~ /^(devices|\$\$hashKey)$/;
    }
    my $id = $arg->{id};
    my $ok;
    eval {
        $ok = $RAVADA->update_host_device($arg);
    };
    my $error = ( $@ or '');
    return $c->render( json => { ok => $ok, error => $error } );
};

get '/node/host_device/remove/(:id)' => sub($c) {
    return access_denied($c) unless $USER->is_admin;
    my $id = $c->stash('id');
    my $req = Ravada::Request->remove_host_device(
        uid => $USER->id
        ,id_host_device => $id
    );
    return $c->render(json => { ok => 1 });
};

get '/list_routes.json' => sub {
    my $c = shift;
    $c->render(json => [ Ravada::Route->list_networks ]);
};

get '/v2/vm/list_networks/(:id_vm)' => sub {
    my $c = shift;

    return access_denied($c) unless $USER->is_admin
    || $USER->can_create_networks || $USER->can_manage_all_networks;

    $c->render(json => Ravada::Front->list_networks($c->stash('id_vm'), $USER->id) );
};

get '/route/info/#id' => sub($c) {
    my $sth = $RAVADA->_dbh->prepare("SELECT * from networks WHERE id=?");
    $sth->execute($c->stash('id'));
    my $row = $sth->fetchrow_hashref;
    return $c->render(json => $row);
};

get '/v2/route/remove/#id' => sub($c) {
    my $sth = $RAVADA->_dbh->prepare("DELETE from networks WHERE id=?");
    $sth->execute($c->stash('id'));
    $USER->send_message("Network ".$c->stash('id')." removed");
    return $c->render(json => { ok => 1 });
};

get '/route/list_domains/#id' => sub($c) {
    return $c->render( json => $RAVADA->list_bases_network($c->stash('id')));
};

any '/network/new' => sub($c) {
    return access_denied($c) unless $USER->is_admin
    || $USER->can_create_networks || $USER->can_manage_all_networks;

    _add_admin_libs($c);
    _select_vm($c);
    $c->stash(item => 'network', id => undef, tab => undef);

    return $c->render( template => '/main/network_new' );
};

any '/v2/network/new/#id_vm' => sub($c) {

    return access_denied($c) unless $USER->is_admin
    || $USER->can_create_networks || $USER->can_manage_all_networks;

    my $name = 'net';
    if ( $c->req->method eq 'POST') {
        my $arg = decode_json($c->req->body);
        $name = $arg->{name} if $arg->{name};
    }

    my $req = Ravada::Request->new_network(
        uid => $USER->id
        ,id_vm => $c->stash('id_vm')
        ,name => $name
    );
    $RAVADA->wait_request($req);
    my $data = {};
    if ($req->status eq 'done' && $req->output && $req->output =~ /^\{/) {
        eval {
            $data = decode_json($req->output);
        };
        warn $@ if $@;
        $data->{_owner} = {
            id => $USER->id
            ,name => $USER->name
        };
    }
    $c->render(json => $data);
};

get '/v2/network/info/#id' => sub($c) {
    my $sth = $RAVADA->_dbh->prepare("SELECT * from virtual_networks WHERE id=?");
    $sth->execute($c->stash('id'));
    my $row = $sth->fetchrow_hashref;
    $row->{_owner}
        = { id => $row->{id_owner}
            ,name => Ravada::Utils::search_user_name($RAVADA->_dbh,$row->{id_owner})
        };
    return $c->render(json => $row);
};

sub _network_id_owner($id_network) {

    confess "Error: undefined id_network "if !defined $id_network;
    my $sth = $RAVADA->_dbh->prepare("SELECT * FROM virtual_networks "
        ." WHERE id=?");
    $sth->execute($id_network);
    my $row = $sth->fetchrow_hashref;
    warn "Error: network id=$id_network not found " if !$row->{id};

    return ($row->{id_owner} or 0 );
}

post '/v2/network/set' => sub($c) {
    my $arg = decode_json($c->req->body);
    delete $arg->{_owner};

    return access_denied_json($c)
    unless $USER->is_admin || $USER->can_manage_all_networks
    ||  ($USER->can_create_networks
            && ( !$arg->{id} || $USER->id == _network_id_owner($arg->{id}))
        );

    my $req;
    my %data;
    if ($arg->{id}) {
        $req = Ravada::Request->change_network(
            uid => $USER->id
            ,data => $arg
        );
    } else {
        my $id_vm = delete $arg->{id_vm};
        $req = Ravada::Request->create_network(
            uid => $USER->id
            ,id_vm => $id_vm
            ,data => $arg
        );
        $RAVADA->wait_request($req, 120);
        my $out = {};
        eval { $out = decode_json($req->output) if $req->output };
        warn $@ if $@;
        %data = %$out if $out && keys %$out;
    }
    $data{error} = $req->error;
    return $c->render(json => \%data, output => $req->output , error => $req->error);
};

sub _add_admin_libs($c) {
    push @{$c->stash->{js}}, '/js/admin.js?v='.$RAVADA->version;
    push @{$c->stash->{css}}, '/css/admin.css';
}

any '/route/new' => sub($c) {
    _add_admin_libs($c);
    return $c->render(template => "/main/route_new");
};

post '/v2/route/set' => sub($c) {
    return access_denied($c) if !$USER->is_admin;

    my $arg = decode_json($c->req->body);
    return _update_fields($c, "networks", $arg);
};

get '/v2/route/set/#id/#field/#id_domain/#value' => sub ($c) {
    my $id_network = $c->stash('id');
    my $field = $c->stash('field');
    my $id_domain = $c->stash('id_domain');
    my $value = $c->stash('value');

    my $sth = $RAVADA->_dbh->prepare(
        "SELECT * "
        ." FROM domains_network WHERE id_network = ? "
        ." AND id_domain= ? "
    );
    $sth->execute($id_network, $id_domain);
    my $domain_network = $sth->fetchrow_hashref();
    my $id = $domain_network->{id};

    return $c->render( json => { ok => 1 } )
        if $id && exists $domain_network->{$field}
        && $domain_network->{$field} eq $value;

    my $change = '';
    if ($id) {
        $change = "Updating";
        $sth = $RAVADA->_dbh->prepare(
            "UPDATE domains_network set $field=? "
            ." WHERE id=?"
        );
        $sth->execute($value, $id);
    } else {
        $change = "Inserting";
        $sth = $RAVADA->_dbh->prepare(
            "INSERT INTO domains_network "
            ."( id_network, id_domain, $field) "
            ." VALUES(?,?,?) "
        );
        $sth->execute($id_network, $id_domain, $value);
    }
    $USER->send_message("$change $field = $value for network $id_network "
        .", machine $id_domain");
    return $c->render( json => { ok => 1 } );

};

post '/v1/exists/:item' => sub($c) {
    return unless $USER->is_operator();

    my $table = $c->stash("item");
    die "Error: invalid item '$table'" unless $table =~ m{^\w[\w\d_]+$};

    my $arg = decode_json($c->req->body);

    my $id = delete $arg->{id};
    die "Error: invalid id '$id'" if defined $id && $id !~ m{^\d+$};

    my $query = '';
    my @fields;
    for my $field ( sort keys %$arg ) {
        next if $field =~ /^_/;
        die "Error: invalid field '$field'" unless $field =~ m{^\w[\w\d_]+$};
        $query .= " AND " if $query;
        $query .= "$field=?";
        push @fields, ($field);
    }
    return if !@fields;

    $query .= " AND id <> $id " if defined $id;


    my $sth = $RAVADA->_dbh->prepare("SELECT id FROM $table WHERE $query ");
    $sth->execute(map { $arg->{$_} } @fields );

    my $row = $sth->fetchrow_hashref;
    $row->{id} = undef if !exists $row->{id};

    return $c->render(json => $row );

};

get '/node/enable/(:id).json' => sub {
    my $c = shift;
    return access_denied($c) if !$USER->is_admin;
    return $c->render(json => {enabled => $RAVADA->enable_node($c->stash('id'),1)});
};

get '/node/disable/(:id).json' => sub {
    my $c = shift;
    return access_denied($c) if !$USER->is_admin;

    my $machines = $RAVADA->_list_machines_vm($c->stash('id'));
    for ( @$machines ) {
        my $req = Ravada::Request->shutdown_domain( uid => $USER->id , id_domain => $_->{id} );
    }
    return $c->render(json => {enabled => $RAVADA->enable_node($c->stash('id'),0)});
};

get '/v1/node/remove/(:id)' => sub {
    my $c = shift;
    return access_denied($c) if !$USER->is_admin;
    return $c->render(json => {remove => $RAVADA->remove_node($c->stash('id'),1)});
};

get '/node/shutdown/(:id).json' => sub {
    my $c = shift;
    return access_denied($c) if !$USER->is_admin;

    my $machines = $RAVADA->_list_machines_vm($c->stash('id'));
    for ( @$machines ) {
        my $req = Ravada::Request->shutdown_domain(
                    uid => $USER->id
            , id_domain => $_->{id}
        );
    }
    my $at = 0;
    if (@$machines) {
        $at = time + 60 + scalar @$machines;
    }
    my $req = Ravada::Request->shutdown_node(
                id_node => $c->stash('id')
                ,at => $at
    );
    Ravada::Request->connect_node(
                id_node => $c->stash('id')
                ,at => $at + 10
    );
    return $c->render(json => {id_req => $req->id });
};

get '/node/start/(:id).json' => sub {
    my $c = shift;
    return access_denied($c) if !$USER->is_admin;
    my $req = Ravada::Request->start_node(
                id_node => $c->stash('id')
    );
    for my $seconds ( 30,60,90,120 ) {
        Ravada::Request->connect_node(
            id_node => $c->stash('id')
            ,at => time + $seconds
        );
    }

    return $c->render(json => {id_req => $req->id });

};

any '/v1/node/new' => sub {
    my $c = shift;
    return access_denied($c)    if !$USER->is_admin;
    return new_node($c);
};

get '/node/connect/(#backend)/(#hostname)' => sub {
    my $c = shift;
    return access_denied($c)    if !$USER->is_admin;
    my $req = Ravada::Request->connect_node(
                backend => $c->stash('backend')
                    ,hostname => $c->stash('hostname')
    );
    return $c->render(json => {id_req => $req->id });
};

get '/node/connect/(#id)' => sub {
    my $c = shift;
    return access_denied($c)    if !$USER->is_admin;
    my $req = Ravada::Request->connect_node(
        id_node => $c->stash('id')
    );
    return $c->render(json => {id_req => $req->id });
};


get '/list_bases.json' => sub {
    my $c = shift;

    my $domains = $RAVADA->list_bases();
    my @domains_show = @$domains;
    if (!$USER->is_admin) {
        @domains_show = ();
        for (@$domains) {
            push @domains_show,($_) if $_->{is_public};
        }
    }
    $c->render(json => [@domains_show]);

};

get '/list_images.json' => sub {
    my $c = shift;

    return access_denied($c) unless _logged_in($c)
        && $USER->can_create_machine();

    my $vm_name = $c->param('backend');

    $c->render(json => $RAVADA->list_iso_images($vm_name or undef));
};

get '/list_machine_types.json' => sub {
    my $c = shift;

    return access_denied($c) unless _logged_in($c)
        && $USER->can_create_machine();

    my $vm_name = $c->param('vm_type');
    my $types = $RAVADA->list_machine_types($USER->id, $vm_name );
    $c->render(json => $types);
};

get '/list_cpu_models.json' => sub {
    my $c = shift;

    return $c->render(json => []) unless _logged_in($c)
        && $USER->can_create_machine();

    my $id_domain = $c->param('id_domain');
    my $models = $RAVADA->list_cpu_models($USER->id, $id_domain);
    $c->render(json => $models);
};



get '/iso_file.json' => sub {
    my $c = shift;
    return access_denied($c) unless _logged_in($c)
        && $USER->can_create_machine();
    my @isos =('<NONE>');
    push @isos,(@{$RAVADA->iso_file('KVM')});
    $c->render(json => \@isos);
};

get '/list_machines.json' => sub {
    my $c = shift;

    return access_denied($c) unless _logged_in($c)
        && (
            $USER->can_list_machines
            || $USER->can_list_own_machines()
            || $USER->can_list_clones()
            || $USER->can_list_clones_from_own_base()
            || $USER->is_admin()
        );

    return $c->render( json => $RAVADA->list_machines($USER) );

};

get '/list_machines_user.json' => sub {
    my $c = shift;
    return $c->render( json => $RAVADA->list_machines_user($USER));
};

get '/list_bases_anonymous.json' => sub {
    my $c = shift;

    # shouldn't this be "list_bases" ?
    $c->render(json => $RAVADA->list_bases_anonymous(_remote_ip($c)));
};

get '/list_lxc_templates.json' => sub {
    my $c = shift;
    $c->render(json => $RAVADA->list_lxc_templates);
};

# machine commands

get '/machine/info/(:id).(:type)' => sub {
    my $c = shift;
    my $id = $c->stash('id');
    die "No id " if !$id;

    my ($domain) = _search_requested_machine($c);
    return access_denied($c)    if !$domain;

    return access_denied($c,"Access denied to user ".$USER->name) unless $USER->can_manage_machine($domain->id);

    my $info = $domain->info($USER);
    if ($domain->is_active && ( !exists $info->{ip} || !$info->{ip})) {
        Ravada::Request->refresh_machine(id_domain => $domain->id, uid => $USER->id);
    }
    return $c->render(json => $info);
};

get '/machine/requests/(:id).json' => sub {
    my $c = shift;
    my $id_domain = $c->stash('id');
    return access_denied($c) unless $USER->can_manage_machine($id_domain);

    $c->render(json => $RAVADA->list_requests($id_domain,10));
};

any '/machine/manage/(:id).(:type)' => sub {
   	 my $c = shift;
     Ravada::Request->refresh_machine(id_domain => $c->stash('id'), uid => $USER->id);
     return manage_machine($c);
};

get '/machine/list_shares/#id' => sub($c) {
    my $id = $c->stash('id');

    my $machine = Ravada::Front::Domain->open($id);
    return json_error($c, "Unknown machine id=$id" ) if !$machine;

    return access_denied($c) unless $USER->id == $machine->id_owner
    || $USER->is_admin;

    return $c->render(json => [$machine->list_shares]);
};

get '/machine/share/#id/#name' => sub($c) {
    _add_share($c);
};

get '/machine/remove_share/#id/#name' => sub($c) {
    _add_share($c,1);
};

sub _add_share($c, $remove=0) {
    my $id = $c->stash('id');
    my $name = $c->stash('name');

    my $machine = Ravada::Front::Domain->open($id);

    return json_error($c, "Unknown machine id=$id" ) if !$machine;

    return access_denied_json($c) unless $machine->id_owner == $USER->id
    || $USER->is_admin;

    my $user_share = Ravada::Auth::SQL->new(name => $name);

    return json_error($c,"Unknown user name=$name" )
    if !$user_share || !$user_share->id;

    if ($remove) {
        return json_error($c,"Machine not shared with user $name" )
            if !$user_share->_machine_shared($id);
    } else {
        return json_error($c,"Machine already shared with user $name" )
            if $user_share->_machine_shared($id);

    }

    if ($remove) {
        $machine->remove_share($user_share);
        $USER->send_message("Removed shared access from user $name");
    } else {
        $machine->share($user_share);
        $USER->send_message("Machine shared with user $name");
    }

    return $c->render(json => {id => $id, name => $name});
};

any '/(:item)/settings/(:id).html' => sub($c) {

    _add_admin_libs($c);
    $c->stash(tab => ($c->param('tab') or ''));
    my $table = $c->stash('item')."s";
    $table = 'virtual_networks' if $table eq 'networks';
    $table = 'networks'         if $table eq 'routes';

    return access_denied($c) if $table !~ /^[a-z][a-z_]+$/i;

    if ($table eq 'virtual_networks') {
        my $id = $c->stash('id'); # get id network
        return access_denied_json($c)
            unless $USER->is_admin || $USER->can_manage_all_networks
        ||  ($USER->can_create_networks && $USER->id == _network_id_owner($id))
        ;
    }

    my $id_vm;
    if ($table eq 'nodes') {
        $id_vm = $c->stash('id');
    } elsif ($table ne 'networks') {
        my $sth = $RAVADA->_dbh->prepare(
            "SELECT id,id_vm FROM $table WHERE id=?"
        );
        $sth->execute($c->stash('id'));
        my $id2;
        ($id2, $id_vm) = $sth->fetchrow;
        return not_found($c, 404, $c->stash('item')." ".$c->stash('id')
        ." not found in $table") if !$id2;

    }
    $c->stash('id_vm' => $id_vm);

    return $c->render( template => '/main/settings_generic' );
};

any '/(:item)/settings/' => sub($c) {

    _add_admin_libs($c);
    $c->stash(tab => ($c->param('tab') or ''));
    my $item = $c->stash('item');

    return $c->render( template => '/main/settings_generic' );
};

get '/node/info/(:id).json' => sub($c) {
    my $sth = $RAVADA->_dbh->prepare("SELECT * FROM vms "
        ." WHERE id=?"
    );
    $sth->execute($c->stash('id'));
    my $row = $sth->fetchrow_hashref;
    delete $row->{tls};
    for my $field (keys %$row) {
        delete $row->{$field} if $field =~ /^cache/;
    }
    for (('version','security','connection_args','vm_type')) {
        delete $row->{$_};
    }
    return $c->render(json => $row);
};

get '/node/list_bases/(:id)' => sub($c) {
    return $c->render( json => $RAVADA->_list_bases_vm_all($c->stash('id')));
};

any '/hardware/(:id).(:type)' => sub {
   	 my $c = shift;
     return $c->render(template => 'main/hardware');
};

get '/machine/view/(:id).(:type)' => sub {
    my $c = shift;
    my $id = $c->stash('id');
    my $type = $c->stash('type');

    my ($domain) = _search_requested_machine($c);
    return access_denied($c)    if !$domain;

    $c->stash(id_base => undef);
    return view_machine($c) if $USER->is_admin
                              || $USER->can_view_all;

    if ( $domain->id_owner == $USER->id || $USER->can_start_machine($domain) ) {
        if ( $domain->id_base) {
            my $base = Ravada::Front::Domain->open($domain->id_base);
            if (($base->show_clones && $USER->allowed_access_group($base->id))
                || ($base->is_public  && $USER->allowed_access($base->id))
            ) {
                return view_machine($c);
            }
        } else {
                return view_machine($c);
        }
    }
    return access_denied($c);
};

any '/machine/clone/(:id).(:type)' => sub {
    my $c = shift;

    return clone_machine($c) if $USER->can_clone_all();

    if ( $USER && $USER->can_clone() && !$USER->is_temporary() ) {
        my $base = Ravada::Front::Domain->open($c->stash('id'));
        if (!$base->is_public) {
            return access_denied($c) if !$base->show_clones;

            my @clones = $base->clones();
            my ($clone) = grep { $_->{id_owner} == $USER->id } @clones;
            return access_denied($c) if !$clone;
        }

        return access_denied($c) if !$USER->allowed_access($base->id);

        return clone_machine($c);
    }

    my $bases_anonymous = $RAVADA->list_bases_anonymous(_remote_ip($c));
    for (@$bases_anonymous) {
        if ($_->{id} == $c->stash('id') ) {
            return clone_machine($c,1);
        }
    }

    return login($c)    if !$USER || $USER->is_temporary;
    return access_denied($c);
};

get '/machine/shutdown/(:id).(:type)' => sub {
        my $c = shift;
    return access_denied($c)        if !$USER ->can_shutdown($c->stash('id'));

        return shutdown_machine($c);
};

get '/machine/force_shutdown/(:id).(:type)' => sub {
        my $c = shift;
    return access_denied($c)        if !$USER ->can_shutdown($c->stash('id'));

        return force_shutdown_machine($c);
};

get '/machine/reboot/(:id).(:type)' => sub {
        my $c = shift;
    return access_denied($c)        if !$USER ->can_reboot($c->stash('id'));

        return reboot_machine($c);
};

get '/machine/shutdown_start/(:id).(:type)' => sub($c) {
    return access_denied($c)        if !$USER ->can_reboot($c->stash('id'));
    return shutdown_start($c);
};


any '/machine/remove/(:id).(:type)' => sub {
        my $c = shift;
    return access_denied($c)       if !$USER->can_remove_machine($c->stash('id'));
        return remove_machine($c);
};

any '/machine/remove_clones/(:id).(:type)' => sub {
    my $c = shift;

    # TODO : call to $domain->_allow_remove();
	return access_denied($c)
        unless
            $USER -> can_remove_clone_all()
	        || $USER->can_remove_clone()
            || $USER->can_remove_all();
    return remove_clones($c);
};

get '/machine/prepare/(:id).(:type)' => sub {
        my $c = shift;
        return prepare_machine($c);
};

get '/machine/set_base_vm/(:id_vm)/(:id_domain).(:type)' => sub {
    my $c = shift;
    return set_base_vm($c, 1);
};

get '/machine/remove_base_vm/(:id_vm)/(:id_domain).(:type)' => sub {
    my $c = shift;
    return set_base_vm($c, 0);
};

get '/machine/remove_b/(:id).(:type)' => sub {
        my $c = shift;
        return remove_base($c);
};

get '/machine/remove_base/(:id).(:type)' => sub {
    my $c = shift;
    return remove_base($c);
};

get '/machine/screenshot/(:id).(:type)' => sub {
        my $c = shift;
        my $domain = _search_requested_machine($c);
        return access_denied($c)   if (!$USER->can_screenshot() || $domain->is_base());
        return screenshot_machine($c);
};

get '/machine/copy_screenshot/(:id).(:type)' => sub {
        my $c = shift;
        my $domain = _search_requested_machine($c);
        return access_denied($c) if (!$USER->is_admin() || $domain->id_base() == NULL );
        return copy_screenshot($c);
};

get '/machine/pause/(:id).(:type)' => sub {
        my $c = shift;
        return pause_machine($c);
};

get '/machine/hibernate/(:id).(:type)' => sub {
        my $c = shift;
          return access_denied($c)
             unless $USER->is_admin() || $USER->can_shutdown($c->stash('id'));

        return hybernate_machine($c);
};

get '/machine/resume/(:id).(:type)' => sub {
        my $c = shift;
        return resume_machine($c);
};

get '/machine/start/(:id).(:type)' => sub {
        my $c = shift;
        return start_machine($c);
};

get '/machine/exists/#name' => sub {
    my $c = shift;
    my $name = $c->stash('name');
    #TODO
    # return failure if it can't find the name in the URL

    return $c->render(json => $RAVADA->domain_exists($name));

};

post '/machine/hardware/change' => sub {
    my $c = shift;
    my $arg = decode_json($c->req->body);

    my $domain = Ravada::Front::Domain->open(delete $arg->{id_domain});

    return access_denied($c)
        unless $USER->id == $domain->id_owner || $USER->is_admin;

    my $hardware = delete $arg->{hardware} or die "Missing hardware name";
    my $index = delete $arg->{index};
    my $data = delete $arg->{data};

    die "Unknown fields in request ".Dumper($arg) if keys %{$arg};
    return $c->render(json => { req => Ravada::Request->change_hardware(
                id_domain => $domain->id
                ,hardware => $hardware
                ,index => $index
                ,data => $data
                ,uid => $USER->id
            )
    });
};

get '/machine/list_access/(#id_domain)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    my @access0 = $domain->list_access();
    my $default = {};
    my @access;
    for my $access (@access0) {
        if ($access->{value} eq '*') {
            $default = $access;
            next;
        }
        push @access,($access);
    }

    return $c->render(json => {list => \@access, default => $default} );
};

get '/machine/check_access/(#id_domain)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    my %client;
    for my $name (@{$c->req->headers->names}) {
        $client{$name}= $c->req->headers->header($name);
    }
    return $c->render( json => { ok => $domain->access_allowed(client => \%client)});
};

get '/machine/check_access/(#id_domain)/(#user)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $user_name = $c->stash('user');
    return if !$user_name || $user_name eq 'undefined';
    my $user = Ravada::Auth::SQL->new(name => $user_name);

    if (!$user->id && Ravada::Auth::LDAP::search_user($user_name)) {
        Ravada::Auth::SQL::add_user(
            name => $user_name
            ,is_external => 1
            ,external_auth => 'ldap'
        );
        $user = Ravada::Auth::SQL->new(name => $user_name);
    }

    return $c->render( json => { ok => $user->allowed_access($c->stash('id_domain'))});
};


get '/machine/delete_access/(#id_domain)/(#id_access)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);
    $domain->delete_access($c->stash('id_access'));

    # delete default if it is the only one left
    my @access = $domain->list_access();
    if (scalar @access == 1 && $access[0]->{value} eq '*') {
        $domain->delete_access($access[0]->{id});
    }

    return $c->render(json => { ok => 1 });

};

get '/machine/move_access/(#id_domain)/(#id_access)/(#position)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    $domain->move_access($c->stash('id_access'),$c->stash('position'));

    return $c->render(json => { ok => 1 });

};

get '/machine/list_access_groups/#type/#id_domain' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);
    return $c->render( json => [ $domain->list_access_groups($c->stash('type')) ] );
};

post '/machine/add_access_group/#type/#id_domain' => sub($c) {
    my $type = $c->stash('type');
    my $id_domain = $c->stash('id_domain');
    my $arg = decode_json($c->req->body);
    my $group_name = delete $arg->{group};

    my $id_group;
    my $ok = 0;
    eval {
        my $domain = Ravada::Front::Domain->open($id_domain);
        if ($group_name =~ /^\d+$/) {
            $id_group = $group_name;
            $group_name =undef;
        }
        if ($type eq 'local' && !$id_group) {
            my $group = Ravada::Auth::Group->new(name => $group_name);
            $id_group = $group->id;
            $group_name=undef;
        }

        $domain->grant_access(type => "group.$type" , group => $group_name, id_group => $id_group);
        $ok =1;
    };
    return $c->render( json => { ok => $ok, error => $@ });
};

post '/machine/remove_access_group/#type/#id_domain' => sub($c) {

    my $arg = decode_json($c->req->body);

    my $group_name = delete $arg->{group};
    if ($c->stash('type') eq 'ldap') {

        my $sth = $RAVADA->_dbh->prepare("DELETE FROM group_access WHERE id_domain=? "
            ." AND name=? "
            ." AND type=?"
        );

        $sth->execute($c->stash('id_domain'), $group_name, $c->stash('type'));
    } else {
        my $group = Ravada::Auth::Group->new(name => $group_name);
        if ($group && $group->id) {
            my $sth = $RAVADA->_dbh->prepare("DELETE FROM group_access WHERE id_domain=? "
                ." AND id_group=? "
                ." AND type=?"
            );

            $sth->execute($c->stash('id_domain'), $group->id, $c->stash('type'));
        }

    }
    return $c->render(json => { ok => 1 } );
};

get '/machine/compact/(#id_domain)' => sub($c) {
    my $req = Ravada::Request->compact(
        id_domain => $c->stash('id_domain')
        ,uid => $USER->id
    );
    return $c->render(json => { request => $req->id });
};

get '/execution_machines_limit' => sub {
    my $c = shift;
    
    return get_execution_machines_limit_per_current_user($c);
};

get '/node/exists/#name' => sub {
    my $c = shift;
    my $name = $c->stash('name');

    return $c->render(json => $RAVADA->node_exists($name));

};
get '/machine/rename/#id/#value' => sub {
    my $c = shift;
    return access_denied($c)       if !$USER->can_manage_machine($c->stash('id'));
    return rename_machine($c);
};

any '/machine/copy' => sub {
    my $c = shift;
    return access_denied($c)    if !$USER -> can_clone_all();
    return copy_machine($c);
};

get '/machine/public/#id' => sub {
    my $c = shift;
    return machine_is_public($c);
};

get '/machine/public/#id/#value' => sub {
    my $c = shift;
    return machine_is_public($c);
};


post '/machine/set' => sub($c) {

    my $arg = decode_json($c->req->body);

    my $id = delete $arg->{id} or die "Error: missing id";
    my $options = delete $arg->{options};
    my $field = delete $arg->{field};

    die "Error: requires options or field ".join(" ",$arg)
    unless $options || $field;

    die "Error: missing value" if $field && !exists $arg->{value};
    die "Error: unnecessary value when options" if $options && exists $arg->{value};

    if ($field) {
        my $value = delete $arg->{value};
        my $value_text = (delete $arg->{value_text} or $value);

        machine_set_value($c, $id, $field, $value, $value_text);

        return $c->render(json => { $field => $value_text });
    } else {
        machine_change_hardware($c, $id, $options);
        machine_set_options($c, $id, $options);

        return $c->render(json => $options );
    }
};

get '/machine/set/#id/#field/#value' => sub {

    my $c = shift;
    my $id = $c->stash('id');
    my $field = $c->stash('field');
    my $value = $c->stash('value');

    machine_set_value($c, $id, $field, $value);
};

get '/node/set/#id/#field/#value' => {value => ''} => sub($c) {

    return access_denied($c) if !$USER->is_admin;

    my $id = $c->stash('id');
    my $field = $c->stash('field');
    my $value = $c->stash('value');

    my $node = Ravada::VM->open(id => $id , readonly => 1) or die "Unkown node $id";
    $USER->send_message("Setting $field to '$value' in ".$node->name)
        if $node->_data($field) ne $value;
    return $c->render(json => { $field => $node->_data($field, $value)});

};

post '/v1/node/set' => sub($c) {
    return access_denied($c) if !$USER->is_admin;

    my $arg = decode_json($c->req->body);
    delete $arg->{is_local};
    delete $arg->{has_bases};
    return _update_fields($c, "vms", $arg);
};

get '/machine/autostart/#id/#value' => sub {
    my $c = shift;
    my $req = Ravada::Request->domain_autostart(
        id_domain => $c->stash('id')
           ,value => $c->stash('value')
             ,uid => $USER->id
    );
    return $c->render(json => { request => $req->id});
};

get '/machine/display/(:driver)/(:id).(#extension)' => sub {
    my $c = shift;

    my $id = $c->stash('id');
    my $extension = $c->stash('extension');
    my $driver = $c->stash('driver');

    my $domain = $RAVADA->search_domain_by_id($id);
    return $c->render(text => "unknown machine id=$id") if !$domain;

    return access_denied($c)
        unless $USER->id eq $domain->id_owner
        || $USER->is_admin
        || $USER->can_view_all
    ;

    my $display = $domain->_get_display($c->stash('driver'));
    return $c->render(text => "unknown display ".$c->stash('driver')." for machine id=$id")
    if !$display;

    if ($driver =~ /spice/ ) {
        return _display_file_spice($c, $domain, $extension, $display);
    } elsif ($driver eq 'rdp' ) {
        return _display_file_rdp($c, $domain, $extension, $display);
    } else {
        return not_found($c);
    }
};

sub _display_file_rdp($c, $domain, $extension, $display) {
    $c->res->headers->content_type('application/x-rdp');
        $c->res->headers->content_disposition(
        "inline;filename=".$domain->id.".$extension");

    my $format = $extension;
    $format =~ s/^(\w+)\.(\w+)/$2/;
    return $c->render(data => $domain->_display_file_rdp($display), format => $format);
};

sub _display_file_spice($c, $domain, $extension, $display) {
    $c->res->headers->content_type('application/x-virt-viewer');
        $c->res->headers->content_disposition(
        "inline;filename=".$domain->id.".$extension");

    my $format = $extension;
    $format =~ s/^(\w+)\.(\w+)/$2/;
    my $tls = 0;
    $tls = 1 if $1 && $1 eq 'tls';
    return $c->render(data => $domain->_display_file_spice($display, $tls), format => $format);
};


# Network ##########################################################3

get '/network/interfaces/(:vm_type)/(:type)' => sub {
    my $c = shift;

    my $vm_type = $c->stash('vm_type');
    my $type = $c->stash('type');

    return $c->render( json => $RAVADA->list_network_interfaces(
               user => $USER
              ,type => $type
           ,vm_type => $vm_type
       )
    );
};

# Users ##########################################################3

##add user

any '/users/register' => sub {

       my $c = shift;
       return access_denied($c) if !$USER->is_admin();
       return register($c);
};

get '/group/new' => sub($c) {

    return access_denied($c) if !$USER->is_admin();

    my $type = ( $c->req->param('type') or 'local');
    $c->render(template => 'bootstrap/new_group', type => $type);
};

post '/group/new' => sub($c) {
    return new_group($c);
};

any '/group/upload_members.#req' => sub($c) {

    return access_denied_json($c) unless $USER->is_admin;
    _add_admin_libs($c);

    my $csv = $c->req->upload('users');
    if($csv->headers->content_type !~ m{text/(csv|plain)}) {
        return $c->render(status => 400
            ,text => "Wrong content type ".$csv->headers->content_type
                    ." , it should be text/csv or plain"
        );
    }

    my $strict = $c->req->param('strict');
    my $group_name = $c->req->param('group');

    my ($found, $count, $error) = $RAVADA->upload_group_members(
        $group_name, $csv->slurp, $strict
    );

    return $c->render(json =>
        { output => "$count users added"
        ,error => $error
    }) if $c->stash('req') eq 'json';

};

get '/group/#type/list_data' => sub($c) {
    return _group_list($c,1);
};

get '/group/#type/list' => sub($c) {
    return _group_list($c);
};

get '/group/#type/list/#filter' => sub($c) {
    return _group_list($c);
};

get '/group_local_list'=> sub($c) {
    $c->stash(type => 'local');
    return _group_list($c);
};

get '/group_ldap_list'=> sub($c) {
    $c->stash(type => 'ldap');
    return _group_list($c);
};

get '/group_local_list/#filter'=> sub($c) {
    $c->stash(type => 'local');
    return _group_list($c);
};

get '/group_ldap_list/#filter'=> sub($c) {
    $c->stash(type => 'ldap');
    return _group_list($c);
};

sub _group_list($c, $data=0) {
    return access_denied($c) unless $USER->can_view_groups || $USER->can_manage_groups;

    my $type = $c->stash('type');
    my $filter = $c->stash('filter');

    if ($type eq 'ldap') {
        $filter = '*' if !defined $filter;
        return _list_ldap_groups($c,$filter, $data);
    } elsif($type eq 'local') {
        return _list_sql_groups($c, $filter, $data);
    } else {
        return not_found($c);
    }
}

any '/admin/users/upload.#req' => sub($c) {
    return access_denied_json($c) unless $USER->is_admin;

    _add_admin_libs($c);

    my $type = $c->req->param('type');

    return $c->render(template => "/main/upload_users", done => 0, output => {}
        ,error => []
        ,type => 'sql') if !$type;

    my $create = ( $c->req->param('create') or 0);

    return $c->render(json => { error => "Unknown type $type" })
            if $type !~ /^(sql|ldap|sso|openid)/;

    my $file = $c->req->upload('users');

    if($file->headers->content_type =~ m{text/(csv|plain)}) {
        _upload_users_csv($c, $file, $type, $create);
    } elsif ( $file->headers->content_type =~ m{application/json}) {
        _upload_users_json($c, $file, $type, $create);
    } else {
        return $c->render(status => 400
            ,text => "Wrong content type ".$file->headers->content_type
                    ." , it should be text/csv , application/json or plain"
        );

    }
};

sub _upload_users_json($c, $file, $type, $create) {

    my ($result, $error)=$RAVADA->upload_users_json($file->slurp, $type);

    if ($create) {
        push @$error,("Warning: create not implemented with json upload");
    }
    return $c->render(json =>
        {
            output => $result
        ,error => $error
        }
    ) if $c->stash('req') eq 'json';

    return $c->render(template => "/main/upload_users"
        ,output => $result
        ,error => $error
        ,done => 1
    );
}

sub _upload_users_csv($c, $csv, $type, $create) {
    my ($found, $count, $error) = $RAVADA->upload_users(
        $csv->slurp, $type, $create
    );
    my $output = {
        users_found => $found
        ,users_added => $count
    };

    return $c->render(json =>
        {
        output => $output
        ,error => $error
        ,done => 1
    }) if $c->stash('req') eq 'json';

    return $c->render(template => "/main/upload_users"
        ,output => $output
        ,error => $error
        ,done => 1
    );
}

get '/admin/user/remove/#id' => sub($c) {
    return access_denied($c) unless $USER->is_admin;
    my $id = $c->stash('id');
    my $user = Ravada::Auth::SQL->search_by_id($id);
    return $c->render(message => "User not found. id=$id."
            , link => ['/admin/users','Users']
        ,template => '/main/standard_message'
        ,status => 404
    ) if !$user;
    $user->remove();

    return $c->render(message => "User removed : ".$user->name
            , link => ['/admin/users','Users']
        ,template => '/main/standard_message'
    );
};

any '/admin/user/(#id).(:type)' => sub {
    my $c = shift;

    return access_denied($c) unless $USER->can_manage_users() && $USER->can_grant();

    my $password = $c->param('password');

    _add_admin_libs($c);
    my $id = $c->stash('id');
    my $user;
    if ($id =~ /cn=(.*?),/) {
        $user = Ravada::Auth::SQL->new(name => $1);
    } elsif ($id =~ /^\d+$/) {
        $user = Ravada::Auth::SQL->search_by_id($id);
    } else {
        $user = Ravada::Auth::SQL->new(name => $id);
    }

    my $origin = $c->param('origin');
    $c->stash(origin => $origin, warning => '');
    return $c->render(text => "Unknown user ".$c->stash('id'))
        if !$user || (!$user->id && !$c->stash('origin'));

    if ($c->param('import') ) {
        return $c->render( text => "Error: missing origin") if !$origin;
        if ($user->id) {
            $c->stash( warning => "Warning: user already imported");
        } else {
            Ravada::Auth::SQL::add_user(name => $user->name, is_external => 1, is_temporary => 0
                , external_auth => $origin);
            $user = Ravada::Auth::SQL->new(name => $user->name);
        }
    }

    if ($password) {
        #TODO: set an error status
        return $c->render(text => "You can not change the password for "
            ."an external user. ".$user->name." is ".$user->external_auth
        ) if $user->is_external;
        my $force_change= $c->param('force_change_password');
        $user->change_password($password, $force_change);
    }

    $c->stash(user => $user);
    return $c->render(json => {name => $user->name}) if $c->stash('type') eq 'json';
    return $c->render(template => 'main/manage_user');
};

get '/user/grants/(:id)' => sub($c) {
    return access_denied($c) unless $USER->can_manage_users() && $USER->can_grant();

    my $user = Ravada::Auth::SQL->search_by_id($c->stash('id'));
    my %grants = $user->grants_info;
    for my $key (keys %grants) {
        my ($value, $type) = @{$grants{$key}};
        if ( $type eq 'boolean' ) {
            $grants{$key} = \1 if $value;
            $grants{$key} = \0 if !$value;
        } else {
            $grants{$key} = $value;
        }
    }
    return $c->render(json => \%grants);
};

get '/user/info/(:id)' => sub ($c) {
    return access_denied($c) unless $USER->can_manage_users();
    my $user = Ravada::Auth::SQL->search_by_id($c->stash('id'));
    my %info = %{$user->{_data}};
    for my $key (keys %info) {
        next if $key !~ /^is_/;
        if($info{$key}) {
            $info{$key} = \1;
        } else {
            $info{$key} = \0;
        }
    }
    return $c->render(json => \%info);
};

post '/user/set/(:id)' => sub ($c) {
    return access_denied($c) unless $USER->can_manage_users() && $USER->can_grant();

    my $user = Ravada::Auth::SQL->search_by_id($c->stash('id'));
    my $args = decode_json($c->req->body);
    lock_hash(%$args);

    my $sql = "";
    my @values;
    my $error = '';
    for my $key (sort keys %$args ) {
        if ($key !~ /^[a-z][a-z_]+$/) {
            my $error = "Permission '$key' invalid";
            $USER->send_message($error);
            return $c->render(json => { error => $error });
        }
        if ($key eq 'is_admin') {
            eval {
                if ( $args->{$key} eq 'true' || $args->{$key} eq '1' ) {
                    $USER->send_message("Granting admin permissions to user ".$user->name);
                    $USER->make_admin($user->id);
                } elsif ( $args->{$key} eq 'false' || !$args->{$key}) {
                    $USER->send_message("Revoking admin persmissions from user ".$user->name);
                    $USER->remove_admin($user->id);
                }
            };
            my $subj = $error;
            $USER->send_message($subj, $error) if $error;
            next;
        }
        $sql .= " , " if $sql;
        $sql .= "$key= ?";
        push @values,($args->{$key});
    }
    if ($sql) {
        $sql = "UPDATE users set $sql WHERE id=?";
        eval {
            my $sth = $RAVADA->_dbh->prepare($sql);
            $sth->execute(@values, $c->stash('id'));
        };
    }
    $error = $@;

    my $subj = $error;
    $subj =~ s/ at.*//;
    $USER->send_message($subj, $error) if $error;
    return $c->render(json => { error => $error });
};

get '/user/grant/(:id_user)/(:grant)/(:value)' => sub($c) {
    return access_denied($c) unless $USER->can_grant();

    my $user = Ravada::Auth::SQL->search_by_id($c->stash('id_user'));
    my $error = '';
    my $value = $c->stash('value');
    my $grant = $c->stash('grant');
    if ($value eq 'false' || !$value) {
        $value = 0;
    } elsif ($USER->grant_type($grant) eq 'boolean' ) {
        $value = 1;
    }
    eval { $USER->grant($user,$grant,$value) };
    $error = $@;
    my $info = '';
    if ($error) {
        $USER->send_message($error);
    } else {
        my $grant = $c->stash('grant');
        my $grant_type = $USER->grant_type($grant);
        $grant =~ s/_/ /g;
        if ( $value ) {
            $info = "Permission granted to user"
        } else {
            $info = "Permission revoked from user";
        }
        $grant .= " $value" if $grant_type eq 'int';
        $info = $c->stash->{i18n}->localize($info);

        $USER->send_message($info." ".Encode::decode_utf8($user->name)
            ." : $grant");
    }
    return $c->render(json => { error => $error, info => $info });
};

get '/user/list_groups/(#id_user)' => sub($c) {
    my $id_user = $c->stash('id_user');
    return _access_denied($c) unless $USER->is_admin || $id_user == $USER->id;

    my $user = Ravada::Auth::SQL->search_by_id($id_user);
    return $c->render(json => [] ) if !$user;
    return $c->render(json => [] ) if $user->external_auth && $user->external_auth eq 'ldap' && !$RAVADA->feature('ldap');
    return $c->render(json => [$user->groups()]);
};

any '/user/change_password' => sub {
    my $c = shift;
    return change_password($c);
};

get '/list_ldap_attributes/(#cn)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;


    return $c->render( json => {attributes => []} )
    if !$RAVADA->feature('ldap');

    my $cn = $c->stash('cn');
    return $c->render(json => $LDAP_ATTRIBUTES{$cn}) if exists $LDAP_ATTRIBUTES{$cn};

    my $user;
    eval {
        if ($cn =~ /^[a-z0-9\.-_]+/i) {
            ($user) = Ravada::Auth::LDAP::search_user(name => $cn.'*', escape_username => 0 , sizelimit => 2);
        } else {
            ($user) = Ravada::Auth::LDAP::search_user($cn);
        }
    };
    my $field = $Ravada::CONFIG->{ldap}->{field};
    $field = 'cn' if !defined $field;
    my $return;

    if ( $@ ) {
        my $error = $@;
        $error =~ s/(.*) at lib\/Ravada.*/$1/;
        $return = { error => $error };
    } elsif (!$user) {
        $return = { error => "User not found" };
    } else {
        my $values;
        for my $attribute ( sort $user->attributes ) {
            my @values = $user->get_value($attribute);
            if  ($attribute =~/Password|unicodePwd/i) {
                for (@values) {
                    s/./*/g;
                    $_='********' if length($_)>8;
                }
            }
            if (scalar(@values)<2) {
                $values->{$attribute} = $values[0];
            } else {
                $values->{$attribute} = \@values;
            }
        }
        my $user_name = $user->get_value($field);
        $return = {
            dn_found => $user->dn
            , name => $user_name
            , attributes => [sort { uc($a) cmp uc($b) } $user->attributes]
            , values => $values
        };
    }
    $return->{field} = $field;

    $c->session(ldap_attributes_cn => $cn) if $user;
    $LDAP_ATTRIBUTES{$cn} = $return;

    return $c->render( json => $return );
};

get '/count_ldap_entries/(#attribute)/(#value)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my @entries;
    eval {
        @entries = Ravada::Auth::LDAP::search_user(
            field => $c->stash('attribute')
            ,name => $c->stash('value')
            ,typesonly => 1
        );
    };
    if ($@) {
        if ( $@ =~ /Sizelimit exceeded/ ) {
            @entries = [ 'too many' ];
        } else {
            warn $@;
        }
    }
    return $c->render(json => { entries => scalar @entries });
};

post '/machine/add_access/(#id_domain)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    my $args = decode_json($c->req->body);

    my $attribute = delete $args->{attribute};
    my $value = delete $args->{value};
    my $type = delete $args->{type};

    my $allowed = delete $args->{allowed};
    if (!defined $allowed || !$allowed || $allowed =~ /false|undefined/) {
        $allowed = 0;
    } else {
        $allowed= 1;
    }
    my $last = delete $args->{last};
    if (!defined $last || !$last || $last =~ /false|undefined/) {
        $last = 0;
    } else {
        $last = 1;
    }
    confess "Error: unknown args ".Dumper($args) if keys %$args;

    $domain->grant_access(type => $type
            , attribute => $attribute
            , allowed => $allowed
            , value => $value
            , last => $last
    );
    _fix_default_ldap_access($c, $domain, $allowed)
    if $type eq 'ldap';

    return $c->render(json => { ok => 1 });

};

get '/add_ldap_access/(#id_domain)/(#attribute)/(#value)/(#allowed)/(#last)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    my $attribute = $c->stash('attribute');
    my $value = $c->stash('value');
    my $allowed = 1;
    if ($c->stash('allowed') eq 'false') {
        $allowed = 0;
    }
    my $last = 1;
    if ($c->stash('last') eq 'false') {
        $last = 0;
    }
    $last = 1 if !$allowed;

    eval { $domain->allow_ldap_access($attribute => $value, $allowed, $last ) };
    _fix_default_ldap_access($c,'ldap', $domain, $allowed) if !$@;
    return $c->render(json => { error => $@ }) if $@;
    return $c->render(json => { ok => 1 });

};

sub _fix_default_ldap_access($c, $type, $domain, $allowed) {
    my @list = $domain->list_ldap_access();
    my $default_found;
    for ( @list ) {
        if ( $_->{value} eq '*' ) {
            $default_found = $_->{id};
        }
    }
    if ( $default_found ) {
        $domain->move_ldap_access($default_found, +1);
        return;
    }
    my $allowed_default = 0;
    $allowed_default = 1 if !$allowed;
    eval { $domain->grant_access(type => $type
            , attribute => 'DEFAULT'
            , value => '*'
            , last => $allowed_default
    ) };
    die $@ if $@;
}

get '/delete_ldap_access/(#id_domain)/(#id_access)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    $domain->delete_ldap_access($c->stash('id_access'));

    # delete default if it is the only one left
    my @ldap_access = $domain->list_ldap_access();
    if (scalar @ldap_access == 1 && $ldap_access[0]->{value} eq '*') {
        $domain->delete_ldap_access($ldap_access[0]->{id});
    }

    return $c->render(json => { ok => 1 });
};

get '/list_ldap_access/(#id_domain)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    my @ldap_access = $domain->list_ldap_access();
    my $default = {};
    if (scalar @ldap_access && $ldap_access[-1]->{value} eq '*') {
        $default = pop @ldap_access;
    }
    return $c->render(json => {list => \@ldap_access, default => $default} );
};

get '/move_ldap_access/(#id_domain)/(#id_access)/(#count)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    $domain->move_ldap_access($c->stash('id_access'), $c->stash('count'));

    return $c->render(json => { ok => 1});
};

get '/set_ldap_access/(#id_domain)/(#id_access)/(#allowed)/(#last)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    my $allowed = $c->stash('allowed');
    if ($allowed =~ /false/ || !$allowed) {
        $allowed = 0;
    } else {
        $allowed = 1;
    }
    my $last= $c->stash('last');
    if ($last=~ /false/ || !$last) {
        $last= 0;
    } else {
        $last= 1;
    }

    $domain->set_ldap_access($c->stash('id_access'), $allowed, $last);
    return $c->render(json => { ok => 1});
};

get '/machine/set_access/(#id_domain)/(#id_access)/(#allowed)/(#last)' => sub {
    my $c = shift;

    return _access_denied($c) if !$USER->is_admin;

    my $domain_id = $c->stash('id_domain');
    my $domain = Ravada::Front::Domain->open($domain_id);

    my $allowed = $c->stash('allowed');
    if ($allowed =~ /false|undefined/ || !$allowed) {
        $allowed = 0;
    } else {
        $allowed = 1;
    }
    my $last= $c->stash('last');
    if ($last=~ /false|undefined/ || !$last) {
        $last= 0;
    } else {
        $last= 1;
    }

    $domain->set_access($c->stash('id_access'), $allowed, $last);
    return $c->render(json => { ok => 1});
};

##############################################

post '/request/(:name)/' => sub {
    my $c = shift;
    my $name = $c->stash('name');

    my $args = decode_json($c->req->body);
    my $wait = delete $args->{_wait};

    for (qw(remote_ip uid)) {
        confess "Error: $_ should not be provided".Dumper($args)
        if exists $args->{$_};
    }
    if ($name =~ /^(start_clones|open_exposed_ports)/) {
        $args->{remote_ip} = _remote_ip($c);
    }
    app->log->info($USER->name." requesting $name ".encode_json($args)) if $CONFIG_FRONT->{log}->{log};
    my $req;

    $name = 'start_clones_sequentially' if (($name eq 'start_clones') && ($args->{'exec_sequentially'}));
    delete($args->{exec_sequentially});
    delete($args->{at}) if ((! $args->{at}) || ($args->{at} < time()));

    delete $args->{data}->{_name} if $name eq 'change_hardware';

    if ($name eq 'start_clones_sequentially') {

        my $domain = $RAVADA->search_domain_by_id($args->{'id_domain'}) or do {
            $c->stash( error => "This machine doesn't exist. Probably it has been deleted recently.");
            return;
        };       
        delete($args->{'id_domain'});

        foreach my $clone ($domain->clones()) { 
            $req = Ravada::Request->start_domain(uid => $USER->id
                                           ,name => $clone->{'name'}
                                           ,%$args
            );      
            $args->{'after_request'} = $req->id; 
        }
    } else {
        eval {
            $req = Ravada::Request->new_request(
                $name
                ,uid => $USER->id
                ,%$args
            );
        };
        warn $@ if $@;
    }
    my $error = ( $@ or '');
    my $output = '';

    if ($wait) {
        $RAVADA->wait_request($req, 60);
        $error = $req->error if !$error;
        $output = $req->output if $req->output;
    }
    $RAVADA->_cache_clean();

    return $c->render(json => { ok => 0, error => $@ }) if !$req;
    return $c->render(json => { ok => 1, request => $req->id
                                ,error => $error, output => $output });
};

get '/request/(:id).(:type)' => sub {
    my $c = shift;
    my $id = $c->stash('id');

    if (!$USER) {
        $USER = _get_anonymous_user($c) or access_denied($c);
    }
    if ($c->stash('type') eq 'json') {
        my $request = Ravada::Request->open($id);
        return $c->render(json => $request->info($USER));
    }
    return _show_request($c,$id);
};

get '/anonymous/request/(:id).(:type)' => sub {
    my $c = shift;
    my $id = $c->stash('id');

    $USER = _anonymous_user($c);

    return _show_request($c,$id) if ($USER);
};

get '/requests.json' => sub {
    my $c = shift;
    return access_denied($c) unless _logged_in($c)
        && $USER->is_admin;
    return list_requests($c);
};

get '/messages.json' => sub {
    my $c = shift;


    return $c->render( json => [$USER->messages()] );
};

get '/unshown_messages.json' => sub {
    my $c = shift;

    return $c->redirect_to('/login') if !_logged_in($c);

    return $c->render( json => [$USER->unshown_messages()] );
};


get '/messages/read/all.html' => sub {
    my $c = shift;
    $USER->mark_all_messages_read;
    return $c->render(inline => "1");
};

get '/messages/read/(#id).json' => sub {
    my $c = shift;
    my $id = $c->stash('id');
    $USER->mark_message_read($id);
    return $c->render(inline => "1");
};

get '/messages/unread/(#id).json' => sub {
    my $c = shift;
    my $id = $c->stash('id');
    $USER->mark_message_unread($id);
    return $c->render(inline => "1");
};

get '/messages/view/(#id).html' => sub {
    my $c = shift;
    my $id = $c->stash('id');
    return $c->render( json => $USER->show_message($id) );
};

any '/ng-templates/(#template).html' => sub {
  my $c = shift;
  my $id = $c->stash('template');
  return $c->render(template => 'ng-templates/'.$id);
};

any '/about' => sub {
    my $c = shift;

    $c->render(template => 'main/about');
};


any '/requirements' => sub {
    my $c = shift;

    $c->render(template => 'main/requirements');
};

any '/admin/monitoring' => sub {
    my $c = shift;

    $c->render(template => 'main/monitoring');
};

any '/auto_view/(#value)/' => sub {
    my $c = shift;
    my $value = $c->stash('value');
    if ($value =~ /toggle/i) {
        $value = $c->session('auto_view');
        if ($value) {
            $value = 0;
        } else {
            $value = 1;
        }
    }
    $c->session('auto_view' => $value);
    return $c->render(json => {auto_view => $c->session('auto_view') });
};

get '/auto_view' => sub {
    my $c = shift;
    return $c->render(json => {auto_view => $c->session('auto_view') });
};

get '/machine/hardware/remove/(#id_domain)/(#hardware)/(#index)' => sub {
    my $c = shift;
    my $hardware = $c->stash('hardware');
    my $index = $c->stash('index');
    my $domain_id = $c->stash('id_domain');

    my $domain = Ravada::Front::Domain->open($domain_id);

    return access_denied($c)
        unless $USER->id == $domain->id_owner || $USER->is_admin;

    my $req = Ravada::Request->remove_hardware(uid => $USER->id
        , id_domain => $domain_id
        , name => $hardware
        , index => $index
    );

    $RAVADA->wait_request($req,60);

    return $c->render( json => { ok => "Hardware Modified" });
};

post '/machine/hardware/add' => sub {
    my $c = shift;
    my $arg = decode_json($c->req->body);

    my $domain = Ravada::Front::Domain->open($arg->{id_domain});
    return access_denied($c)
        unless $USER->id == $domain->id_owner || $USER->is_admin;

    my $hardware = delete $arg->{hardware} or die "Missing hardware name";
    my $number = ( $arg->{number} or undef );

    my @fields;
    push @fields, ( number => $arg->{number} )
        if exists $arg->{number} && defined $arg->{number};
    push @fields, ( data => $arg->{data} ) if exists $arg->{data};

    my $req = Ravada::Request->add_hardware(
        uid => $USER->id
        ,name => $hardware
        ,id_domain => $domain->id
        ,@fields
    );
    return $c->render( json => { request => $req->id } );
};

get '/list_users.json' => sub($c) {
    return access_denied($c) if !$USER->is_admin;
    return $c->render(json => $RAVADA->list_users );
};

get '/user/#type/list' => sub($c) {
    return _list_users($c);
};

get '/user/#type/list/#name' => sub($c) {
    return _list_users($c);
};


sub _list_users($c) {
    return access_denied($c) unless $USER->can_manage_users || $USER->can_manage_groups;

    my $name = ($c->stash('name') or '');
    my $type = $c->stash('type');
    if ($type eq 'ldap') {
        $name .= '*' unless $name =~ m{\*$};
        return _list_ldap_users($c,$name);
    } elsif ($type eq 'local') {
        my  $users = $RAVADA->list_users($name);
        return $c->render(json => { entries => $users});
    } else {
        return not_found($c);
    }
};

get '/search_user/#name'=> sub($c) {
    my $name = $c->stash('name');
    return _search_user($c, $name);
};

get '/search_other_user/#name'=> sub($c) {
    my $name = $c->stash('name');
    return _search_user($c, $name,1);
};

get '/search_user/'=> sub($c) {

    return _search_user($c);
};


sub _search_user($c, $name='', $other=0) {

    my $list_found;
    if ($other) {
        $list_found = $RAVADA->list_users_share($name,$USER);
    } else {
        $list_found = $RAVADA->list_users_share($name);
    }
    my $found;
    if (scalar(@$list_found) == 1 ) {
        ($found) = @$list_found ;
    } else {
        ($found) = grep { $_->{name} eq $name } @$list_found;
    }
    $found = $found->{name} if $found;
    return $c->render(json => {found => $found , count => scalar(@$list_found), list => $list_found } );
};

get '/group/#type/list_members/#name' => sub($c) {
    return access_denied($c) if !$USER->can_view_groups;

    my $name = $c->stash('name');
    my $type = $c->stash('type');

    return _list_group_members($c,$type,$name);

};

get '/list_ldap_users' => sub($c) {
    return access_denied($c) unless $USER->can_manage_users || $USER->can_manage_groups;
    return _list_ldap_users($c,'*');
};

get '/list_ldap_users/#name' => sub($c) {
    return access_denied($c) unless $USER->can_manage_users || $USER->can_manage_groups;
    my $name = $c->stash('name');
    $name .= '*' unless $name =~ m{\*$};
    return _list_ldap_users($c,$name);
};



get '/list_ldap_groups/:name' => sub($c) {
    my $name = $c->stash('name');
    return access_denied($c) unless $USER->can_view_groups || $USER->can_manage_groups;

    return _list_ldap_groups($c,$name);
};

get '/list_ldap_group_members/#name' => sub($c) {
    return access_denied($c) if !$USER->can_view_groups;

    my $name = $c->stash('name');

    return _list_ldap_group_members($c,$name);
};

post '/group/#type/add_member' => sub($c) {
    return access_denied($c) if !$USER->can_manage_groups;

    my $arg = decode_json($c->req->body);

    my $login = delete $arg->{name};
    my $id_user = delete $arg->{id_user};
    my $group = delete $arg->{group};
    my $id_group = delete $arg->{id_group};

    return $c->render(json => { error => "Error: unknown args ".Dumper($arg)}) if keys %$arg;

    my $type = $c->stash('type');
    my $error = '';

    my $user;
    if ($id_user) {
        $user = Ravada::Auth::SQL->search_by_id($id_user);
        $login = $user->name if $user;
    }

    if ($type eq 'ldap') {
        die "Error: missing login name" if !$login;
        die "Error: missing group name" if !$group;
        eval {
            Ravada::Auth::LDAP::add_to_group($login, $group);
        };
        $error = $@;
    } elsif ($type eq 'local') {
        my $group = Ravada::Auth::Group->open($id_group);
        if ($user->is_member($group->id)) {
            $error = "Error: user $login already added to $id_group";
        } else {
            $user->add_to_group($group->id);
        }
    } else { $error = "Error: unkonwn group type '$type" }
    $error =~ s/(.*) at lib.*/$1/ if $error;
    return $c->render(json => { error => $error } );

};

post '/group/#type/remove_member' => sub($c) {
    return access_denied($c) if !$USER->can_manage_groups;

    my $arg = decode_json($c->req->body);

    my $type = $c->stash('type');
    my $group = delete $arg->{group};
    my $id_group = delete $arg->{id_group};
    my $user_name = delete $arg->{name};
    my $id_user = delete $arg->{id_user};

    return $c->render(json => { error => "Error: unknown args ".Dumper($arg)}) if keys %$arg;
    if ($id_user && $id_user =~ /^\d+$/) {
        my $user = Ravada::Auth::SQL->search_by_id($id_user);
        $user_name = $user->name;
    }

    my $error = '';
    $error = "Error: unkonwn args ".Dumper($arg) if keys %$arg;
    if ($type =~ /ldap/i) {
        if (!defined $user_name) {
            $error = "Error: missing user name";
        } else {
            eval {
            Ravada::Auth::LDAP::remove_from_group($user_name, $group);
            };
            $error = ( $@ or '');
        }
    } elsif ($type eq 'local') {
        if (!defined $id_user) {
            $error = "Error: missing user id";
        } else {
            my $user = Ravada::Auth::SQL->search_by_id($id_user);
            $id_group = $group if !defined $id_group && $group =~ /^\d+$/;

            $user->remove_from_group($id_group);
        }
    }
    return $c->render(json => { error => $error } );

};


get '/ldap/group/remove/(#group)' => sub($c) {
    return access_denied($c) if !$USER->can_manage_groups;
    eval {   Ravada::Auth::LDAP::remove_group($c->stash('group')) };
    return $c->render( json => { error => ($@ or '') });
};

get '/group/#type/remove/#group' => sub($c) {
    return access_denied($c) if !$USER->can_manage_groups;
    my $error = '';
    my $type = $c->stash('type');

    if ($type =~ /ldap/i) {
        eval {
            Ravada::Auth::LDAP::remove_group($c->stash('group'));
            my $sth = $RAVADA->_dbh->prepare(
                "DELETE FROM group_access WHERE type = 'ldap' "
                ." AND name=?"
            );
            $sth->execute($c->stash('group'));
        };

        $error = $@;
    } elsif ($type eq 'local') {
        my $group = Ravada::Auth::Group->open($c->stash('group'));
        if ($group && $group->id) {
            $group->remove;
        } else {
            $error = "Error: unknown group $group";
        }
    }
    warn $error if $error;
    return $c->render( json => { error => $error });
};

get '/booking' => sub($c) {

    my $sth = $RAVADA->_dbh->prepare("SELECT count(*) FROM groups_local");
    $sth->execute();
    my ($groups_local) = $sth->fetchrow;

    my @groups_ldap;
    $sth = $RAVADA->_dbh->prepare("SELECT count(*) FROM users_group");
    $sth->execute();
    my ($members_found) = $sth->fetchrow;

    if (!$groups_local && $RAVADA->feature('ldap')) {
        @groups_ldap = Ravada::Auth::LDAP::search_group( name => '*' );
        if (!$members_found) {
            for my $group (@groups_ldap) {
                $members_found = $group->get_value('memberUid');
                last if $members_found;
            }
        }
    }

    if (!$groups_local && !@groups_ldap) {
        return $c->render(template => '/ng-templates/error'
        ,message => "Groups are required to set up bookings. No groups found. <a href='/group/new'>Add new entries here.</a>"
    );
    }

    if (!$members_found) {
        return $c->render(template => '/ng-templates/error'
            ,message => "Groups are required to set up bookings. Some groups where found but no members belong to them. <a href='/admin/groups'>Add new entries here.</a>"
        )

    }

    return $c->render(template => '/ng-templates/booking/calendar');
};

get '/v1/bookings/'=> sub($c) {
    my $arg = $c->req->params->to_hash;
    return $c->render(json => { data => [Ravada::Booking::bookings_range(%$arg)] } );
};

get '/v1/bookings/:id'=> sub($c) {
    my $booking= Ravada::Booking->new( id =>  $c->stash('id') );
    return $c->render( json => $booking->{_data} );
};

post '/v1/bookings/' => sub($c) {
    return access_denied_json($c) if !$USER->is_operator();

    my $arg = decode_json($c->req->body);
    delete $arg->{dow};
    delete $arg->{repeat};
    delete $arg->{editable};
    delete $arg->{background_color};
    $arg->{id_owner} = $USER->id;
    my $dow = '';
    for my $curr ( split//,$arg->{day_of_week} ) {
        $dow .= $curr if $curr;
    }
    _push_new_values($arg);
    $arg->{day_of_week} = $dow;
    eval {
        my $booking = Ravada::Booking->new(%$arg);
    };
    my ($error) = split /\n/,$@;
    warn $error if $error;
    $error =~ s/ at .*// if $error;
    return $c->render(json => { ok => !$error, error => ($error  or '')} );
};

get '/v1/booking_entry/:id' => sub($c) {
    my $booking_entry = Ravada::Booking::Entry->new( id => $c->stash('id'));
    $booking_entry->{_data}->{ldap_groups} = [ $booking_entry->ldap_groups ];
    $booking_entry->{_data}->{local_groups} = [ $booking_entry->local_groups ];
    $booking_entry->{_data}->{users} = [ $booking_entry->users ];
    return $c->render( json => $booking_entry->{_data} );
};

post '/v1/booking_entry/:id' => sub($c) {
    my $arg = decode_json($c->req->body);
    my $mode = delete $arg->{mode};
    my $entry = Ravada::Booking::Entry->new( id => $c->stash('id') );
    my $booking = Ravada::Booking->new( id => $entry->_data('id_booking'));

    return _access_denied($c)
    unless $booking->_data('id_owner') == $USER->id
        || $USER->is_operator;

    # we are changing only this entry, so date_end doesn't make sense
    # anyway in the future we may use it to create other entries
    # so it is only removed if null
    delete $arg->{date_end} if !defined $arg->{date_end};
    _push_new_values($arg);

    if (!$mode || $mode eq 'current') {
        $entry->change(%$arg);
    } elsif ( $mode eq 'next' ) {
        $entry->change_next(%$arg);
    } elsif ($mode eq 'next_dow') {
        $entry->change_next_dow(%$arg);
    } elsif ($mode eq 'all') {
        $entry->change_all(%$arg);
    } else {
        return $c->render( json => { error => "Mode '$mode' unknown" } );
    }
    return $c->render( json => { error => '' } );
};

sub _push_new_values($arg) {
    if ($arg->{ldap_group_new}) {
        push @{$arg->{ldap_groups}},($arg->{ldap_group_new});
    }
    delete $arg->{ldap_group_new};
}

del '/v1/booking_entry/:id/:mode' => sub($c) {
    my $entry;
    eval { $entry = Ravada::Booking::Entry->new(id => $c->stash('id')) };

    if ($@ && $@ =~ /not found/i) {
        return $c->reply->not_found;
    }
    die $@ if $@;

    my $booking = Ravada::Booking->new( id => $entry->_data('id_booking'));
    my %get_token = (
        redirect_uri => $c->url_for('connect')->userinfo(undef)->to_abs
        ,authorize_query => { response_type => 'code' }
    );

    return error_access_denied_json($c)
    unless $booking->_data('id_owner') == $USER->id
        || $USER->is_operator;


    my $mode = $c->stash('mode');
    eval {
        if ($mode eq 'current') {
            $entry->remove();
        } elsif ($mode eq 'next') {
            $entry->remove_next();
        } elsif ($mode eq 'next_dow') {
            $entry->remove_next_dow();
        } elsif ($mode eq 'all') {
            $booking->remove();
        } else {
            die "Mode '$mode' unknown";
        }
    };
    my $error = ($@ or '');
    return $c->render(json => { error => $error } );
};
###################################################

## user_settings

any '/user_settings' => sub {
    my $c = shift;
    user_settings($c);
};

sub _languages() {
    my %lang_name=(
        ar => 'Arab'
        ,en => 'English'
        ,ca => 'Catalan'
        ,es => 'Spanish'
        ,eu => 'Euskara'
        ,gl => 'Galician'
        ,fa => 'Persian'
        ,fr => 'French'
        ,de => 'German'
        ,hi => 'Hindi'
        ,id => 'Indonesian'
        ,it => 'Italian'
        ,ja => 'Japanese'
        ,ko => 'Korean'
        ,my => 'Melayu'
        ,nb_NO => 'Norwegian'
        ,pt => 'Portuguese'
        ,tr => 'Turkish'
        ,'cat@valencia' => 'Valencian'
        ,'ru' => 'Russian'
        ,vi => 'Vietnamese'
        ,zh_CN => 'Chinese'
    );
    return \%lang_name;
}

get '/translations' => sub($c) {
    my $lang_name = _languages();
    return $c->render(json => $lang_name);
};

sub _translations($c) {
    my $lang_name = _languages();
    $c->stash(lang_name => $lang_name);
    my $language = $USER->language;
    if (!$language) {
        for my $detected_lang (_detect_languages($c)) {
            if ( exists $lang_name->{$detected_lang}) {
                $language = $detected_lang;
                last;
            }
        }
    }
    $c->stash( language => ( $language or 'en') );

}

sub user_settings {
    my $c = shift;
    my $changed_lang;
    my $changed_pass;
    if ($c->param('language')) {
        $USER->language($c->param('language'));
        $changed_lang = $c->param('language');
        Ravada::Request->post_login(
                user => $USER->name
            , locale => $changed_lang);
        _logged_in($c);
    }
    _translations($c);
    my @errors;
    if ($c->param('password-form') && $c->req->method eq 'POST') {
	my $auth_ok;
        eval { $auth_ok = Ravada::Auth::login($USER->name, $c->param('current_password'))};
	if (!$auth_ok || $@ || ($c->param('current_password') eq "")) {
		push @errors, ("Current password is wrong");
	}
	else {

        	if (($c->param('password') eq "") || ($c->param('conf_password') eq "") || ($c->param('current_password') eq "")) {
	            push @errors,("Some of the password's fields are empty");
	        }
        	else {
	            if ($c->param('password') eq $c->param('conf_password')) {
        	        eval {
                	    $USER->change_password($c->param('password'));
	                    _logged_in($c);
        	        };
	                if ($@ =~ /Password too small/) {
	                    push @errors,("Password too small")
	                }
	                else {
	                    $changed_pass = 1;
	                }
	            }
	            else {
	                    push @errors,("Password fields aren't equal")
	            }
	        }
	    }
    }
    $c->render(template => 'bootstrap/user_settings', changed_lang=> $changed_lang, changed_pass => $changed_pass
      ,errors =>\@errors);
};

get '/img/screenshots/:file' => sub {
    my $c = shift;
    my $file = $c->param('file');
    my $path = $DOCUMENT_ROOT."/".$c->req->url->to_abs->path;
    my ($id_domain) =$path =~ m{/(\d+)\..+$};
    my $domain = $RAVADA->search_domain_by_id($id_domain);

    my $image = new Image::Magick;
    my $sshot = $image->BlobToImage($domain->get_info()->{screenshot});
    if (!$id_domain) {
        warn"ERROR : no id domain in $path";
        return $c->reply->not_found;
    }
    if ($USER && !$USER->is_admin) {
        #my $domain = $RAVADA->search_domain_by_id($id_domain);
        return $c->reply->not_found if !$domain;
        unless ($domain->is_base && $domain->is_public) {
            return access_denied($c) if $USER->id != $domain->id_owner;
        }
    }
    return $c->reply->not_found  if ! -e $path;
    return $c->render_file(
                      filepath => $path
        ,'content_disposition' => 'inline'
    );
};

get '/iso/download/(#id).json' => sub {
    my $c = shift;

    return access_denied($c)    if !$USER->is_admin;
    my $id = $c->stash('id');

    my $req = Ravada::Request->download(
        id_iso => $id
        ,uid => $USER->id
    );

    return $c->render(json => {request => $req->id});
};

get '/balance_options.json' => sub($c) {

    return $c->render(json => [
        {
            id => 0
            ,name => $c->stash->{i18n}->localize('Node with more free memory')
        }
        ,{  id => 1
            ,name => $c->stash->{i18n}->localize('Node with machines from the same user')
        }

    ]);
};

websocket '/ws/unsubscribe_all' => sub {
    my $c = shift;
    $WS->unsubscribe_all();
} => 'ws_unsubscribe';

websocket '/ws/subscribe' => sub {
    my $c = shift;
    my $expiration = $SESSION_TIMEOUT;
    $USER = _logged_in($c) if !$USER;
    return access_denied_user($c) if !$USER;
    $expiration = $SESSION_TIMEOUT_ADMIN    if $USER->is_operator;
    $c->inactivity_timeout( $expiration );
    $c->on(message => sub {
            my ($ws, $channel ) = @_;
            $USER = _logged_in($c) if !$USER;
            if (!$USER) {
                cluck "Warning: USER unknown";
                return;
            }
            return access_denied($c)
              if !_allowed_anonymous_ws($channel) && $USER->is_temporary;

            $WS->subscribe( ws => $ws
                , channel => $channel
                , login => $USER->name
                , remote_ip => _remote_ip($c)
                , client => _headers($c)
            );
    });

    $c->on(finish => sub { my $ws = shift; $WS->unsubscribe($ws) });
} => 'ws_subscribe';

sub _allowed_anonymous_ws($channel) {
    return 1 if $channel =~ m{^(machine_info|request)/} || $ALLOWED_ANONYMOUS_WS{$channel};
    return 0;
}

sub _headers($c) {
    my %client;
    for my $name (@{$c->req->headers->names}) {
        $client{$name}= $c->req->headers->header($name);
    }
    return \%client
}

get '/booking/(#template).html' => sub($c) {
    return $c->render( template => '/booking/'.$c->stash('template'));
};

get '/storage/list_pools/(:id_vm)' => sub($c) {
    my $query = $c->req->query_params;
    my $pools = $RAVADA->list_storage_pools($USER->id, $c->stash('id_vm'));
    return $c->render( json => $pools );
};

get '/storage/list_unused_volumes' => sub($c) {
    my $query = $c->req->query_params;
    my $req = Ravada::Request->list_unused_volumes(
        uid => $USER->id
        ,start => $query->param('start')
        ,limit => $query->param('limit')
        ,id_vm => $query->param('id_vm')
    );
    $RAVADA->wait_request($req,180);
    my $output = [];
    if ($req->output) {
        $output = decode_json($req->output);
    }
    return $c->render( json => $output );
};

any '/storage/new/(:id_vm)' => sub($c) {
    _add_admin_libs($c);

    return $c->render(template => "/main/storage_new");
};

###################################################
#
# session settings
#
get '/session/(#tag)/(#value)' => sub {
    my $c = shift;
    my %allowed = map { $_ => 1 } qw(monitoring);

    my $tag = $c->stash('tag');
    my $value = $c->stash('value');

    return $c->render( json => { error => "Session $tag not allowed" }) if !$allowed{$tag};

    $c->session($tag => $value);
    return $c->render( json => { ok => "Session $tag set to $value " });
};

###################################################
#
# host devices
#
#
get '/host_devices/templates/list/(#id_vm)' => sub($c) {

    my $id_vm = $c->stash('id_vm');
    my $templates = Ravada::HostDevice::Templates::list_templates($id_vm);
    return $c->render( json => $templates);

};

sub _request_recent() {
    my @now = localtime(time);
    $now[4]++;
    for ( 1 .. 4 ){
        $now[$_] = "0".$now[$_] if length ($now[$_])<2
    }
    my $now = "".($now[5]+1900)."-$now[4]-$now[3] $now[2]:$now[1]";
    $now[1]--;
    my $now2 = "".($now[5]+1900)."-$now[4]-$now[3] $now[2]:$now[1]";
    my $sth = $RAVADA->_dbh->prepare(
        "SELECT date_changed,status,command FROM requests ORDER BY date_changed DESC LIMIT 10"
    );
    $sth->execute();
    my $n = 100;
    while (my ($date_changed, $status, $command) = $sth->fetchrow ) {
        next if $status !~ /working|done/;
        return 1 if $date_changed =~ /^($now|$now2)/;
        last if $n--<0;
    }
    return 0;
}

sub _ping_backend() {
    return 1 if _request_recent();

    my $req = Ravada::Request->ping_backend();
    $RAVADA->wait_request($req, 10);
    if ($req->status eq 'done' && !$req->error) {
        return 1;
    }
    return 0;
}

get '/status.(#type)'=> sub($c) {
    my $remote_ip = _remote_ip($c);

    my %allowed = ('127.0.0.1' => 1);
    if (exists $CONFIG_FRONT->{status} && $CONFIG_FRONT->{status}->{allowed}) {
        if (ref($CONFIG_FRONT->{status}->{allowed}) eq 'ARRAY') {
            for my $ip ( @{$CONFIG_FRONT->{status}->{allowed}} ) {
                warn $ip;
                $allowed{$ip}++;
            }
        } else {
            $allowed{$CONFIG_FRONT->{status}->{allowed}}++;
        }
    }
    return access_denied($c) unless $allowed{$remote_ip};
    my $backend = _ping_backend();
    if ($backend) {
        $backend='true';
    } else {
        $backend='false';
    }
    my $status = { backend => $backend, frontend => 'true' };
    my $sth = $RAVADA->_dbh->prepare("SELECT name,is_active "
        ." FROM vms "
        ." ORDER BY 'name'"
    );
    $sth->execute;

    my $sth_active = $RAVADA->_dbh->prepare("SELECT count(*) "
        ." FROM domains "
        ." WHERE status='active' AND id_vm=? "
    );
    while ( my $row = $sth->fetchrow_hashref) {
        $sth_active->execute($row->{id});
        $row->{vms}=$sth_active->fetchrow;
        if ($row->{is_active}) {
            $row->{status} = 'active';
        } else {
            $row->{status} = 'disabled';
        }
        delete $row->{is_active};
        push@{$status->{nodes}},($row);
    }
    return $c->render(json => $status);
};

###################################################

sub _init_error {
    my $c = shift;
    $c->stash(error_title => '');
    $c->stash(error => []);
    $c->stash(link => '');
    $c->stash(link_msg => '');

}

sub _logged_in {
    my $c = shift;

    confess "missing \$c" if !defined $c;
    $USER = undef;

    _init_error($c);
    $c->stash(_logged_in => undef , _user => undef, _anonymous => 1);
    my $login = $c->session('login');
    if ($login) {
        $USER = Ravada::Auth::SQL->new(name => $login);
        #Mojolicious::Plugin::I18N::
        $c->languages($USER->language) if $USER->language();

        $c->stash(_logged_in => $login );
        $c->stash(_user => $USER);
        $c->stash(_anonymous => !$USER);
    }
    $c->stash(url => undef);

    return $USER;
}

sub _detect_languages($c) {
    my @languages = I18N::LangTags::implicate_supers(
        I18N::LangTags::Detect::detect()
    );
    my $header = ( $c->req->headers->header('accept-language') or '');
    my @languages2 = map {my $lang = $_ ; $lang =~ s/^(.*?)[;-].*/$1/; $lang } split /,/,$header;
    my @languages_browser = map {my $lang = $_ ;$lang =~ s/;.*//; $lang } split /,/,$header;

    return (@languages_browser, @languages, @languages2);


}

sub login($c, $status=200) {
    $c->session(login => undef);

    my $login = $c->param('login');
    my $password = $c->param('password');
    my $ticket = $c->param('ticket');

    my @error =();

    if (((defined $login) || (defined $password) || ((defined $c->param('submit')) && ($c->param('submit') ne 'sso'))) && (! $ticket)) {
        push @error,("Empty login name")  if !length $login;
        push @error,("Empty password")  if !length $password;
    }
    my $auth_ok;
    if (!@error) {
        if (defined $login && defined $password) {
            eval { $auth_ok = Ravada::Auth::login($login, $password)};
        } elsif (($c->param('ticket')) || ($c->param('submit') && $c->param('submit') eq 'sso')) {
            eval { $auth_ok = Ravada::Auth::login_external($ticket, $c->session('ticket')) };
            if ($auth_ok && !$@) {
                return $c->redirect_to($auth_ok->{'redirectTo'}) if ($auth_ok->{'redirectTo'});
                $c->session('ticket' => $auth_ok->{'ticket'}) if ($auth_ok->{'ticket'});
                $c->session('logoutURL' => $auth_ok->{'logoutURL'}) if ($auth_ok->{'logoutURL'});
                $login = $auth_ok->name;
            }
        }

        if ( $auth_ok && !$@) {
            _login_ok($c, $auth_ok);
            return;
        } elsif (defined $c->param('submit') || $login || $password) {
            app->log->error("Access denied to $login from "._remote_ip($c)) if $CONFIG_FRONT->{log}->{log};
            push @error,("Access denied");
        }
    }


    sleep 5 if scalar(@error) && !$ENV{mode} && !$ENV{mode} eq 'development';
    $status = 403 if @error;

    $c->stash('login' => $login);
    _render_login($c, \@error, $status);
}

sub _render_login($c, $error, $status=undef) {

    $error = [$error] if !ref($error);
    $status = 403 if @$error && !$status;

    my @status;
    @status = ( status => $status ) if $status;

    my $css_snippets = ["\t.intro {\n\t\tbackground:"
                    ." url($CONFIG_FRONT->{login_bg_file})"
                    ." no-repeat bottom center scroll;\n\t}"];

    $c->session(url => $c->req->url->to_abs->path);
    $c->render(
                    template => ($CONFIG_FRONT->{login_custom} or 'main/start')
                        ,css => ['/css/main.css']
                        ,csssnippets => $css_snippets
                        ,js => [
                            '/js/main.js?v='.$RAVADA->version
                            ,'/js/ravada.js?v='.$RAVADA->version
                        ]
                        ,navbar_custom => 1
                      ,error => $error
                      ,sso_available => ( $Ravada::Auth::SSO_OK && Ravada::Auth::SSO::init())
                      ,openid_available => $RAVADA->setting('/frontend/openid/enabled')
                      ,login_header => $CONFIG_FRONT->{login_header}
                      ,login_message => $CONFIG_FRONT->{login_message}
                      ,guide => $CONFIG_FRONT->{guide}
                      ,login_hash => ''
                      ,@status
    );
}

sub _login_ok($c, $auth_ok) {

    my $login = $auth_ok->name;

    my $url = ($c->param('url') or $c->session('url') or $c->req->url->to_abs->path);
    $url = '/' if $url =~ m{^/login};

    $c->session('login' => $login);
    my $expiration = $SESSION_TIMEOUT;
    $expiration = $SESSION_TIMEOUT_ADMIN    if $auth_ok->is_admin;
    Ravada::Request->post_login(
        user => $auth_ok->name
        , locale => [_detect_languages($c)]
    );

    $auth_ok = Ravada::Auth::SQL->new(name => $auth_ok->name);
    if ( $RAVADA->is_in_maintenance() ) {
        return maintenance($c) unless $auth_ok->is_operator;
        $auth_ok->send_message('Warning: Server under maintenance. <a href="/admin/settings">Settings</a>');
    }

    my $machines = $RAVADA->list_machines_user($auth_ok);

    $url = "/machine/clone/". $machines->[0]->{id}.".html" if scalar(@$machines) == 1 && !($auth_ok->is_admin);
    my $auto_view = 1;

    $c->session(auto_view => $auto_view, expiration => $expiration);
    app->log->info("Access granted to $login from "._remote_ip($c)) if $CONFIG_FRONT->{log}->{log};

    return $c->redirect_to($url);
}

sub logout {
    my $c = shift;

    $WS->unsubscribe_all();

    $USER = undef;
    $c->session(expires => 1);
    $c->session(login => undef);
    sleep 1;
    $c->session(expires => 1);
    $c->session(login => undef);
    $c->session(ticket => undef);

    my $logout_url = $c->session('logoutURL');
    $c->session(logoutURL => undef);

    my $c_name= 'mod_auth_openidc_session';
    my $cookie_oidc = $c->req->headers->cookie($c_name);

    $c->cookie($c_name => '', {expires => 1}) if $cookie_oidc;

    return $logout_url;
}

sub quick_start {
    my $c = shift;

    _logged_in($c);

    my $login = $c->param('login');
    my $password = $c->param('password');
    my $ticket = $c->param('ticket');
    my $id_base = $c->param('id_base');

    my @error =();
    if ($c->param('submit') && $c->param('submit') ne 'sso' && !$ticket) {
        push @error,("Empty login name")  if !length $login;
        push @error,("Empty password")  if !length $password;
    }
    if (defined $ticket) {
        push @error, ("Empty ticket value") if !length $ticket;
    }

    if (!@error) {
        my $log_ok;
        if ( $login && $password) {
            eval { $log_ok = Ravada::Auth::login($login, $password) };
        } elsif (($c->param('ticket')) || ($c->param('submit') && $c->param('submit') eq 'sso')) {
            eval { $log_ok = Ravada::Auth::login_external($ticket, $c->session('ticket')) };
            if ($log_ok && !$@) {
                return $c->redirect_to($log_ok->{'redirectTo'}) if ($log_ok->{'redirectTo'});
                $c->session('ticket' => $log_ok->{'ticket'}) if ($log_ok->{'ticket'});
                $c->session('logoutURL' => $log_ok->{'logoutURL'}) if ($log_ok->{'logoutURL'});
                $login = $log_ok->name;
            }
        }
        if ($log_ok && !$@) {
            $c->session('login' => $login);
        }
        else {
            push @error,($@ ? $@ : "Access denied");
        }
    }

    if ( $c->param('submit') && _logged_in($c) && defined $id_base ) {

        return quick_start_domain($c, $id_base, ($login or $c->session('login')));

    }

    return render_machines_user($c);

}

sub render_machines_user {
    my $c = shift;
    my $anonymous = (shift or 0);

    if ($CONFIG_FRONT->{guide_custom}) {
        push @{$c->stash->{js}}, $CONFIG_FRONT->{guide_custom};
    } else {
        push @{$c->stash->{js}}, '/js/ravada_guide.js';
    }
    return $c->render(
        template => 'main/list_bases_ng'
        ,user => $USER
        ,_anonymous => $anonymous
    );
}

sub quick_start_domain {
    my ($c, $id_base, $name) = @_;
    my $anonymous = (shift or 0);

    return $c->redirect_to('/login') if !$USER;

    confess "Missing id_base" if !defined $id_base;
    $name = $USER->name    if !$name;

    my $base = $RAVADA->search_domain_by_id($id_base) or die "I can't find base $id_base";

    my $domain_name = $base->name."-".$name;
    $domain_name =~ tr/[\.]/[\-]/;

    my $domain = $RAVADA->search_clone(id_base => $base->id, id_owner => $USER->id);
    $domain_name = $domain->name if $domain;

    return run_request($c,provision_req($c, $id_base, $domain_name), $anonymous);

}

sub show_failure {
    my $c = shift;
    my $name = shift;
    $c->render(template => 'main/fail', name => $name);
}


#######################################################

sub admin {
    my $c = shift;
    my $page = $c->stash('type');
    my @error = ();

    _add_admin_libs($c);

    if ($page =~ m/group/) {
        return access_denied($c)    unless $USER->is_admin || $USER->can_manage_users
            || $USER->can_view_groups || $USER->can_manage_groups;
    }
    if ($page eq 'users') {
        return access_denied($c)    if !$USER->is_admin && !$USER->can_manage_users && !$USER->can_grant;
        $c->stash(list_users => []);
        $c->stash(name => $c->param('name' or ''));
        $c->stash(message => '');
        if ( $c->param('name') ) {
            $c->stash(list_users => $RAVADA->list_users($c->param('name') ))
        } else {
            my $users = $RAVADA->list_users();
            if  (($users) && (scalar(@$users) < $LIMIT_SHOW_USERS)) {
                $c->stash(list_users => $users);
            } else {
                $c->stash(message => "There are ".scalar(@$users)." users. Type a search pattern to list them");
            }
        }
    }
    if ($page eq 'machines') {

        return access_denied($c) if !$USER->can_view_admin_machines;

        _count_nodes($c);
        Ravada::Request->refresh_vms();
        $c->stash(n_clones_hide => ($CONFIG_FRONT->{admin}->{hide_clones} or 10) );
        $c->stash(autostart => ( $CONFIG_FRONT->{admin}->{autostart} or 0));

        $c->stash(USER => $USER);
        if ($USER && $USER->is_admin && $CONFIG_FRONT->{monitoring}) {
            if (!defined $c->session('monitoring')) {
                my $host = $c->req->url->to_abs->host;
                $c->stash(check_netdata => "https://$host:19999/index.html");
            }
            $c->stash( monitoring => 1 ) if $c->session('monitoring');
        }
    }
    if ($page eq 'nodes') {
        return access_denied($c) unless $USER->is_admin;
        Ravada::Request->refresh_vms();
    }
    $c->session(expiration => $SESSION_TIMEOUT_ADMIN2);
    if ($page eq 'settings') {
        return access_denied($c) unless $USER->is_admin;
        my $url = $c->req->url->to_abs->path;
        my $host = $c->req->url->to_abs->host;
        my $csp = $RAVADA->_settings_by_parent("/frontend/content_security_policy");
        my $csp_advanced = 0;
        $csp_advanced = grep $csp->{$_}, grep /-/,keys %$csp;

        $c->stash( csp => $csp , csp_advanced => $csp_advanced);
        $c->stash(url_login => "/login");
    }
    if ($page eq 'storage') {
        _select_vm($c);
    }
    return _admin_host_devices($c) if ($page eq 'hostdev');
    $c->render( template => 'main/admin_'.$page);
};

sub _select_vm($c , $type =undef) {
    my $sth = $RAVADA->_dbh->prepare("SELECT id,name FROM vms WHERE hostname='localhost' AND vm_type=?");
    my $types = Ravada::Front::list_vm_types();
    for my $type ('KVM',@$types) {
        $sth->execute($type);
        my ($id, $name) = $sth->fetchrow;
        next if !$id;
        $c->stash('id_vm' => $id, vm_name => $name);
        return;
    }
    $sth = $RAVADA->_dbh->prepare("SELECT id,name FROM vms WHERE vm_type=?");
    for my $type ('KVM',@$types) {
        $sth->execute($type);
        my ($id, $name) = $sth->fetchrow;
        next if !$id;
        $c->stash('id_vm' => $id, vm_name => $name);
        return;
    }
    die "Error: No VM found in @$types";
}

sub _count_nodes($c) {
    my $sth = $RAVADA->_dbh->prepare(
        "SELECT count(*) FROM vms"
    );
    $sth->execute();
    my ($n) = $sth->fetchrow;
    $c->stash( _n_nodes => $n );
}

sub _search_id_iso($name) {
    my $sth = $RAVADA->_dbh
        ->prepare("SELECT id FROM iso_images WHERE name=?");
    $sth->execute($name);
    my ($id) = $sth->fetchrow;
    return $id;
}

sub new_machine {
    my $c = shift;
    my @error ;
    if ($c->param('submit')) {
        if (!$c->param('id_iso_id') && $c->param('id_iso')) {
            $c->param('id_iso_id', _search_id_iso($c->param('id_iso')));
        }
        $c->param('id_iso', $c->param('id_iso_id')) if $c->param('id_iso_id');
        push @error,("Name is mandatory")   if !$c->param('name');
        push @error,("Invalid name '".$c->param('name')."'"
                .".It can only contain alphabetic, numbers, undercores and dashes.")
            if $c->param('name') && $c->param('name') !~ /^[а-яА-Яa-zA-Z0-9àèìòùáéíóúäëïöüçñ€_\$\-\.]+$/i;
        if (!@error) {
            req_new_domain($c);
            $c->redirect_to("/admin/machines");
        }
    } else {
        my $req = Ravada::Request->refresh_storage();
        $RAVADA->_cache_delete('list_isos');
        # TODO handle possible errors
    }
    $c->stash(errors => \@error);
    _add_admin_libs($c);
    my %valid_vm = map { $_ => 1 } @{$RAVADA->list_vm_types};
    $c->render(template => 'main/new_machine'
        , name => $c->param('name')
        , valid_vm => \%valid_vm
    );
};

sub new_node {
    my $c = shift;

    _add_admin_libs($c);

    if ($c->param('_submit')) {
        $c->req->params->remove('_submit');
        my $pairs = $c->req->params()->pairs;
        $RAVADA->add_node(@$pairs);
	return $c->render(template => 'main/admin_nodes');
    }
    return $c->render(template => 'main/new_node');
}

sub req_new_domain {
    my $c = shift;
    my $name = $c->param('name');
    my $swap = ($c->param('swap') or 0);
    my $vm = ( $c->param('backend') or 'KVM');
    $swap = int($swap * 1024*1024*1024);
    my $bios = $c->param('bios');
    my $hardware = $c->param('hardware');
    my $machine = ($c->param('machine') or '');
    $machine =~ s/^string://;
    $machine = '' if $machine eq '?';
    my $start = $c->param('start');

    my $data = ($c->param('data') or 0);
    $data *= 1024*1024*1024;

    if (!$c->param('_advanced_options')) {
        $swap = int(1024 * 1024 * 1024) if !$swap;
        $data = int(1024 * 1024 * 1024) if !$data;
        $start = 1 if !defined $start;
    }

    my %args = (
           name => $name
        ,id_iso => $c->param('id_iso')
        ,id_template => $c->param('id_template')
        ,vm=> $vm
        ,id_owner => $USER->id
        ,swap => $swap
        ,data => $data
        ,start => $start
        ,remote_ip => _remote_ip($c)
    );
    $args{iso_file} = $c->param('iso_file') if defined $c->param('iso_file');
    $args{options}->{uefi} = 1 if $bios && $bios eq 'UEFI';
    $args{options}->{machine} = $machine if $machine;
    $args{memory} = int($c->param('memory')*1024*1024)  if $c->param('memory');
    $args{disk} = int($c->param('disk')*1024*1024*1024) if $c->param('disk');
    $args{id_template} = $c->param('id_template')   if $vm =~ /^LX/;
    $args{id_iso} = $c->param('id_iso')             if $vm eq 'KVM';
    $args{storage} = $c->param('storage');

    return $RAVADA->create_domain(%args);
}

sub _show_request {
    my $c = shift;
    my $id_request = shift;

    my $request;
    if (!ref $id_request) {
        eval { $request = Ravada::Request->open($id_request) };
        return $c->render(data => "Request $id_request unknown")   if !$request;
    } else {
        $request = $id_request;
    }

    return access_denied($c)
        unless $USER->is_admin || $request->{args}->{uid} == $USER->id;

    return $c->render(data => "Request $id_request unknown ".Dumper($request))
        if !$request->{id};

#    $c->stash(url => undef, _anonymous => undef );
    $c->render(
         template => 'main/request'
        , request => $request
    );
    return if $request->status ne 'done';

    return $c->render(data => "Request $id_request error ".$request->error)
        if $request->error
            &&  !($request->command eq 'start' && $request->error =~ /already running/);

    my $name = $request->defined_arg('name');
    return if !$name;

    my $domain = $RAVADA->search_domain($name);

    if (!$domain) {
        return $c->render(data => "Request ".$request->status." , but I can't find domain $name");
    }
    $c->stash('id_base' => $domain->_data('id_base'));
    return view_machine($c,$domain);
}

sub _search_req_base_error {
    my $name = shift;
}

sub error_access_denied_json(@args) {
    return access_denied_json(@args);
}

sub access_denied_json($c, $msg='Access denied') {
    $c->stash( type => 'json');
    return access_denied($c, $msg);
}

sub not_found($c,$code=404, $msg="Not found") {
    my $agent = $c->req->headers->user_agent;
    if ($agent =~ /Mojolicious/) {
        return $c->render( text => $msg , status => $code );
    }
    if (defined $c->stash('type') && $c->stash('type') eq 'json') {
        return $c->render(json => { error => $msg }, status => $code);
    }
    my @css_snippets = ["\t.intro {\n\t\tbackground:"
                    ." url($CONFIG_FRONT->{login_bg_file})"
                    ." no-repeat bottom center scroll;\n\t}"];

    return $c->render(
                    template => ($CONFIG_FRONT->{not_found} or '/main/not_found')
                    ,css => ['/css/main.css']
                    ,csssnippets => @css_snippets
                    ,js => ['/js/main.js?v='.$RAVADA->version]
                    ,navbar_custom => 1
                    ,error => $msg
                    ,can_login => ((! $USER) || ($USER->is_temporary))
                    ,guide => $CONFIG_FRONT->{guide}
                    ,status => $code
    );

}

sub access_denied {
    my $c = shift;
    my $msg = shift;

    if (!$msg) {
        $msg = 'Access denied to '.$c->req->url->to_abs->path;
        $msg .= ' for user '.$USER->name if $USER && !$USER->is_temporary;
    }

    if (defined $c->stash('type') && $c->stash('type') eq 'json') {
        return $c->render(json => { error => $msg }, status => 403);
    }

    my $agent = $c->req->headers->user_agent;
    if ($agent =~ /Mojolicious/) {
        return $c->render(text => $msg, status => 403);
    }
    my @css_snippets = ["\t.intro {\n\t\tbackground:"
                    ." url($CONFIG_FRONT->{login_bg_file})"
                    ." no-repeat bottom center scroll;\n\t}"];

    return $c->render(
                    template => ($CONFIG_FRONT->{access_denied} or '/main/access_denied')
                    ,css => ['/css/main.css']
                    ,csssnippets => @css_snippets
                    ,js => ['/js/main.js?v='.$RAVADA->version]
                    ,navbar_custom => 1
                    ,error => $msg
                    ,can_login => ((! $USER) || ($USER->is_temporary))
                    ,guide => $CONFIG_FRONT->{guide}
                    ,status => 403
    );
}

sub _access_denied { return access_denied(@_) }

sub json_error($c, $error) {
    $USER->send_message($error);
    $c->render(json => {error => $error } );
}

sub base_id {
    my $name = shift;
    my $base = $RAVADA->search_domain($name);

    return $base->id;
}

sub provision_req($c, $id_base, $name, $ram=0, $disk=0) {

    my $enable_host_devices = $c->req->param('enable_host_devices');
    $enable_host_devices=1 if !defined $enable_host_devices;
    my @enable_host_devices;
    @enable_host_devices = ( 'enable_host_devices' => $enable_host_devices );

    my @after_req;
    if ( $RAVADA->domain_exists($name) ) {
        my $domain = $RAVADA->search_domain($name);
        if ( $domain->id_owner == $USER->id
                && $domain->id_base == $id_base && !$domain->is_base ) {
            if ($domain->is_active) {
                Ravada::Request->open_iptables(
                    uid => $USER->id
                , id_domain => $domain->id
                , remote_ip => _remote_ip($c)
                );

                 Ravada::Request->open_exposed_ports(
                    uid => $USER->id
                , id_domain => $domain->id
                , retry => 20
                , remote_ip => _remote_ip($c)
                ) if $domain->list_ports();
            }

            app->log->info($USER->name." start_domain ".$domain->name." from "._remote_ip($c))
            if $CONFIG_FRONT->{log}->{log};

            if ($domain->is_volatile && !$domain->is_active) {
                my $req_remove = Ravada::Request->remove_domain(
                    uid => Ravada::Utils::user_daemon->id
                    ,name => $name
                );
                @after_req = ( after_request => $req_remove );
            } else {
                return Ravada::Request->start_domain(
                uid => $USER->id
                , id_domain => $domain->id
                , remote_ip => _remote_ip($c)
                , @enable_host_devices
                )
            }
        }
    }

    app->log->info($USER->name." requesting create_domain ".$name." from "._remote_ip($c))
    if $CONFIG_FRONT->{log}->{log};

    my @create_args = ( start => 1, remote_ip => _remote_ip($c));
    push @create_args, ( memory => $ram ) if $ram;
    push @create_args, (   disk => $disk) if $disk;

    my $req = Ravada::Request->clone(
        id_domain => $id_base
        ,uid => $USER->id
       ,@create_args
       ,@after_req
       , @enable_host_devices
    );
    return $req;

}

sub run_request($c, $request, $anonymous = 0) {
    my $timeout = $SESSION_TIMEOUT;
    $timeout = $SESSION_TIMEOUT_ADMIN    if $USER->is_admin;
    return $c->render(template => 'main/run_request', request => $request
        , auto_view => ( $CONFIG_FRONT->{auto_view} or $c->session('auto_view') or 0)
        , anonymous => $anonymous
        , timeout => $timeout * 1000
    );
}

sub _open_iptables {
    my ($c, $domain) = @_;
    my $req = Ravada::Request->open_iptables(
               uid => $USER->id
        ,id_domain => $domain->id
        ,remote_ip => _remote_ip($c)
    );
    $RAVADA->wait_request($req);
    return $c->render(data => 'ERROR opening domain for '._remote_ip($c)." ".$req->error)
            if $req->error;

}

sub _init_user_group {
    return if $>;

    my ($run_dir) = $CONFIG_FRONT->{hypnotoad}->{pid_file} =~ m{(.*)/.*};
    mkdir $run_dir if ! -e $run_dir;

    my $user = $CONFIG_FRONT->{user};
    my $group = $CONFIG_FRONT->{group};

    if (defined $group) {
        my $gname=$group;
        $group = getgrnam($gname) or die "CRITICAL: I can't find user $gname\n"
            if $gname!~ /^\d+$/;
    }
    if (defined $user) {
        $user = getpwnam($user) or die "CRITICAL: I can't find user $user\n"
            if $user !~ /^\d+$/;

    }
    chown $user,$group,$run_dir or die "$! chown $user,$group,$run_dir"
        if defined $user;

    if (defined $group) {
        $) = $group;
    }
    if (defined $user) {
        $> = $user;
    }

}

sub init {

    app->log( Mojo::Log->new( path => $CONFIG_FRONT->{log}->{file}, level => $CONFIG_FRONT->{log}->{level} ) )
            if $CONFIG_FRONT->{log}->{log};
    _init_user_group();
    my $home = Mojo::Home->new();
    $home->detect();

    if (exists $ENV{MORBO_VERBOSE}
        || (exists $ENV{MOJO_MODE} && defined $ENV{MOJO_MODE}
                && $ENV{MOJO_MODE} =~ /devel/i )) {
            app->static->paths->[0] = $home->rel_file("public");
            app->renderer->paths->[0] = $home->rel_file("templates");
            app->renderer->paths->[1] = $home->rel_file("custom");
            return;
    }
    app->static->paths->[0] = ($CONFIG_FRONT->{dir}->{public}
            or $home->rel_file("public"));
    app->renderer->paths->[0] =($CONFIG_FRONT->{dir}->{templates}
            or $home->rel_file("templates"));
    app->renderer->paths->[1] =($CONFIG_FRONT->{dir}->{custom}
            or $home->rel_file("templates"));

}

sub _search_requested_machine {
    my $c = shift;
    confess "Missing \$c" if !defined $c;

    my $id = $c->stash('id');
    my $type = $c->stash('type');

    return show_failure($c,"I can't find id in ".$c->req->url->to_abs->path)
        if !$id;

    my $domain = $RAVADA->search_domain_by_id($id) or do {
        #$c->stash( error => "Unknown domain id=$id");
        $c->stash( error => "This machine doesn't exist. Probably it has been deleted recently.");
        return;
    };

    return ($domain,$type) if wantarray;
    return $domain;
}

sub new_group($c) {
    my @error = ();
    my $arg = decode_json($c->req->body);

    my $type = delete $arg->{'type'};
    my $groupname = delete $arg->{'group_name'};
    my $h_object_class = delete $arg->{'object_class'};
    if(!keys %$h_object_class) {
         $h_object_class->{'posixGroup'}=1;
    }
    warn "Warning: extra arguments ignored ".Dumper($arg)
    if keys %$arg;

    my $object_class = ['top',keys %$h_object_class];

    if ($groupname) {
        if ($groupname !~ /\s/) {
            if ($type eq 'ldap') {
                eval {
                Ravada::Auth::LDAP::add_group($groupname,undef, $object_class);
                };
                push @error,($@) if $@;
            } else {
                Ravada::Auth::Group::add_group(name => $groupname);
            }
        } else {
            push @error,("Error: group name '$groupname' invalid."
            ,"Group name can not contain spaces");
        }
    } else {
        push @error,("Empty group name");
    }
    return $c->render(json => { error => \@error });
}

sub register {

    my $c = shift;

    my @error = ();

    my $username = $c->param('username');
    my $password = $c->param('password');

   if($username) {
       my @list_users = Ravada::Auth::SQL::list_all_users();

       if (grep {lc($_) eq lc($username)} @list_users) {
           push @error,("Username already exists, please choose another one");
           return $c->render(template => 'bootstrap/new_user',error => \@error);
       }
       else {
           #username don't exists
           Ravada::Auth::SQL::add_user(name => $username, password => $password);
           return $c->render(template => 'bootstrap/new_user_ok' , username => $username);
       }
   }
   $c->render(template => 'bootstrap/new_user');
}

sub change_password {
    my $c = shift;

    my ($forcing_change_password) = @_;

    return $c->render(text => "User is anonymous")
        if (! _logged_in($c));

    return $c->render(text => "User is temporary")
        if $USER->is_temporary;

    return $c->render(text => "User is external")
        if $USER->is_external;

    my $old_password = $c->param('old_password');

    if ($old_password) {
        return $c->render(template => 'bootstrap/change_password', error => [ "Old password do not match!" ]) if (! $USER->compare_password($old_password));

        my $new_password = $c->param('new_password');
        return $c->render(template => 'bootstrap/change_password', error => [ "New password length is less than 6!" ]) if (length($new_password) < 6);

        my $repeated_new_password = $c->param('repeated_new_password');
        return $c->render(template => 'bootstrap/change_password', error => [ "New password and their repeat do not match!" ]) if ($new_password ne $repeated_new_password);

        $USER->change_password($c->param('new_password'), 0);

        $c->redirect_to('/');
    }

    $c->render(template => 'bootstrap/change_password', forcing_change_password => $forcing_change_password);
}

sub manage_machine {
    my $c = shift;
    my ($domain) = _search_requested_machine($c);
    return access_denied($c)    if !$domain;
  	return access_denied($c)    if !($USER->can_manage_machine($domain->id)
                                    || $USER->is_admin
    );

    $c->stash(domain => $domain);
    $c->stash(USER => $USER);
    $c->stash(ldap_attributes_cn => ( $c->session('ldap_attributes_cn') or $USER->name or ''));
    my ($active_actions, $active_description, $show_actions, $show_description)
    =('','','','');
    if (!$domain->is_base) {
        $active_actions = "active";
        $show_actions = "active show";
    } else {
        $active_description = "active";
        $show_description = "active show";
    }
    $c->stash(active_actions => $active_actions , show_actions => $show_actions);
    $c->stash(active_description => $active_description , show_description => $show_description);

    my @messages;
    my @errors;
    my @reqs = ();

    my %cur_driver;
    for my $driver (qw(image jpeg zlib playback streaming)) {
        next if !$domain->drivers($driver);
        $cur_driver{$driver} = $domain->get_driver_id($driver);
        my $value = $c->param("driver_$driver");
        next if !defined $value
                || !$value
                || (defined $domain->get_driver_id($driver)
                    && $value eq $domain->get_driver_id($driver));
            my $req2 = Ravada::Request->set_driver(uid => $USER->id
                , id_domain => $domain->id
                , id_option => $value
            );
            $cur_driver{$driver} = $value;
            my $msg = "Driver changed: $driver.";
            $msg .= " Changes will apply on next start."    if $domain->is_active;
            push @messages,($msg);
            push @reqs,($req2);

    }
    $c->stash(cur_driver => \%cur_driver);

    for my $option (qw(description)) {

        next if $option eq 'description' && !$c->param('btn_description');

            my $old_value = $domain->_data($option);
            my $value = $c->param($option);

            next if defined $domain->_data($option) && defined $value
                    && $domain->_data($option) eq $value;
            next if !$domain->_data($option) && !$value;

            $domain->set_option($option, $value);
            my $option_txt = $option;
            $option_txt =~ s/_/ /g;
            push @messages,("\u$option_txt changed.");
    }

    $c->stash(messages => \@messages);
    $c->stash(errors => \@errors);
    my $displays = $domain->info($USER)->{hardware}->{display};
    my $has_graphics_spice = grep { $_->{driver} eq 'spice' } @$displays;
    $c->stash(has_graphics_spice => $has_graphics_spice);

    $c->stash( auto_compact => $RAVADA->settings_global()->{'backend'}->{'auto_compact'}->{'value'} );
    return $c->render(template => 'main/settings_machine'
        , nodes => [$RAVADA->list_vms($domain->type)]
        , FEATURE => $RAVADA->feature()
        , list_clones => [map { $_->{name} } grep { !$_->{is_base} } $domain->clones]
        , list_clones_base => [ map { $_->{name } } grep { $_->{is_base} } $domain->clones]
        , action => $c->req->url->to_abs->path
        , headers => $c->req->headers
    );
}

sub _enable_buttons {
    my $c = shift;
    my $domain = shift;
    if (($c->param('pause') && !$domain->is_paused)
        ||($c->param('resume') && $domain->is_paused)) {
        sleep 2;
    }
    $c->stash(_shutdown_disabled => '');
    $c->stash(_shutdown_disabled => 'disabled') if !$domain->is_active;

    $c->stash(_reboot_disabled => '');
    $c->stash(_reboot_disabled => 'disabled') if !$domain->is_active;

    $c->stash(_start_disabled => '');
    $c->stash(_start_disabled => 'disabled')    if $domain->is_active;

    $c->stash(_pause_disabled => '');
    $c->stash(_pause_disabled => 'disabled')    if $domain->is_paused
                                                    || !$domain->is_active;

    $c->stash(_resume_disabled => '');
    $c->stash(_resume_disabled => 'disabled')    if !$domain->is_paused;



}

sub view_machine {
    my $c = shift;
    my $domain = shift;

    return login($c) unless ( defined $USER && $USER->is_temporary) || _logged_in($c);

    $domain =  _search_requested_machine($c) if !$domain;
    return $c->render(template => 'main/fail') if !$domain;

    app->log->info($USER->name." requesting start_domain ".$domain->name." from "._remote_ip($c))
    if $CONFIG_FRONT->{log}->{log};

    my $enable_host_devices = $c->req->param('enable_host_devices');
    $enable_host_devices=1 if !defined $enable_host_devices;
    my @enable_host_devices;
    @enable_host_devices = ( 'enable_host_devices' => $enable_host_devices );

    return run_request($c, Ravada::Request->start_domain(
                    uid => $USER->id
             ,id_domain => $domain->id
            , remote_ip => _remote_ip($c)
            , @enable_host_devices
            )
    );
}

sub clone_machine($c, $anonymous=0) {
    return login($c) unless $anonymous || _logged_in($c);
    _init_error($c);

    my $base = _search_requested_machine($c);
    if (!$base ) {
        $c->stash( error => "Unknown base ") if !$c->stash('error');
        return $c->render(template => 'main/fail');
    };
    $c->stash(id_base => $base->id);
    return quick_start_domain($c, $base->id, $USER->name, $anonymous);
}

sub shutdown_machine {
    my $c = shift;
    return login($c) if !_logged_in($c);

    my ($domain, $type) = _search_requested_machine($c);

    my $id_req;
    if ($domain) {
        my $req;
        $req = Ravada::Request->force_shutdown_domain(id_domain => $domain->id, uid => $USER->id)
            if ($c->param('force'));
        $req = Ravada::Request->shutdown_domain(id_domain => $domain->id, uid => $USER->id)
            unless ($c->param('force'));
        $id_req = $req->id if $req;
    }
    return $c->redirect_to('/machines') if $type eq 'html';
    return $c->render(json => { req => $id_req });
}

sub reboot_machine {
    my $c = shift;
    return login($c) if !_logged_in($c);

    my ($domain, $type) = _search_requested_machine($c);

    my $req;
    $req = Ravada::Request->force_reboot_domain(id_domain => $domain->id, uid => $USER->id) if ($c->param('force'));;
    $req = Ravada::Request->reboot_domain(id_domain => $domain->id, uid => $USER->id) unless ($c->param('force'));;

    return $c->redirect_to('/machines') if $type eq 'html';
    return $c->render(json => { req => $req->id });
}

sub shutdown_start($c) {
    return login($c) if !_logged_in($c);

    my ($domain, $type) = _search_requested_machine($c);

    my $req = Ravada::Request->shutdown_start(id_domain => $domain->id
        ,uid => $USER->id
    );
    return $c->render(json => { req => $req->id });
}

sub force_shutdown_machine {
    my $c = shift;
    return login($c) if !_logged_in($c);

    my ($domain, $type) = _search_requested_machine($c);
    my $req = Ravada::Request->force_shutdown_domain(id_domain => $domain->id, uid => $USER->id);

    return $c->redirect_to('/machines') if $type eq 'html';
    return $c->render(json => { req => $req->id });
}

sub _do_remove_machine {
    my $c = shift;
    return login($c) if !_logged_in($c);

    my $domain = _search_requested_machine($c);

    if (!$domain) {
        $c->render(json => { msg => "Virtual Machine already removed" });
        return;
    }

    my $req = Ravada::Request->remove_domain(
        name => $domain->name
        ,uid => $USER->id
    );

    $c->render(json => { request => $req->id});
}

sub remove_machine {
    my $c = shift;
    return login($c)    if !_logged_in($c);
    return _do_remove_machine($c,@_);#   if $c->param('sure') && $c->param('sure') =~ /y/i;

}

sub remove_clones {
    my $c = shift;

    my $domain = _search_requested_machine($c);
    my @req;
    for my $clone ( $domain->clones) {
        next if $clone->{is_base};
        my $req = Ravada::Request->remove_domain(
            name => $clone->{name}
            ,uid => $USER->id
        );
        push @req,({ request => $req->id });
    }
    $c->render(json => \@req );

}

sub remove_base {
  my $c = shift;
  return login($c)    if !_logged_in($c);

  my $domain = _search_requested_machine($c);

  $c->render(json => { error => "Domain not found" })
    if !$domain;

  my $req = Ravada::Request->remove_base(
      id_domain => $domain->id
      ,uid => $USER->id
  );

  $c->render(json => { request => $req->id});
}

sub screenshot_machine {
    my $c = shift;
    return login($c)    if !_logged_in($c);

    my $domain = _search_requested_machine($c);

    my $req = Ravada::Request->screenshot(
        id_domain => $domain->id
    );
    $c->render(json => { request => $req->id});
}

sub copy_screenshot {
    my $c = shift;
    return login($c)    if !_logged_in($c);

    my $domain = _search_requested_machine($c);

    my $req = Ravada::Request->copy_screenshot (
        id_domain => $domain->id
    );
    $c->render(json => { request => $req->id});
}

sub set_base_vm( $c, $new_value) {

    my $id_vm = $c->stash('id_vm');
    my $domain = Ravada::Front::Domain->open($c->stash('id_domain'));

    if ($USER->id != $domain->id && !$USER->is_admin) {
        return $c->render(json => {message => 'access denied'});
    }

    $domain->_set_base_vm_db($id_vm, $new_value);
    if ($new_value) {
        my $req = Ravada::Request->set_base_vm(
        id_vm => $id_vm
        , id_domain => $domain->id
        , uid => $USER->id
        );
    } else {
        my $req = Ravada::Request->remove_base_vm(
            id_vm => $id_vm
            , id_domain => $domain->id
            , uid => $USER->id
        );
    }
    return $c->render(json => {message => 'processing request'});
}

sub prepare_machine {
    my $c = shift;
    return login($c)    if !_logged_in($c);

    my $domain = _search_requested_machine($c);
    return $c->render(json => { error => "Domain ".$domain->name." is locked" })
            if  $domain->is_locked();

    if (! $domain->_data('screenshot') && $domain->can_screenshot()
            && $domain->is_active) {
        Ravada::Request->screenshot(
            id_domain => $domain->id
        );
    }

    my $req = Ravada::Request->prepare_base(
        id_domain => $domain->id
        ,uid => $USER->id
    );

    $c->render(json => { request => $req->id});

}

sub start_machine {
    my $c = shift;
    return login($c) if !_logged_in($c);

    my ($domain, $type) = _search_requested_machine($c);
    return $c->render(text => "Domain not found") if !$domain;
    $domain->_data(status => 'starting');

    app->log->info($USER->name." start_domain ".$domain->name." from "._remote_ip($c))
    if $CONFIG_FRONT->{log}->{log};

    my $req = Ravada::Request->start_domain( uid => $USER->id
                                           ,id_domain => $domain->id
                                      ,remote_ip => _remote_ip($c)
    );

    return $c->render(json => { req => $req->id });
}

sub copy_machine {
    my $c = shift;

    return login($c) if !_logged_in($c);

    my $arg = decode_json($c->req->body);

    my $number = delete $arg->{copy_number};
    my $id_base= delete $arg->{id_base} or confess "Missing param id_base";
    my $ram = delete $arg->{copy_ram};
    $ram =~ s/,/./ if $ram;
    $ram = 0 if !$ram || $ram !~ /^\d+(\.\d+)?$/;
    $ram = int($ram*1024*1024);

    my $new_name = delete $arg->{new_name};
    my $owner = delete $arg->{new_owner};
    my $volatile = ( delete $arg->{copy_is_volatile} or 0);

    warn "Error: unknown args ".Dumper($arg) if keys %$arg;

    my $base = $RAVADA->search_domain_by_id($id_base) or confess "I can't find domain $id_base";
    my $name = ( $new_name or $base->name."-".$USER->name );

    $USER = _logged_in($c);
    $owner = undef if (! $USER) || (! $USER->is_admin);
    confess "owner do not exists" if ($owner) && (! Ravada::Auth::SQL->new(name => $owner));

    my @create_args = ( from_pool => 0 );
    push @create_args,( memory => $ram ) if $ram;
    push @create_args, ( name => $name ) if !defined $number || $number == 1;
    push @create_args, ( name => $name ) if !$number || $number == 1;
    push @create_args, ( id_owner => $owner ) if ($owner);
    push @create_args, ( volatile => $volatile);
    push @create_args, ( add_to_pool => $arg->{copy_is_pool} && $base->is_pool() ? 1 : 0 );

    my $req2 = Ravada::Request->clone(
            uid => $USER->id
            , id_domain => $base->id
            , number => $number
            ,@create_args
    );
    return $c->render(json => { request => $req2->id } );
}

sub new_machine_copy($c) {
    my $id_base = $c->param('src_machine');
    my $copy_name = $c->param('copy_name');

    my $ram = $c->param('copy_ram');
    $ram = 0 if !$ram || $ram !~ /^\d+(\.\d+)?$/;
    $ram = int($ram*1024*1024);

    my $req = Ravada::Request->clone(
        uid => $USER->id
        ,id_domain => $id_base
        ,name => $copy_name
        ,memory => $ram
    );

   return $c->redirect_to("/admin/machines");
}

sub machine_is_public {
    my $c = shift;
    my $id_machine = $c->stash('id');
    my $value = $c->stash('value');
    my $domain = $RAVADA->search_domain_by_id($id_machine);

    return $c->render(text => "unknown domain id $id_machine")  if !$domain;

    $domain->is_public($value) if defined $value;

    if ($value && !$domain->is_base) {
        my $req = Ravada::Request->prepare_base(
            id_domain => $domain->id
            ,uid => $USER->id
        );
    }

    return $c->render(json => $domain->is_public);
}

sub rename_machine {
    my $c = shift;
    my $id_domain = $c->stash('id');
    my $new_name = $c->stash('value');
    return login($c) if !_logged_in($c);

    #return $c->render(data => "Machine id not found in $uri ")
    return $c->render(data => "Machine id not found")
        if !$id_domain;
    #return $c->render(data => "New name not found in $uri")
    return $c->render(data => "New name not found")
        if !$new_name;

    my $req = Ravada::Request->rename_domain(    uid => $USER->id
                                               ,name => $new_name
                                          ,id_domain => $id_domain
    );

    return $c->render(json => { req => $req->id });
}

sub pause_machine {
    my $c = shift;
    return login($c) if !_logged_in($c);

    my ($domain, $type) = _search_requested_machine($c);
    my $req = Ravada::Request->pause_domain(name => $domain->name, uid => $USER->id);

    return $c->render(json => { req => $req->id });
}

sub hybernate_machine {
    my $c = shift;
    my ($domain, $type) = _search_requested_machine($c);
    my $req = Ravada::Request->hybernate(id_domain => $domain->id, uid => $USER->id);

    return $c->render(json => { req => $req->id });

}

sub resume_machine {
    my $c = shift;
    return login($c) if !_logged_in($c);

    my ($domain, $type) = _search_requested_machine($c);
    my $req = Ravada::Request->resume_domain(name => $domain->name, uid => $USER->id);

    return $c->render(json => { req => $req->id });
}

sub machine_change_hardware($c, $id, $options) {

    my $domain = Ravada::Front::Domain->open($id) or die "Unkown domain $id";


    my $max_mem = delete $options->{max_mem};
    my $memory = delete $options->{memory};

    my $info = $domain->get_info();
    my %change_mem;
    if (int($info->{max_mem}/1024) != $max_mem && $USER->is_admin) {
        $change_mem{max_mem} = $max_mem*1024;
    }

    if (int($info->{memory}/1024) != $memory) {
        $change_mem{memory} = $memory*1024;
    }
    Ravada::Request->change_hardware(
        uid => $USER->id
        ,id_domain => $id
        ,hardware=> 'memory'
        ,data => \%change_mem
    ) if keys %change_mem;

    my $max_virt_cpu = delete $options->{max_virt_cpu};
    my $n_virt_cpu = delete $options->{n_virt_cpu};

    my %change_cpu;

    if ($info->{max_virt_cpu} != $max_virt_cpu && $USER->is_admin) {
        $change_cpu{max_virt_cpu} = $max_virt_cpu;
    }

    if ($info->{n_virt_cpu} != $n_virt_cpu) {
        $change_cpu{n_virt_cpu} = $n_virt_cpu;
    }
    Ravada::Request->change_hardware(
        uid => $USER->id
        ,id_domain => $id
        ,hardware=> 'vcpus'
        ,data => \%change_cpu
    ) if keys %change_cpu;


}

sub machine_set_options($c, $id, $options) {
    return access_denied($c)       if !$USER->can_manage_machine($id);

    my $domain = Ravada::Front::Domain->open($id) or die "Unkown domain $id";

    my $owner = delete $options->{owner};
    $options->{id_owner} = $owner->{id} if $owner;

    my %field_minutes = map { $_ => 1 } ('run_timeout','shutdown_timeout');

    my $change;
    for my $key (keys %$options) {
        next if _privileged_machine_field($key) && !$USER->is_admin;

        next if !defined $options->{$key} && !defined $domain->_data($key);
        next if !$options->{$key} && !$domain->_data($key);

        next unless defined $options->{$key} && !defined $domain->_data($key)
         || !defined $options->{$key} && defined $domain->_data($key)
         || $options->{$key} ne $domain->_data($key);

        $change->{$key} = $options->{$key};
        my $value_text = $options->{$key};

        if ( exists $options->{$key} && defined $options->{$key}
            && $field_minutes{$key}) {
                $options->{$key} = $options->{$key}*60;

                if ($value_text =~ /^\d+$/) {
                    if ( $value_text >1 ) {
                        $value_text .= " minutes";
                    } elsif ($value_text == 1 ) {
                        $value_text .=" minute";
                    } else {
                        $value_text = "none";
                    }
                }
        }

        machine_set_value($c, $id, $key => $options->{$key}, $value_text);
    }

    return $c->render(json => $change);
}

sub _privileged_machine_field($field) {

    my %privileged = map { $_ => 1 } qw(timeout id_owner shutdown_disconnected shutdown_timeout autostart balance_policy);
    return $privileged{$field};
}

sub machine_set_value($c, $id, $field, $value, $value_text=$value) {

    return access_denied($c)       if !$USER->can_manage_machine($id);
    return access_denied($c) if _privileged_machine_field($field) && !$USER->is_admin;

    my $domain = Ravada::Front::Domain->open($id) or die "Unkown domain $id";
    my $field_text = $field;
    $field_text =~ tr/_/ /;

    return if !defined $domain->_data($field) && !defined $value;
    return if defined $value && defined $domain->_data($field)
    && $value eq $domain->_data($field);

    $value_text = 0 if !defined $value_text;

    if ($field eq 'id_owner') {
        $field_text = 'owner';
        my $user = Ravada::Auth::SQL->search_by_id($value);
        $value_text = $user->name;

    }

    if ($field eq "autostart" && defined $value) {
        my $req = Ravada::Request->domain_autostart(
            uid => $USER->id
            ,id_domain => $domain->id
            ,value => $value
        );
    }
    $USER->send_message("Setting $field_text to $value_text in "
        .$domain->name)
        if !defined $domain->_data($field) || $domain->_data($field) ne $value;

    $domain->_data($field, $value);

}

sub get_execution_machines_limit_per_current_user {
    my $c = shift;
    return login($c) if !$USER && !_logged_in($c);

    my %grants = $USER->grants();
    my $start_limit = ((exists($grants{'start_limit'})) && (defined($grants{'start_limit'})) && ($grants{'start_limit'} > 0)) ? $grants{'start_limit'} : $RAVADA->settings_global()->{'backend'}->{'start_limit'}->{'value'};
    my $can_start_many = $USER->can_start_many ? 1 : 0;
    my @running_domains;

    foreach my $domain (@{$RAVADA->list_domains( id_owner => $USER->id )})
    {
        push(@running_domains, $domain->{'id'}) if ($domain->{'is_active'});
    }

    return $c->render(json => { can_start_many => $can_start_many, start_limit => $start_limit, running_domains => \@running_domains });
}

sub list_requests {
    my $c = shift;

    my $list_requests = $RAVADA->list_requests();
    $c->render(json => $list_requests);
}

sub access_denied_if_no_anonymous_bases
{
    my $c = shift;

    my $bases_anonymous = $RAVADA->list_bases_anonymous(_remote_ip($c));

    return access_denied($c)    if !scalar @$bases_anonymous;
}

sub list_bases_anonymous {
    my $c = shift;

    my $bases_anonymous = $RAVADA->list_bases_anonymous(_remote_ip($c));

    return access_denied($c)    if !scalar @$bases_anonymous;

    return render_machines_user($c,1);
}

sub _remote_ip {
    my $c = shift;
    return (
            $c->req->headers->header('X-Forwarded-For')
                or
            $c->req->headers->header('Remote-Addr')
                or
            $c->tx->remote_address
    );
}

sub _get_anonymous_user {
    my $c = shift;

    $c->stash(_user => undef);
    my $name = $c->session('anonymous_user');

    my $user= Ravada::Auth::SQL->new( name => $name );

    confess "user ".$user->name." has no id, may not be in table users"
        if !$user->id;

    return $user;
}

# get or create a new anonymous user
sub _anonymous_user {
    my $c = shift;

    return if (access_denied_if_no_anonymous_bases($c));

    $c->stash(_user => undef);
    my $name = $c->session('anonymous_user');
    if (!$name) {
        $name = _new_anonymous_user($c);
        $c->session(anonymous_user => $name);
    }
    my $user= Ravada::Auth::SQL->new( name => $name );

    if ( !$user->id ) {
        $name = _new_anonymous_user($c);
        $c->session(anonymous_user => $name);
        $user= Ravada::Auth::SQL->new( name => $name );

        confess "USER $name has no id after creation"
            if !$user->id;
    }
    $c->stash( _user => $user );

    return $user;
}

sub _random_name {
    my $length = shift;
    my $ret = substr($$,3);
    my $max = ord('z') - ord('a');
    for ( 0 .. $length ) {
        my $n = int rand($max + 1);
        $ret .= chr(ord('a') + $n);
    }
    return $ret;
}

sub _new_anonymous_user {
    my $c = shift;

    my $length = 32;
    my $cookie = ($c->signed_cookie('mojolicious') or _random_name($length));
    my $name_mojo = reverse($cookie);

    $name_mojo =~ tr/[^a-z][^A-Z][^0-9]/___/c;

    my $name;
    for my $n ( 4 .. $length ) {
        $name = "anon".substr($name_mojo,0,$n);
        my $user;
        eval {
            $user = Ravada::Auth::SQL->new( name => $name );
            $user = undef if !$user->id;
        };
        last if !$user;
    }
    Ravada::Auth::SQL::add_user(name => $name, is_temporary => 1);

    return $name;
}

sub _insert_fields($c, $table, $arg) {
    my $sql = "INSERT INTO $table " ;
    my @names = sort grep { !/^_/ } keys %$arg;
    my $fields = join("," , @names);
    my $values = join("," , map { '?' } @names );

    my $sth = $RAVADA->_dbh->prepare("$sql ($fields) VALUES ($values)" );
    eval { $sth->execute(map {$arg->{$_} } @names) };
    my $error = $@;
    warn $error if $error;
    $error =~ s{ at /.*}{};
    return $c->render(json => { 'ok' => !$error, error => ($error or '') } );

}

sub _update_fields($c, $table, $arg) {
    my $id = delete $arg->{id};
    return _insert_fields($c, $table, $arg) if !$id;

    my $sth = $RAVADA->_dbh->prepare("SELECT * FROM $table WHERE id=?");
    $sth->execute($id);
    my $old = $sth->fetchrow_hashref;
    return $c->render(json => { ok => 0, error => "Error: id=$id not found in $table" })
    if !keys %$old;

    my $error = '';
    for my $field ( keys %$arg ) {
        next if $field =~ /^_/;
        next if
        !defined $old->{$field} && !defined $arg->{$field}
        || ( defined $old->{$field} && defined $arg->{$field}
            && $old->{$field} eq $arg->{$field});
        $sth = $RAVADA->_dbh->prepare("UPDATE $table set $field=? WHERE id=?");
        eval { $sth->execute($arg->{$field},$id) };
        $error = $@;
        last if $error;

        $USER->send_message("Setting $field to ".$arg->{$field}
            ." in $table");
    }
    if ($error) {
        warn $error;
        chomp $error;
        $error =~ s{ at .*}{};
    }
    return $c->render(json => { ok => !$error , error => ($error  or '') });

};

sub maintenance($c) {

    my $maintenance_end = $RAVADA->settings_global->{frontend}->{maintenance_end}
                                                            ->{value};
    my $localtime_end = DateTime::Format::DateParse->parse_datetime($maintenance_end
    );
    #    return $c->render(text => "Maintenance until ".$localtime_end->strftime('%Y-%m-%d %H:%M [%Z]'));
    return $c->render(
        maintenance_end => $maintenance_end
        ,template => '/main/maintenance'
    );
}

sub _list_sql_groups($c, $filter=undef, $data=0) {
    my $sql = "SELECT id,name FROM groups_local ";
    $sql .= " WHERE name like ? " if defined $filter;
    $sql .= " ORDER BY name";
    my $sth = $RAVADA->_dbh->prepare($sql);
    if (defined $filter) {
       $sth->execute('%'.$filter.'%');
    } else {
       $sth->execute();
    }
    my @groups;
    while (my $row = $sth->fetchrow_hashref) {
        if ($data) {
            push @groups,($row);
        } else {
            push @groups,($row->{name});
        }
    }
    return $c->render(json => \@groups);
}

sub _list_ldap_groups($c, $name='*', $data=0) {
    return $c->render(json => []) if !$RAVADA->feature('ldap');

    $name = '*'.$name.'*' if $name !~ /\*/;
    my @groups = Ravada::Auth::LDAP::search_group( name => $name );
    if ($data) {
        return $c->render(json => [ sort { $a cmp $b } map { {name => $_->get_value('cn')} } @groups ] );
    } else {
        return $c->render(json => [ sort { $a cmp $b } map { $_->get_value('cn') } @groups ] );
    }
};

sub _list_ldap_users($c, $filter='*' ) {
    return $c->render(json => []) if !$RAVADA->feature('ldap');

    $filter = '*' if $filter eq 'undefined';
    $filter = '*'.$filter if length($filter) && $filter !~ /^\*/;

    if ( exists $LDAP_USERS{$filter} ) {
        my $data_json = $LDAP_USERS{$filter};
        my $data;
        eval { $data = decode_json($data_json) };
        return $c->render(json => $data) if $data;
    }
    $LDAP_USERS{$filter} = encode_json({ error => '', entries => [] });
    my @users;
    eval { @users = Ravada::Auth::LDAP::search_user(name => $filter, escape_username => 0) };
    my $error = ($@ or '');
    if ($@ =~ /Sizelimit exceeded/) {
        $error = "Warning: Too many results, type in the search box to filter.";
    }
    if (scalar @users>100) {
        $error = "Warning: Too many results, type in the search box to filter.";
        $#users= 100;
    }
    my @entries;
    for my $entry (@users) {
        push @entries,({name => $entry->dn});
    }
    my $ret = {error => $error, entries => \@entries };
    $LDAP_USERS{$filter} = encode_json($ret);
    return $c->render(json => $ret );
}

sub _list_ldap_group_members($c, $name) {
    return $c->render(json => [ map {  { id => $_, name => $_ } } Ravada::Auth::LDAP::_group_members($name) ] );
}

sub _list_group_members($c, $type, $name) {
    if ($type eq 'ldap') {
        return _list_ldap_group_members($c, $name);
    } elsif ($type eq 'local') {
        my $group;
        if ($name =~ /^\d+$/) {
            $group = Ravada::Auth::Group->open($name);
        } else {
            $group = Ravada::Auth::Group->new(name => $name);
        }
        return $c->render(json => [ $group->members_info ]);
    } else {
        confess "Error: unkonwn group type $type";
    }
}

#################################################################################
my $routes = app->routes->children;
for my $route (@$routes){
    $route->pattern->quote_start('(');
    $route->pattern->quote_end(')');
    $route->pattern->parse($route->pattern->unparsed);
}

app->secrets($CONFIG_FRONT->{secrets})  if $CONFIG_FRONT->{secrets};
app->start;
__DATA__

@@ index.html.ep
% layout 'default';
<h1>Welcome to SPICE !</h1>

<form method="post">
    User Name: <input name="login" value ="<%= $login %>"
            type="text"><br/>
    Password: <input type="password" name="password" value=""><br/>
    Base: <select name="id_base">
%       for my $option (sort keys %$base) {
            <option value="<%= $option %>"><%= $base->{$option} %></option>
%       }
    </select><br/>

    <input type="submit" name="submit" value="launch">
</form>
% if (scalar @$error) {
        <ul>
%       for my $i (@$error) {
            <li><%= $i %></li>
%       }
        </ul>
% }

@@ bases.html.ep
% layout 'default';
<h1>Choose a base</h1>

<ul>
% for my $i (sort values %$base) {
    <li><a href="/ip/<%= $i %>"><%= $i %></a></li>
% }
</ul>

@@ run.html.ep
% layout 'default';
<h1>Run</h1>

Hi <%= $name %>,
<a href="<%= $url %>">click here</a>

@@ layouts/default.html.ep

<h1>Run</h1>


<!DOCTYPE html>
<html>
  <head><title><%= title %></title></head>
  <body>
    <%= content %>
    <hr>
        <h2>Requirements</h1>
            <ul>
            <li>Linux: virt-viewer</li>
            <li>Windows: <a href="http://bfy.tw/5Nur">Spice plugin for Firefox</a></li>
            </ul>
        </h2>
  </body>
</html>

@@ exception.development.html.ep
<!DOCTYPE html>
<html>
  <head><title>Ravada Server error</title></head>
  <body>
    <h1>Exception</h1>
    <p><%= $exception->message %></p>
    <h1>Stash</h1>
    <pre><%= dumper $snapshot %></pre>
  </body>
</html>
