/**
*	UInternationalizationExportCommandlet
*/
bool UInternationalizationExportCommandlet::DoExport( const FString& SourcePath, const FString& DestinationPath, const FString& Filename )
{
	// Get native culture.
	FString NativeCultureName;
	if( !GetStringFromConfig( *SectionName, TEXT("NativeCulture"), NativeCultureName, ConfigPath ) )
	{
		UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("No native culture specified.") );
		return false;
	}

	// Get manifest name.
	FString ManifestName;
	if( !GetStringFromConfig( *SectionName, TEXT("ManifestName"), ManifestName, ConfigPath ) )
	{
		UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("No manifest name specified.") );
		return false;
	}

	// Get archive name.
	FString ArchiveName;
	if( !( GetStringFromConfig(* SectionName, TEXT("ArchiveName"), ArchiveName, ConfigPath ) ) )
	{
		UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("No archive name specified."));
		return false;
	}

	// Get culture directory setting, default to true if not specified (used to allow picking of export directory with windows file dialog from Translation Editor)
	bool bUseCultureDirectory = true;
	if (!(GetBoolFromConfig(*SectionName, TEXT("bUseCultureDirectory"), bUseCultureDirectory, ConfigPath)))
	{
		bUseCultureDirectory = true;
	}

	bool ShouldAddSourceLocationsAsComments = true;
	GetBoolFromConfig(*SectionName, TEXT("ShouldAddSourceLocationsAsComments"), ShouldAddSourceLocationsAsComments, ConfigPath);

	TSharedRef< FInternationalizationManifest > InternationalizationManifest = MakeShareable( new FInternationalizationManifest );
	// Load the manifest info
	{
		FString ManifestFilePath = SourcePath / ManifestName;
		if( !FPaths::FileExists(ManifestFilePath) )
		{
			UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("Could not find manifest file %s."), *ManifestFilePath);
			return false;
		}

		TSharedPtr<FJsonObject> ManifestJsonObject = ReadJSONTextFile( ManifestFilePath );

		if( !ManifestJsonObject.IsValid() )
		{
			UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("Could not read manifest file %s."), *ManifestFilePath);
			return false;
		}

		FJsonInternationalizationManifestSerializer ManifestSerializer;
		ManifestSerializer.DeserializeManifest( ManifestJsonObject.ToSharedRef(), InternationalizationManifest );
	}

	TArray< TSharedPtr<FInternationalizationArchive> > NativeArchives;
	{
		const FString NativeCulturePath = SourcePath / *(NativeCultureName);
		TArray<FString> NativeArchiveFileNames;
		IFileManager::Get().FindFiles(NativeArchiveFileNames, *(NativeCulturePath / TEXT("*.archive")), true, false);

		for (const FString& NativeArchiveFileName : NativeArchiveFileNames)
		{
			// Read each archive file from the culture-named directory in the source path.
			FString ArchiveFilePath = NativeCulturePath / NativeArchiveFileName;
			ArchiveFilePath = FPaths::ConvertRelativePathToFull(ArchiveFilePath);
			TSharedRef<FInternationalizationArchive> InternationalizationArchive = MakeShareable(new FInternationalizationArchive);
			TSharedPtr< FJsonObject > ArchiveJsonObject = ReadJSONTextFile( ArchiveFilePath );
			FJsonInternationalizationArchiveSerializer ArchiveSerializer;
			ArchiveSerializer.DeserializeArchive( ArchiveJsonObject.ToSharedRef(), InternationalizationArchive );

			NativeArchives.Add(InternationalizationArchive);
		}
	}

	// Process the desired cultures
	for(int32 Culture = 0; Culture < CulturesToGenerate.Num(); Culture++)
	{
		// Load the archive
		const FString CultureName = CulturesToGenerate[Culture];
		const FString CulturePath = SourcePath / CultureName;
		FString ArchiveFileName = CulturePath / ArchiveName;
		TSharedPtr< FJsonObject > ArchiveJsonObject = NULL;

		if( FPaths::FileExists(ArchiveFileName) )
		{
			ArchiveJsonObject = ReadJSONTextFile( ArchiveFileName );

			FJsonInternationalizationArchiveSerializer ArchiveSerializer;
			TSharedRef< FInternationalizationArchive > InternationalizationArchive = MakeShareable( new FInternationalizationArchive );
			ArchiveSerializer.DeserializeArchive( ArchiveJsonObject.ToSharedRef(), InternationalizationArchive );

			{
				FPortableObjectFormatDOM NewPortableObject;

				FString LocLang;
				if( !NewPortableObject.SetLanguage( CultureName ) )
				{
					UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Skipping export of loc language %s because it is not recognized."), *LocLang );
					continue;
				}

				NewPortableObject.SetProjectName( FPaths::GetBaseFilename( ManifestName ) );
				NewPortableObject.CreateNewHeader();

				{
					for(TManifestEntryBySourceTextContainer::TConstIterator ManifestIter = InternationalizationManifest->GetEntriesBySourceTextIterator(); ManifestIter; ++ManifestIter)
					{
						// Gather relevant info from manifest entry.
						const TSharedRef<FManifestEntry>& ManifestEntry = ManifestIter.Value();
						const FString& Namespace = ManifestEntry->Namespace;
						const FLocItem& Source = ManifestEntry->Source;

						// For each context, we may need to create a different or even multiple PO entries.
						for( auto ContextIter = ManifestEntry->Contexts.CreateConstIterator(); ContextIter; ++ContextIter )
						{
							const FContext& Context = *ContextIter;

							// Create the typical PO entry from the archive entry which matches the exact same namespace, source, and key metadata, if it exists.
							{
								const TSharedPtr<FArchiveEntry> ArchiveEntry = InternationalizationArchive->FindEntryBySource( Namespace, Source, Context.KeyMetadataObj );
								if( ArchiveEntry.IsValid() )
								{
									const FString ConditionedArchiveSource = ConditionArchiveStrForPo(ArchiveEntry->Source.Text);
									const FString ConditionedArchiveTranslation = ConditionArchiveStrForPo(ArchiveEntry->Translation.Text);

									TSharedRef<FPortableObjectEntry> PoEntry = MakeShareable( new FPortableObjectEntry );
									//@TODO: We support additional metadata entries that can be translated.  How do those fit in the PO file format?  Ex: isMature
									PoEntry->MsgId = ConditionedArchiveSource;
									PoEntry->MsgCtxt = ConditionIdentityForPOMsgCtxt(Namespace, Context.Key, Context.KeyMetadataObj);
									PoEntry->MsgStr.Add( ConditionedArchiveTranslation );

									const FString PORefString = ConvertSrcLocationToPORef( Context.SourceLocation );
									PoEntry->AddReference(PORefString); // Source location.

									PoEntry->AddExtractedComment( GetConditionedKeyForExtractedComment(Context.Key) ); // "Notes from Programmer" in the form of the Key.

									if (ShouldAddSourceLocationsAsComments)
									{
										PoEntry->AddExtractedComment(GetConditionedReferenceForExtractedComment(PORefString)); // "Notes from Programmer" in the form of the Source Location, since this comes in handy too and OneSky doesn't properly show references, only comments.
									}

									TArray<FString> InfoMetaDataStrings;
									if (Context.InfoMetadataObj.IsValid())
									{
										for (auto InfoMetaDataPair : Context.InfoMetadataObj->Values)
										{
											const FString KeyName = InfoMetaDataPair.Key;
											const TSharedPtr<FLocMetadataValue> Value = InfoMetaDataPair.Value;
											InfoMetaDataStrings.Add(GetConditionedInfoMetaDataForExtractedComment(KeyName, Value->AsString()));
										}
									}
									if (InfoMetaDataStrings.Num())
									{
										PoEntry->AddExtractedComments(InfoMetaDataStrings);
									}

									NewPortableObject.AddEntry( PoEntry );
								}
							}

							// If we're exporting for something other than the native culture, we'll need to create PO entries for archive entries based on the native archive's translation.
							if (CultureName != NativeCultureName)
							{
								TSharedPtr<FArchiveEntry> NativeArchiveEntry;
								// Find the native archive entry which matches the exact same namespace, source, and key metadata, if it exists.
								for (const auto& NativeArchive : NativeArchives)
								{
									const TSharedPtr<FArchiveEntry> PotentialNativeArchiveEntry = NativeArchive->FindEntryBySource( Namespace, Source, Context.KeyMetadataObj );
									if (PotentialNativeArchiveEntry.IsValid())
									{
										NativeArchiveEntry = PotentialNativeArchiveEntry;
										break;
									}
								}

								if (NativeArchiveEntry.IsValid())
								{
									// Only need to create this PO entry if the native archive entry's translation differs from its source, in which case we need to find the our translation of the native translation.
									if (!NativeArchiveEntry->Source.IsExactMatch(NativeArchiveEntry->Translation))
									{
										const TSharedPtr<FArchiveEntry> ArchiveEntry = InternationalizationArchive->FindEntryBySource( Namespace, NativeArchiveEntry->Translation, NativeArchiveEntry->KeyMetadataObj );
										if (ArchiveEntry.IsValid())
										{
											const FString ConditionedArchiveSource = ConditionArchiveStrForPo(ArchiveEntry->Source.Text);
											const FString ConditionedArchiveTranslation = ConditionArchiveStrForPo(ArchiveEntry->Translation.Text);

											TSharedRef<FPortableObjectEntry> PoEntry = MakeShareable( new FPortableObjectEntry );
											//@TODO: We support additional metadata entries that can be translated.  How do those fit in the PO file format?  Ex: isMature
											PoEntry->MsgId = ConditionedArchiveSource;
											PoEntry->MsgCtxt = ConditionIdentityForPOMsgCtxt(Namespace, Context.Key, Context.KeyMetadataObj);
											PoEntry->MsgStr.Add( ConditionedArchiveTranslation );

											const FString PORefString = ConvertSrcLocationToPORef( Context.SourceLocation );
											PoEntry->AddReference( PORefString ); // Source location.

											PoEntry->AddExtractedComment( FString::Printf(TEXT("Key:\t%s"), *Context.Key) ); // "Notes from Programmer" in the form of the Key.
											PoEntry->AddExtractedComment( FString::Printf(TEXT("SourceLocation:\t%s"), *PORefString) ); // "Notes from Programmer" in the form of the Source Location, since this comes in handy too and OneSky doesn't properly show references, only comments.
											TArray<FString> InfoMetaDataStrings;
											if (Context.InfoMetadataObj.IsValid())
											{
												for (auto InfoMetaDataPair : Context.InfoMetadataObj->Values)
												{
													const FString KeyName = InfoMetaDataPair.Key;
													const TSharedPtr<FLocMetadataValue> Value = InfoMetaDataPair.Value;
													InfoMetaDataStrings.Add(FString::Printf(TEXT("InfoMetaData:\t\"%s\" : \"%s\""), *KeyName, *Value->AsString()));
												}
											}
											if (InfoMetaDataStrings.Num())
											{
												PoEntry->AddExtractedComments(InfoMetaDataStrings);
											}

											NewPortableObject.AddEntry( PoEntry );
										}
									}
								}
							}
						}
					}
				}

				// Write out the Portable Object to .po file.
				{
					FString OutputFileName;
					if (bUseCultureDirectory)
					{
						OutputFileName = DestinationPath / CultureName / Filename;
					}
					else
					{
						OutputFileName = DestinationPath / Filename;
					}

					// Persist comments if requested.
					if (ShouldPersistComments)
					{
						// Preserve comments from the specified file now, if they haven't already been.
						if (!HasPreservedComments)
						{
							FPortableObjectFormatDOM ExistingPortableObject;
							const bool HasLoadedPOFile = LoadPOFile(OutputFileName, ExistingPortableObject);
							if (!HasLoadedPOFile)
							{
								return false;
							}

							PreserveExtractedCommentsForPersistence(ExistingPortableObject);
						}

						// Persist the comments into the new portable object we're going to be saving.
						for (const auto& Pair : POEntryToCommentMap)
						{
							const TSharedPtr<FPortableObjectEntry> FoundEntry = NewPortableObject.FindEntry(Pair.Key.MsgId, Pair.Key.MsgIdPlural, Pair.Key.MsgCtxt);
							if (FoundEntry.IsValid())
							{
								FoundEntry->AddExtractedComments(Pair.Value);
							}
						}
					}

					NewPortableObject.SortEntries();

					if( SourceControlInfo.IsValid() )
					{
						FText SCCErrorText;
						if (!SourceControlInfo->CheckOutFile(OutputFileName, SCCErrorText))
						{
							UE_LOG(LogInternationalizationExportCommandlet, Error, TEXT("Check out of file %s failed: %s"), *OutputFileName, *SCCErrorText.ToString());
							return false;
						}
					}

					//@TODO We force UTF8 at the moment but we want this to be based on the format found in the header info.
					const FString OutputString = NewPortableObject.ToString();
					if (!FFileHelper::SaveStringToFile(OutputString, *OutputFileName, FFileHelper::EEncodingOptions::ForceUTF8))
					{
						UE_LOG( LogInternationalizationExportCommandlet, Error, TEXT("Could not write file %s"), *OutputFileName );
						return false;
					}
				}
			}
		}
	}
	return true;
}