# Copyright 1999-2012. 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';

sub getBackupFileName {
  return $DUMP_FILE_NAME;
}

#
# make dump in 'shallow' mode. Corresponds to old "-da -nc" mode
#
sub makeShallowDump {
  my $workDir = shift;

  Agent::setWorkDir($workDir);

  my $migrationDumpNode = createMigrationDumpNode();

  # dump resellers
  my @resellerIds = Agent::getResellers();
  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 );
    }
    $migrationDumpNode->addChild( $resellerNode );
  }

  # dump clients
  my @clientIds = Agent::getClients( undef );
  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 );
    }
    $migrationDumpNode->addChild( $clientNode );
  }

  # dump domains
  my @domainIds = Agent::getDomains( undef );
  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 );
    $migrationDumpNode->addChild( $domainNode );
  }

  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 $adminNode = XmlNode->new( 'admin' );
  $adminNode->setAttribute( 'guid', '' );

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

    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', '' );
        $resClientNode->addChild( getPreferencesNode($resClientId) );
		$resClientNode->addChild( getPropertiesNode($resClientId) );

        my @clDomainIds = Agent::getDomains($resClientId);
        if ( @clDomainIds ) {
          my $clDomainsNode = XmlNode->new( 'domains' );
          foreach my $clDomainId ( @clDomainIds ) {
            my $clDomainNode = makeDomainNode( $clDomainId, $configOnly, $onlyMail, $onlyHosting, $gzip);
            $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 = makeDomainNode( $resDomainId, $configOnly, $onlyMail, $onlyHosting, $gzip);
        $resDomainsNode->addChild( $resDomainNode );
      }
      $resellerNode->addChild( $resDomainsNode );
    }
    $resellersNode->addChild( $resellerNode );
  }
  $adminNode->addChild( $resellersNode );

  # dump clients
  my $clientsNode = XmlNode->new( 'clients' );
  my @clientIds = Agent::getClients( undef );
  foreach my $clientId ( @clientIds ) {
    my $clientNode = XmlNode->new( 'client' );
    $clientNode->setAttribute( 'name', $clientId );
    $clientNode->setAttribute( 'guid', '' );
    $clientNode->addChild( getPreferencesNode($clientId) );
    $clientNode->addChild( getPropertiesNode($clientId) );

    my @clDomainIds = Agent::getDomains($clientId);
    if ( @clDomainIds ) {
      my $clDomainsNode = XmlNode->new( 'domains' );
      foreach my $clDomainId ( @clDomainIds ) {
        my $clDomainNode = makeDomainNode( $clDomainId, $configOnly, $onlyMail, $onlyHosting, $gzip);
        $clDomainsNode->addChild( $clDomainNode );
      }
      $clientNode->addChild( $clDomainsNode );
    }
    $clientsNode->addChild( $clientNode );
  }
  $adminNode->addChild( $clientsNode );

  # dump domains
  my $domainsNode = XmlNode->new( 'domains' );
  my @domainIds = Agent::getDomains( undef );
  foreach my $domainId ( @domainIds ) {
    my $domainNode = makeDomainNode( $domainId, $configOnly, $onlyMail, $onlyHosting, $gzip);
    $domainsNode->addChild( $domainNode );
  }
  $adminNode->addChild( $domainsNode );

  my $migrationDumpNode = createMigrationDumpNode($configOnly);
  $migrationDumpNode->addChild( $adminNode );

  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 getPreferencesNode {
  my $accountId = shift;
  my $preferencesNode = XmlNode->new( 'preferences' );
  my $pinfoNode = XmlNode->new( 'pinfo' );
  $pinfoNode->setAttribute( 'name', 'email' );
  $pinfoNode->setText( Agent::getAccountEmail($accountId) );
  $preferencesNode->addChild( $pinfoNode );
  return $preferencesNode;
}

sub getPropertiesNode {
  my $accountId = shift;
  my $status = Agent::getAccountSuspensionStatus($accountId);
  my $statusStateNode =  XmlNode->new( $status );
  if ($status eq 'disabled') {
    $statusStateNode = XmlNode->new( 'disabled-by', 'attributes' => { 'name' => 'admin' });
  }
  my $statusNode = XmlNode->new( 'status' );
  $statusNode->addChild($statusStateNode);
  my $propertiesNode = XmlNode->new( 'properties' );
  my $passwordNode = XmlNode->new( 'password' );
  $passwordNode->setAttribute( 'type', 'plain' ); 
  $passwordNode->setText('');
  $propertiesNode->addChild($passwordNode);
  $propertiesNode->addChild($statusNode);
  return $propertiesNode;
}

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 @rootClientIds = Agent::getClients( undef );
  foreach my $rootClientId ( @rootClientIds ) {
    my @rtDomainIds = Agent::getDomains( $rootClientId );
    if ( @rtDomainIds ) {
      push (@domainIds, @rtDomainIds);
    }
  }
  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 @coldfusionDsnNodes = $domainNode->getChildren( 'coldfusion-dsn' );
    if ( @coldfusionDsnNodes ) {
      foreach my $coldfusionDsnNode ( @coldfusionDsnNodes ) {
        $onlyHostingDomain->addChild( $coldfusionDsnNode );
      }
    }

    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 );
      }
    }
  }
}

sub _getDefaultResellerPropertiesNode {
  my $resellerProperties = XmlNode->new('properties');
  my $passwordNode = XmlNode->new( 'password' );
  $passwordNode->setAttribute( 'type', 'plain' ); 
  $passwordNode->setText('');
  $resellerProperties->addChild($passwordNode);
  
  my $status = XmlNode->new('status');
  $status->addChild(XmlNode->new( 'enabled' ));
  $resellerProperties->addChild($status);
  return $resellerProperties;
}

1;
