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

use strict;
use warnings;
use Logging;
use DumpComposer;
use PreMigrationChecks;

my @MESSAGES;

# severity levels - according to migration framework
use constant {
  ERROR => 'error',
  WARNING => 'warning',
  INFO => 'info',
};

use constant SEVERITY_LEVELS => [ERROR, WARNING, INFO];
use constant XML_REPORT_FILENAME => 'pre-migration.xml';

use PreMigrationMessages;

# ============================= Entry point - check function ==================

sub check {
  print "Checking installation for migration issues, please wait...\n";
  PreMigrationChecks::checkOverall();
  DumpComposer::makeDump(
    AgentConfig::cwd(), 
    undef, undef, undef, undef, 1
  );

  PreMigration::writeXmlReport(XML_REPORT_FILENAME);
}

# ============================= Adding messages to report =====================

# add message by id from PreMigrationMessages.pm
sub message {
  my ($id, $messageContext) = @_;

  my $messageInfo = _getMessageInfo($id);
  if (!defined $messageInfo) {
    print STDERR "Pre-migration message '$id' does not exist\n";
    return;
  }

  # substitute context variables
  my $briefMessage = $messageInfo->{'briefMessage'};
  my $detailedMessage = $messageInfo->{'detailedMessage'};
  foreach my $context ($messageContext, \%PreMigrationMessages::COMMON_CONTEXT) {
    foreach my $key (keys %{$context}) {
      $briefMessage =~ s/{$key}/$context->{$key}/g;
      $detailedMessage =~ s/{$key}/$context->{$key}/g;
    }
  }

  if ($messageInfo->{'severity'} eq WARNING) {
    Logging::warning("Pre-migration check: $briefMessage");
  } elsif ($messageInfo->{'severity'} eq ERROR) {
    Logging::error("Pre-migration check: $briefMessage");
  }

  _addMessage($id, $messageInfo->{'severity'}, $briefMessage, $detailedMessage, $messageContext);
}

# assert that some condition is true, and if so - add message
sub assert {
  my ($condition, $messageId, $context) = @_;

  if ($condition) {
    message($messageId, $context);
  }
}

sub formatList {
	my @list = @_;

	return join(', ', map { "'" . $_ . "'" } @list);
}

# @param $severity see the list of severity levels above
# @param $briefMessage brief description of the problem
# @param $detailedMessage detailed sescription of the problem, should contain a solution 
#   that could be used by customer to avoid or solve the problem
# @param $context reference to a hash with meta-information about context where the message was issued
#   for example, for domain it could be {'type' => 'domain', 'id' => 'example.com'}, for spamassassin option
#   it could be {'type' => 'spamassassion-option', 'name' => 'OPTION_NAME', 'value' => 'option_value', 'mailbox' => 'mail.example.tld'}
#   use this for filtering and sorting of the messages
sub _addMessage {
  my ($id, $severity, $briefMessage, $detailedMessage, $context) = @_;

  push @MESSAGES, {
    'severity' => $severity, 
    'briefMessage' => $briefMessage,
    'detailedMessage' => $detailedMessage,
    'context' => $context,
    'id' => $id
  };
}

# Search for a message with a given ID. If one is found in agent-specific messages,
# return that one, otherwise, search in common messages as well.
# @param message ID
# @return hash of message attributes
sub _getMessageInfo {
  my ($id) = @_;
  my $message;

  if (defined $PreMigrationMessages::MESSAGES{$id}) {
    return $PreMigrationMessages::MESSAGES{$id};
  }
  return $message;
}

# ============================= XML serialization =============================

sub writeXmlReport {
  my ($filename) = @_;

  my $node = _makeReportXmlTree();

  if (open(my $fp, "> $filename")) {
    $node->serialize($fp, 1, 0);
    close($fp);
  } else {
    Logging::error("Failed to write pre-migration XML report '$filename'");
  }
}

sub _makeReportXmlTree {
  my %messages = _getAllMessagesGroupedByObject();

  return DumpComposer::_walkTree(
    DumpComposer::_createFullTree(\&Agent::getPreMigrationDomains),
    my $nodeFunctions = {
      'admin' => sub { 
        my ($id, $childXmlNodes) = @_; 
        return XmlNode->new('report',
          'attributes' => {
            'type' => 'Source server',
            'name' => 'confixx',
          },
          'children' => _makeReportXmlChildNodes($childXmlNodes, $messages{'common'})
        );
      },
      'reseller' => sub { 
        my ($id, $childXmlNodes) = @_; 
        return XmlNode->new('report',
          'attributes' => {
            'type' => 'Reseller',
            'name' => $id
          },
          'children' => _makeReportXmlChildNodes($childXmlNodes, $messages{'user'}->{$id})
        );
      },
      'client' => sub { 
        my ($id, $childXmlNodes) = @_; 
        return XmlNode->new('report',
          'attributes' => {
            'type' => 'Client',
            'name' => $id
          },
          'children' => _makeReportXmlChildNodes($childXmlNodes, $messages{'user'}->{$id})
        )
      },
      'domain' => sub { 
        my ($id, $childXmlNodes) = @_; 
        return XmlNode->new('report',
          'attributes' => {
            'type' => 'Domain', 
            'name' => $id
          },
          'children' => _makeReportXmlChildNodes($childXmlNodes, $messages{'domain'}->{$id})
        )
      }
    }
  );
}

sub _makeReportXmlChildNodes {
  my ($childXmlNodes, $messages) = @_;

  return [
    XmlNode->new('issues', 'children' => [
        map {
          XmlNode->new('issue', 
            'children' => [
              XmlNode->new('type', 'content' => $_->{'id'}),
              XmlNode->new('severity', 'content' => $_->{'severity'}),
              XmlNode->new('description', 'content' => $_->{'briefMessage'}),
              XmlNode->new('solution', 'content' => $_->{'detailedMessage'}),
            ]
          )
        } @$messages
      ]),
    XmlNode->new('children', 'children' => $childXmlNodes)
  ]
}

# @return hash with keys:
#    'user' - hash with keys - user (reseller or client) names, values - message lists
#    'domain' - hash with keys - domain names, values - messages lists
#    'common' - list of common (not related to a reseller, client, or domain) messages
sub _getAllMessagesGroupedByObject {
  my %result = (
    'user' => {}, 
    'domain' => {},
    'common' => []
  );

  foreach my $message (@MESSAGES) {
    if (defined($message->{'context'}->{'domain'})) {
      my $domain = $message->{'context'}->{'domain'};

      unless (defined($result{'domain'}->{$domain})) {
        $result{'domain'}->{$domain} = [];
      }

      push @{$result{'domain'}->{$domain}}, $message;
    } elsif (defined($message->{'context'}->{'user'})) {
      my $user = $message->{'context'}->{'user'};

      unless (defined($result{'user'}->{$user})) {
        $result{'user'}->{$user} = [];
      }

      push @{$result{'user'}->{$user}}, $message;
    } else {
      push @{$result{'common'}}, $message;
    }
  }

  return %result;
}

1;
