# Copyright 1999-2016. Parallels IP Holdings GmbH. All Rights Reserved.
package DumpComposer;

use strict;
use XmlNode;
use Logging;
use Agent;
use MigrationDumpStatus;
use Storage::FileNameCreator;

#
# Constants
#
my $DUMP_FILE_NAME = 'dump.xml';
my $STATUS_FILE_NAME = 'dumping-status.xml';
my $MIGRATION_RESULT_FILE_NAME = 'migration.result';
my $CONTENT_LIST_FILE_NAME = 'content-list.xml';

#
# make dump in 'shallow' mode. Corresponds to old "-da -nc" mode
#


sub getBackupFileName {
  my $fnamecreator = Storage::FileNameCreator->new();
  return $fnamecreator->getFileName( '', 'backup', '', 'info' ) . ".xml";
}

sub makeShallowDump {
  my $workDir = shift;

  Agent::setWorkDir($workDir);

  my $migrationDumpNode = createMigrationDumpNode();
  my $migrationDumpAdminNode = XmlNode->new( 'admin' );

  # dump resellers
  my @resellerIds = Agent::getResellers();
  
  if ( scalar( @resellerIds ) > 0 ) {
    my $resellersNode = XmlNode->new( 'resellers' );
    foreach my $resellerId ( @resellerIds ) {
      my $resellerNode = XmlNode->new( 'reseller' );
      $resellerNode->setAttribute( 'name', $resellerId );
      $resellerNode->setAttribute( 'guid', '' );

      my @resClientIds = Agent::getClients($resellerId);
      if ( @resClientIds ) {
        my $resClientsNode = XmlNode->new( 'clients' );
        foreach my $resClientId ( @resClientIds ) {

          my $resClientNode = XmlNode->new( 'client' );
          $resClientNode->setAttribute( 'name', $resClientId );
          $resClientNode->setAttribute( 'guid', '' );
            
          my @clDomainIds = Agent::getDomains($resClientId);
          if ( @clDomainIds ) {
            my $clDomainsNode = XmlNode->new( 'domains' );
            foreach my $clDomainId ( @clDomainIds ) {

              my $clDomainNode = XmlNode->new( 'domain' );
              $clDomainNode->setAttribute( 'name', $clDomainId);
              $clDomainNode->setAttribute( 'guid', '' );

              $clDomainNode->addChild( XmlNode->new( 'preferences' ) );

              my $clDomainPropertiesNode = Agent::getDomainProperties($clDomainId);
              $clDomainNode->addChild( $clDomainPropertiesNode );
              $clDomainsNode->addChild( $clDomainNode );

            }
            $resClientNode->addChild( $clDomainsNode );
          }
          $resClientsNode->addChild( $resClientNode );
        }
        $resellerNode->addChild( $resClientsNode );
      }
    
      my @resDomainIds = Agent::getDomains($resellerId);
      if ( @resDomainIds ) {
        my $resDomainsNode = XmlNode->new( 'domains' );
        foreach my $resDomainId ( @resDomainIds ) {

          my $resDomainNode = XmlNode->new( 'domain' );
          $resDomainNode->setAttribute( 'name', $resDomainId );
          $resDomainNode->setAttribute( 'guid', '' );

          $resDomainNode->addChild( XmlNode->new( 'preferences' ) );

          my $resDomainPropertiesNode = Agent::getDomainProperties($resDomainId);
          $resDomainNode->addChild( $resDomainPropertiesNode );
          $resDomainsNode->addChild( $resDomainNode );

        }
        $resellerNode->addChild( $resDomainsNode );
      }
      $resellersNode->addChild( $resellerNode );      
    }
    $migrationDumpAdminNode->addChild( $resellersNode );
  }

  # dump clients
  my @clientIds = Agent::getClients( undef );

  if ( scalar( @clientIds ) > 0 ) {
    my $clientsNode = XmlNode->new( 'clients' );
    foreach my $clientId ( @clientIds ) {
      my $clientNode = XmlNode->new( 'client' );
      $clientNode->setAttribute( 'name', $clientId );
      $clientNode->setAttribute( 'guid', '' );

      my @clDomainIds = Agent::getDomains($clientId);
      if ( @clDomainIds ) {
        my $clDomainsNode = XmlNode->new( 'domains' );
        foreach my $clDomainId ( @clDomainIds ) {

          my $clDomainNode = XmlNode->new( 'domain' );
          $clDomainNode->setAttribute( 'name', $clDomainId );
          $clDomainNode->setAttribute( 'guid', '' );

          $clDomainNode->addChild( XmlNode->new( 'preferences' ) );

          my $clDomainPropertiesNode = Agent::getDomainProperties($clDomainId);
          $clDomainNode->addChild( $clDomainPropertiesNode );
          $clDomainsNode->addChild( $clDomainNode );

        }
        $clientNode->addChild( $clDomainsNode );
      }
      $clientsNode->addChild( $clientNode );
    }
    $migrationDumpAdminNode->addChild( $clientsNode );
  }

  # dump domains
  my @domainIds = Agent::getDomains( undef );
  
  if ( scalar( @domainIds ) > 0 ) {
    my $domainsNode = XmlNode->new( 'domains' );
    foreach my $domainId ( @domainIds ) {
      my $domainNode = XmlNode->new( 'domain' );
      $domainNode->setAttribute( 'name', $domainId);
      $domainNode->setAttribute( 'guid', '' );

      $domainNode->addChild( XmlNode->new( 'preferences' ) );

      my $domainPropertiesNode = Agent::getDomainProperties($domainId);
      $domainNode->addChild( $domainPropertiesNode );
      $domainsNode->addChild( $domainNode );
    }
    $migrationDumpAdminNode->addChild( $domainsNode );
  }
  
  $migrationDumpNode->addChild( $migrationDumpAdminNode );

  unless ( serializeXmlNode($migrationDumpNode,"$workDir/$DUMP_FILE_NAME") ) {
    Logging::error("Dump file '$DUMP_FILE_NAME' is not saved.");
  }
  
  return $migrationDumpNode;
}
#
# Pass 'undef' for $resellers, $clients and $domains to make 'dump-all'
#
sub makeDump {
  my ( $workDir, $resellers, $clients, $domains, $configOnly, $onlyMail, $onlyHosting, $gzip) = @_;

  Agent::setWorkDir($workDir);

  $resellers = getAllResellers() unless( defined ( $resellers ) );

  $clients = getAllClients() unless( defined ( $clients ) );

  $domains = getAllDomains() unless( defined ( $domains ) );
  
  # Resellers and clients are now ignored. Otherwise they should be processed in a way like 'makeShallowDump' does.

  my $statusFile = "$workDir/$STATUS_FILE_NAME";  
  my $dumpStatus = MigrationDumpStatus->new($statusFile);
  $dumpStatus->start(scalar(@{$resellers}) + scalar(@{$clients}), scalar(@{$domains}));

  my $migrationDumpNode = createMigrationDumpNode($configOnly);

  foreach my $domainId ( @{$domains} ) {
    my $domainNode = makeDomainNode( $domainId, $configOnly, $onlyMail, $onlyHosting, $gzip);
    $migrationDumpNode->addChild( $domainNode ) if defined ( $domainNode );
  }

  my $contentListNode = getContentList($migrationDumpNode);

  my $append2content = sub {
    my ($fileName, $isMain) = @_;
    return unless (-f  "$workDir/$fileName");
    my $fileSize = -s "$workDir/$fileName";
    my $fileNode = XmlNode->new( 'file' );
    $fileNode->setAttribute( 'size', $fileSize );
	$fileNode->setAttribute( 'main' , $isMain ? 'true' : 'false' );
    $fileNode->setText( $fileName );
    $contentListNode->addChild( $fileNode );
    my $contentListSize = $contentListNode->getAttribute( 'size' ) || '0';
    my $contentListNewSize = int($contentListSize) + int($fileSize);
    $contentListNode->setAttribute( 'size', $contentListNewSize );
  };

  if ( serializeXmlNode($migrationDumpNode,"$workDir/" . getBackupFileName()) ) {
    $append2content->(getBackupFileName(), 1);
  }
  else {
    Logging::error("Dump file ".getBackupFileName()." is not saved.");
  }

  $append2content->($MIGRATION_RESULT_FILE_NAME, 0);

  unless ( serializeXmlNode($contentListNode,"$workDir/$CONTENT_LIST_FILE_NAME") ) {
    Logging::error("Content list file '$CONTENT_LIST_FILE_NAME' is not saved.");
  }

  return $migrationDumpNode;
}

