# Copyright 1999-2012. Parallels IP Holdings GmbH. All Rights Reserved.
package CommonXmlNodes;

# This package contains functions to construct XML nodes, 
# that are commonly used in migration agents to make dump.xml

use strict;
use warnings;

use Logging;
use XmlNode;

sub password {
  my ($type, $password) = @_;

  my $result = XmlNode->new('password', 'attributes' => {'type' => $type});
  $result->setText($password) if $password;

  return $result;
}

sub emptyPassword {
  return password('empty');
}

sub encryptedPassword {
  my ($password) = @_;

  return password('encrypted', $password);
}

sub status {
  my ($status, $disabledBy) = @_;

  my $enabledNode;

  if ( $status ) {
    $enabledNode = XmlNode->new('enabled');
  } else {
    $enabledNode = XmlNode->new('disabled-by', 
      'attributes' => {'name' => $disabledBy}
    );
  }
  
  return XmlNode->new('status', (
    'children' => [ $enabledNode ]
  ));
}

sub ip {
  my ($ip, $type) = @_;

  return XmlNode->new('ip',
    'children' => [
      XmlNode->new('ip-type', 'content' => $type),
      XmlNode->new('ip-address', 'content' => $ip)
    ]
  );
}

sub ipPool {
  my ($ips) = @_;

  my @ipNodes = map { ip($_->{'ip'}, $_->{'type'}) } @{$ips}; 
  return XmlNode->new('ip_pool', 
    'children' => \@ipNodes
  );
}


# parse SOA DNS record, create <dns-zone-param> elements
sub dnsZoneParams {
  my ($src, $ttl, $in, $type, $ns, $email, $serial, @values) = @_;
  my @parameters = qw(ttl refresh retry expire minimum);

  unshift @values, $ttl;
  unless ('SOA' eq $type && @parameters == @values) {
      Logging::warning("Invalid DNS SOA record: " . join ' ', @_);
      return;
  }

  my @records;
  Logging::trace("DNS SOA record: " .  join ' ', @_);
  for my $value (@values) {
    my $record = XmlNode->new('dns-zone-param',
          'attributes' => {
              'name'  => shift @parameters,
              'unit'  => 'second',
              'value' => $value
          });
    push @records, $record;
  }
  return @records;
}

# parse DNS record, create <dnsrec> element
sub dnsRecord {
  my ($src, $ttl, $in, $type, @text) = @_;

  my $record = XmlNode->new('dnsrec', 'attributes' => {'type' => $type, 'src' => $src });
  if (@text) {
    my $dst;
    # TODO check mapping of SRV record field 'Weight' (RFC 2782). Value '1' gets mapped to '50(very high)'
    if ('TXT' eq $type) {
      # treat TXT records specially: put all TXT content into dst field
      $dst = join ' ', @text;
      # BIND puts quotes around field contents, Plesk migrator doesn't handle them correctly; RFC 1035
      $dst =~ s/^\s*\"(.*)\"\s*$/$1/;
    }
    else {
      # put last record field into 'dst' field
      $dst = pop @text;
      if (@text) {
        # if there are remaining fields, put them into 'opt' field
        $record->setAttribute('opt', join ' ', @text);
      }
    }
    $record->setAttribute('dst', $dst);
  }
  return $record;
}

sub limitsAndPermissions {
  my ($limits, $permissions) = @_;

  return XmlNode->new('limits-and-permissions',
    'children' => [
      (map { limit($_, $limits->{$_}) } keys %$limits),
      (map { permission($_, $permissions->{$_}) } keys %$permissions)
    ]
  );
}

sub limit {
  my ($name, $value) = @_;

  return XmlNode->new('limit',
    'attributes' => { 'name' => $name },
    'content' => $value
  );
}

sub permission {
  my ($name, $value) = @_;

  return XmlNode->new('permission',
    'attributes' => { 
      'name' => $name,
      'value' => $value ? 'true' : 'false'
    }
  );
}

sub templateItem {
  my ($name, $value) = @_;

  return XmlNode->new('template-item', 
    'content' => $value,
    'attributes' => { 'name' => $name }
  );
}

sub domainTemplate {
  my ($name, $templateItems) = @_;

  my @templateItemNodes = map { templateItem($_, $templateItems->{$_}) } keys(%$templateItems);

  return XmlNode->new('domain-template',
    'attributes' => { 'name' => $name },
    'children' => \@templateItemNodes 
  );
}

sub sysuser {
  my ($userName, $password, $homedir, $cronLines) = @_;

  return XmlNode->new('sysuser',
    'attributes' => {
      'name' => $userName,
      'home' => $homedir
    },
    'children' => [
      encryptedPassword($password),
      cronJobs(@$cronLines)
    ]
  );
}

# the only parameter - hash with options, which can have the following keys:
#   'status' - is spamassassin enabled?
#   'hits' - number of hits required to consider message as a spam
#   'subj-text' - text to add to a subject of a spam message
#   'action' - action to perform with a spam message - "delete", "mark" or "move"
#   'blacklist', 'whitelist', 'unblacklist', 'unwhitelist' - references to arrays with corresponding e-mail addresses
sub spamassassin {
  my (%options) = @_;

  my (%attributes, @children);

  $attributes{'status'} = $options{'status'} ? 'on' : 'off';
  $attributes{'hits'} = $options{'hits'} if (defined($options{'hits'}));
  $attributes{'subj-text'} = $options{'subj-text'} if (defined($options{'subj-text'}));
  $attributes{'action'} = $options{'action'} if (defined($options{'actions'})); 

  foreach my $listName ('black', 'white', 'unblack', 'unwhite') {
    if (defined($options{$listName . 'list'})) {
      push(
        @children, 
        map { XmlNode->new($listName . 'list-member', 'content' => $_ ) } @{$options{$listName . 'list'}}
      );
    }
  }

  return XmlNode->new('spamassassin', 
    'attributes' => \%attributes,
    'children' => \@children,
  );
}

# the only parameter - array with traffic records - references to a hashes with the following keys:
#   'amount' - amount of traffic in bytes
#   'year', 'month', 'day' - as integers
#   'direction' - 'in' or 'out'
#   'type' - one of: 'http', 'ftp', 'smtp', 'pop3-imap'
sub traffic {
  my (@trafficRecords) = @_;

  return XmlNode->new('traffic',
    'content' => 
      join "\n", (
        map {
          # date format: yyyy-mm-dd 
          my $date = sprintf('%04d-%02d-%02d', $_->{'year'}, $_->{'month'}, $_->{'day'});
          join ' ', ($date, $_->{'type'}, $_->{'direction'}, $_->{'amount'});
        } @trafficRecords
      )
  );
}

sub cronJobs {
  return map { my $node = XmlNode->new('cron', 'content' => $_); } @_;
}

sub dbServerNode {
  my ($type, $host, $port) = @_;
  my $item = XmlNode->new('db-server',
    'attributes' => {
      'type' => $type,
    },
    'children' => [
      XmlNode->new('host', 'content' => $host),
      XmlNode->new('port', 'content' => $port),
    ],
  );
  return $item;
}

sub defaultDbServerNode {
  return dbServerNode('mysql', 'localhost', '3306');
}

1;