sub getAllResellers {
  my @resellerIds = Agent::getResellers();
  return \@resellerIds;
}

sub getAllClients {
  my @clientIds; 

  my @resellerIds = Agent::getResellers();
  foreach my $resellerId ( @resellerIds ) {
    my @resClientIds = Agent::getClients( $resellerId );
    if ( @resClientIds ) {
      push (@clientIds, @resClientIds); 
    }
  }
  
  my @rootClientIds = Agent::getClients( undef );
  push (@clientIds, @rootClientIds); 
  
  return \@clientIds;
}

sub getAllDomains {
  my @domainIds; 

  my @resellerIds = Agent::getResellers();
  foreach my $resellerId ( @resellerIds ) {
    my @resClientIds = Agent::getClients( $resellerId );
    foreach my $resClientId ( @resClientIds ) {
      my @clDomainIds = Agent::getDomains( $resClientId );
      if ( @clDomainIds ) {
        push (@domainIds, @clDomainIds);
      }
    }
    my @resDomainIds = Agent::getDomains( $resellerId );
    if ( @resDomainIds ) {
      push (@domainIds, @resDomainIds);
    }
  }
  
  my @rootDomains = Agent::getDomains( undef );
  push (@domainIds, @rootDomains); 
  
  return \@domainIds;
}

sub createMigrationDumpNode {
  my $configOnly = shift;
  my $migrationDumpNode = XmlNode->new( 'migration-dump' );
  $migrationDumpNode->setAttribute( 'agent-name', Agent::getAgentName() );
  $migrationDumpNode->setAttribute( 'dump-version', Agent::getAgentVersion() );
  $migrationDumpNode->setAttribute( 'content-included', $configOnly ? 'false' : 'true' );

  my $dumpInfoNode = XmlNode->new( 'dump-info' );

  my $cpDescriptionNode = XmlNode->new( 'cp-description' );
  $cpDescriptionNode->setAttribute( 'type', Agent::getAgentName() );
  $dumpInfoNode->addChild( $cpDescriptionNode );

  my $contentTransportDescriptionNode = Agent::getContentTransportDescription();
  if ( $contentTransportDescriptionNode =~ /XmlNode/ ) {
    my $contentTransportNode = XmlNode->new( 'content-transport' );
    $contentTransportNode->addChild( $contentTransportDescriptionNode );
    $dumpInfoNode->addChild ( $contentTransportNode );
  }

  $migrationDumpNode->addChild( $dumpInfoNode );
  return $migrationDumpNode;
}

sub makeDomainNode {
  my ($domainId, $configOnly, $onlyMail, $onlyHosting, $gzip) = @_;

  # The dump returned in 'recursive' mode doesn't have content and is always 'full'.
  my $recursive = 'recursive';

  my %contentOptions;
  $contentOptions{'only-mail'} = 1 if defined ( $onlyMail );
  $contentOptions{'only-hosting'} = 1 if defined ( $onlyHosting );

  my $domainNode = Agent::getDomain( $domainId, $recursive, $configOnly ? undef : 'content', $configOnly ? undef : $gzip, \%contentOptions);

  unless ( ref($domainNode) =~ /XmlNode/ ) {return undef};
  unless ($domainNode->getName() eq 'domain' ) {return undef};

  unless ( defined ( $onlyMail ) || defined ( $onlyHosting ) ) {
    return $domainNode;
  }

  #
  # Process 'onlyMail' and 'onlyHosting' cases.
  # 
  if ( $onlyMail ) {
    my $onlyMailDomain = $domainNode->copy('no_children');

    $onlyMailDomain->addChild( XmlNode->new ( 'preferences' ) );

    my $onlyMailPropertiesNode = XmlNode->new( 'properties' );
    my $propertiesNode = $domainNode->getChild( 'properties' );
    my $ipNode = $propertiesNode->getChild( 'ip' );
    $onlyMailPropertiesNode->addChild( $ipNode ) if defined ( $ipNode );
    $onlyMailPropertiesNode->addChild( $propertiesNode->getChild( 'status' ) );

    $onlyMailDomain->addChild( $onlyMailPropertiesNode );

    my $mailsystemNode = $domainNode->getChild( 'mailsystem' );
    $onlyMailDomain->addChild( $mailsystemNode ) if defined ( $mailsystemNode );

    my $maillistsNode = $domainNode->getChild( 'maillists' );
    $onlyMailDomain->addChild( $maillistsNode ) if defined ( $maillistsNode );

    return $onlyMailDomain;
  }
  elsif ( $onlyHosting ) {
    my $onlyHostingDomain = $domainNode->copy('no_children');

    $onlyHostingDomain->addChild( $domainNode->getChild( 'preferences' ) );

    $onlyHostingDomain->addChild( $domainNode->getChild( 'properties' ) );

    my $limitsAndPermissionsNode = $domainNode->getChild( 'limits-and-permissions' );
    $onlyHostingDomain->addChild( $limitsAndPermissionsNode ) if defined ( $limitsAndPermissionsNode );

    my $databasesNode = $domainNode->getChild( 'databases' );
    $onlyHostingDomain->addChild( $databasesNode ) if defined ( $databasesNode );

    my @odbcDsnNodes = $domainNode->getChildren( 'odbc-dsn' );
    if ( @odbcDsnNodes ) {
      foreach my $odbcDsnNode ( @odbcDsnNodes ) {
        $onlyHostingDomain->addChild( $odbcDsnNode );
      }
    }

    my $trafficNode = $domainNode->getChild( 'traffic' );
    $onlyHostingDomain->addChild( $trafficNode ) if defined ( $trafficNode );

    my $certificatesNode = $domainNode->getChild( 'certificates' );
    $onlyHostingDomain->addChild( $certificatesNode ) if defined ( $certificatesNode );

    my $tomcatNode = $domainNode->getChild( 'tomcat' );
    $onlyHostingDomain->addChild( $tomcatNode ) if defined ( $tomcatNode );

    my $domainuserNode = $domainNode->getChild( 'domainuser' );
    $onlyHostingDomain->addChild( $domainuserNode ) if defined ( $domainuserNode );

    my $phostingNode = $domainNode->getChild( 'phosting' );
    if ( defined ( $phostingNode ) ) {
      $onlyHostingDomain->addChild( $phostingNode );
    }
    else {
      my $shostingNode = $domainNode->getChild( 'shosting' );
      if ( defined ( $shostingNode ) ) {
        $onlyHostingDomain->addChild( $shostingNode );
      }
      else {
        my $fhostingNode = $domainNode->getChild( 'fhosting' );
        if ( defined ( $fhostingNode ) ) {
          $onlyHostingDomain->addChild( $fhostingNode );
        }
      }
    }

    return $onlyHostingDomain;
  }

	return $domainNode;
}


#
# Store $xmlNode to $filename
#
sub serializeXmlNode {
  my ($xmlNode, $filename) = @_;
  
  unless ( open DUMP, ">$filename" ) {
    Logging::error("Unable open file '". $filename . "' for write: $!");
    return;
  }
  binmode DUMP;
  $xmlNode->serialize(\*DUMP);
  close DUMP;
  return 1;
}
#
# Returns an XmlNode with content list.
#
sub getContentList {
  my $dumpXmlNode = shift;
  unless ( ref($dumpXmlNode) =~ /XmlNode/ ) {return undef};
  
  my $contentListNode = XmlNode->new( 'contentlist' );
  $contentListNode->setAttribute( 'size', '0');
  
  _lookup4ContentNode($dumpXmlNode, $contentListNode);
  
  return $contentListNode;
}

sub _lookup4ContentNode {
  my ($node, $contentListNode) = @_;

  my @childNodes = $node->getChildren( undef );
  if ( @childNodes ) {
    foreach my $childNode ( @childNodes ) {
      if ( $childNode->getName() eq 'content' ) {
        _processContentNode($childNode, $contentListNode);
      }
      else {
        _lookup4ContentNode($childNode, $contentListNode);
      }
    }
  }
}

sub _processContentNode {
  my ($node, $contentListNode) = @_;

  my @cidNodes = $node->getChildren( 'cid' );
  foreach my $cidNode ( @cidNodes ) {
    my $path = $cidNode->getAttribute( 'path' );
    $path = '' unless ( defined ( $path ) );
    $path .= '/' if $path and substr( $path, -1, 1 ) ne '/';
    my @contentFileNodes = $cidNode->getChildren( 'content-file' );
    foreach my $contentFileNode ( @contentFileNodes ) {
      my $size = $contentFileNode->getAttribute( 'size' );
      my $filename = $contentFileNode->getText();
      
      my $fileNode = XmlNode->new( 'file' );
      $fileNode->setAttribute( 'size', $size || '0');
      $fileNode->setText( $path . $filename );
      
      $contentListNode->addChild( $fileNode );
      
      if ( defined ( $size ) ) {
        my $contentListSize = $contentListNode->getAttribute( 'size' ) || '0';
        my $contentListNewSize = int($contentListSize) + int($size);
        $contentListNode->setAttribute( 'size', $contentListNewSize );
      }
    }
  }
}


1;