bool hkvTextureTransformationRule::executeTransformation(const hkvTransformationInput& input, hkvTransformationOutput& output) const
{
  Context context(input, output);

  bool ok = !context.m_canceled && (transferSourceProperties(context) == HK_SUCCESS);
  ok = ok && !context.m_canceled && (examineSourceFile(context) == HK_SUCCESS);
  ok = ok && !context.m_canceled && (determineOutputs(context) == HK_SUCCESS);
  ok = ok && !context.m_canceled && (makeIntermediateBaseName(context) == HK_SUCCESS);
  ok = ok && !context.m_canceled && (copySourceFileToTemp(context) == HK_SUCCESS);

  int numVariants = context.m_outputVariants.getSize();
  for (int variantIdx = 0; ok && (variantIdx < numVariants); ++variantIdx)
  {
    const hkvTextureVariant& variant = context.m_outputVariants[variantIdx];
    ok = ok && !context.m_canceled && (prepareTransformationSettings(context, variant) == HK_SUCCESS);

    if (ok && hkvFileHelper::fileExists(context.m_targetFile))
    {
      continue;
    }

    ok = ok && !context.m_canceled && (runConversion(context) == HK_SUCCESS);
    ok = ok && !context.m_canceled && (moveTempFileToTarget(context) == HK_SUCCESS);

    if (ok)
    {
      notifyOutputWritten(context, variant.m_variantKey.cString());
    }
  }

  cleanUpTempFiles(context);

  if (ok)
  {
    context.m_output.m_messages.pushBack(hkvAssetLogMessage(
      HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_INFO, "Texture transformed successfully."));
  }
  else if (context.m_canceled)
  {
    deleteTargetFile(context);
    context.m_output.m_messages.pushBack(hkvAssetLogMessage(
      HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_WARNING, "Transformation of texture was canceled."));
  }
  else
  {
    context.m_output.m_messages.pushBack(hkvAssetLogMessage(
      HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_ERROR, "Transformation of texture failed!"));
  }

  return ok;
}
bool hkvTextureTransformationSettings::checkFormatCompatibility(
  hkArray<hkvAssetLogMessage>& out_messages)
{
  // Check if the file and data formats are compatible
  if (!hkvTextureFileToDataFormatMapping::getInstance().isMapped(m_targetFileFormat, m_targetDataFormat))
  {
    hkStringBuf msg;
    msg.printf("The target file format (%s) is not compatible with the target data format (%s)",
      hkvTextureFileFormatNames[m_targetFileFormat], hkvTextureDataFormatNames[m_targetDataFormat]);
    out_messages.pushBack(hkvAssetLogMessage(HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_ERROR, msg));
  }

  // Check if the platform can handle the target data format
  if (m_platform != HKV_TARGET_PLATFORM_ANY)
  {
    if (!hkvPlatformToTextureDataFormatMapping::getInstance().isMapped(m_platform, m_targetDataFormat))
    {
      const char* platformName = "";
      hkvGetTargetPlatformDefinition().idToString(m_platform, platformName);
      
      hkStringBuf msg;
      msg.printf("The target data format (%s) is not compatible with the target platform (%s)",
        hkvTextureDataFormatNames[m_targetDataFormat], platformName);
      out_messages.pushBack(hkvAssetLogMessage(HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_ERROR, msg));
    }
  }

  // Check if the platform can handle the target file format
  if (m_platform != HKV_TARGET_PLATFORM_ANY)
  {
    if (!hkvPlatformToTextureFileFormatMapping::getInstance().isMapped(m_platform, m_targetFileFormat))
    {
      const char* platformName = "";
      hkvGetTargetPlatformDefinition().idToString(m_platform, platformName);

      hkStringBuf msg;
      msg.printf("The target file format (%s) is not compatible with the target platform (%s)",
        hkvTextureFileFormatNames[m_targetFileFormat], platformName);
      out_messages.pushBack(hkvAssetLogMessage(HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_ERROR, msg));
    }
  }

  return true;
}
bool hkvTextureTransformationSettings::ensureDimensionIntegrity(hkArray<hkvAssetLogMessage>& out_messages)
{
  if (m_sourceWidth == 0 || m_sourceHeight == 0)
  {
    out_messages.pushBack(hkvAssetLogMessage(HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_ERROR, "Size of source texture is invalid; cannot continue!"));
    return false;
  }

  // Perform sanity checks
  if (m_validationNeedsPowerOfTwo && m_validationNeedsMultipleOf > 1)
  {
    if (hkvMath::isPowerOf2(m_validationNeedsMultipleOf))
    {
      // Both Power-of-Two and Multiple-of restrictions are given. However, multiple-of is a power of two,
      // so we can drop the multiple-of constraint as long as we limit the minimum size.
      m_validationMinSize = hkvMath::Max(m_validationMinSize, m_validationNeedsMultipleOf);
    }
    else
    {
      VASSERT_MSG(false, "Both Power-of-Two and Multiple-of restrictions are specified, but Multiple-of is not a power of two. Ignoring Multiple-of.");
    }
    m_validationNeedsMultipleOf = 1;
  }

  if (m_validationNeedsPowerOfTwo)
  {
    if (m_validationMinSize > 0 && !hkvMath::isPowerOf2(m_validationMinSize))
    {
      VASSERT_MSG(false, "According to set restrictions, system minimum size needs to be Power-of-Two. Adjusting.");
      m_validationMinSize = hkvMath::powerOf2_ceil(m_validationMinSize);
    }

    if (m_userMinSize > 0)
    {
      m_userMinSize = adjustToNearestPowerOfTwo(m_userMinSize);
    }

    if (m_validationMaxSize > 0 && !hkvMath::isPowerOf2(m_validationMaxSize))
    {
      VASSERT_MSG(false, "According to set restrictions, system maximum size needs to be Power-of-Two. Adjusting.");
      m_validationMaxSize = hkvMath::powerOf2_floor(m_validationMaxSize);
    }

    if (m_userMaxSize > 0)
    {
      m_userMaxSize = adjustToNearestPowerOfTwo(m_userMaxSize);
    }
  }

  if (m_validationNeedsMultipleOf > 1)
  {
    if ((m_validationMinSize % m_validationNeedsMultipleOf) != 0)
    {
      VASSERT_MSG(false, "According to set restrictions, system minimum size needs to be Multiple-of. Adjusting.");
      m_validationMinSize = ((m_validationMinSize + m_validationNeedsMultipleOf - 1) / m_validationNeedsMultipleOf) * m_validationNeedsMultipleOf;
    }

    m_userMinSize = ((m_userMinSize + m_validationNeedsMultipleOf - 1) / m_validationNeedsMultipleOf) * m_validationNeedsMultipleOf;

    if (m_validationMaxSize % m_validationNeedsMultipleOf != 0)
    {
      VASSERT_MSG(false, "According to set restrictions, system maximum size needs to be Multiple-of. Adjusting.");
      m_validationMaxSize = (m_validationMaxSize / m_validationNeedsMultipleOf) * m_validationNeedsMultipleOf;
    }

    m_userMaxSize = (m_userMaxSize / m_validationNeedsMultipleOf) * m_validationNeedsMultipleOf;
  }

  if ((m_validationMinSize > 0) && (m_validationMaxSize > 0) && (m_validationMaxSize < m_validationMinSize))
  {
    VASSERT_MSG(false, "System maximum size less than system minimum size; adjusting.");
    m_validationMinSize = m_validationMaxSize;
  }

  if ((m_userMinSize > 0) && (m_userMaxSize > 0) && (m_userMaxSize < m_userMinSize))
  {
    m_userMinSize = m_userMaxSize;
  }

  if ((m_validationMinSize > 0) && (m_userMinSize > 0) && (m_userMinSize < m_validationMinSize))
  {
    m_userMinSize = m_validationMinSize;
  }

  if ((m_validationMaxSize > 0) && (m_userMaxSize > 0) && (m_userMaxSize > m_validationMaxSize))
  {
    m_userMaxSize = m_validationMaxSize;
  }

  return true;
}
hkResult hkvTextureTransformationRule::runConversion(Context& context) const
{
  context.m_stepSourceFile = context.m_tempOriginalFile;

  int numSteps = context.m_transformationSteps.getSize();
  for (int stepIdx = 0; stepIdx < numSteps; ++stepIdx)
  {
    TransformationStepInfo& stepInfo = context.m_transformationSteps[stepIdx];

    // Check if this step's transformation settings are valid. If not, try to validate them.
    hkvTextureTransformationSettings& settings = stepInfo.getSettings();
    if (!settings.isValid() && (settings.validate(context.m_output.m_messages) != HK_SUCCESS))
    {
      context.m_output.m_messages.pushBack(hkvAssetLogMessage(
        HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_ERROR, "Failed to validate transformation settings of a transformation step!"));
      return HK_FAILURE;
    }

    // If this is not the first step, take the output of the previous step as the
    // input of this step
    if (stepIdx > 0)
    {
      context.m_stepSourceFile = context.m_stepTargetFile;
    }

    // The new output file name is a generated base name with the step-specific extension
    hkStringBuf nameBuf;
    context.m_stepTargetFile = makeIntermediateFileName(context, stepInfo.getTargetExtension(), nameBuf);

    hkRefPtr<hkvFileTransformationStep> step;
    switch (stepInfo.getStepType())
    {
    case STEP_TYPE_WIIU_TEXCONV2:
      {
        step = hkRefNew<hkvFileTransformationStep>(
          new hkvExternalToolWiiUTexConv2(stepInfo.getSettings(), context.m_stepSourceFile, context.m_stepTargetFile));
        break;
      }
    case STEP_TYPE_IMAGE_TO_DDS:
      {
        step = hkRefPtr<hkvFileTransformationStep>(
          new hkvTransformationStepImageToDds(stepInfo.getSettings(), context.m_stepSourceFile, context.m_stepTargetFile));
        break;
      }
    case STEP_TYPE_PVRTEXTOOL:
      {
        step = hkRefNew<hkvFileTransformationStep>(
          new hkvExternalToolPvrTexTool(stepInfo.getSettings(), context.m_stepSourceFile, context.m_stepTargetFile));
        break;
      }
    case STEP_TYPE_TEXCONV:
      {
        step = hkRefNew<hkvFileTransformationStep>(
          new hkvExternalToolTexConv(stepInfo.getSettings(), context.m_stepSourceFile, context.m_stepTargetFile, false));
        break;
      }
    case STEP_TYPE_TEXCONV_FORCE_DXT10:
      {
        step = hkRefNew<hkvFileTransformationStep>(
          new hkvExternalToolTexConv(stepInfo.getSettings(), context.m_stepSourceFile, context.m_stepTargetFile, true));
        break;
      }
    default:
      {
        VASSERT_MSG(FALSE, "Missing tool case!");
        return HK_FAILURE;
      }
    }

    // Some tools want to create output files with a name of their choosing. Now that we
    // know the concrete step to execute, query it for its output file name.
    context.m_stepTargetFile = step->getTargetFile();
    context.m_tempFileNames.pushBack(context.m_stepTargetFile);

    {
      hkvCriticalSectionLock lock(s_protect);
      context.m_currentTransformationStep = step;
    }

    hkResult stepResult = step->run();

    {
      hkvCriticalSectionLock lock(s_protect);
      context.m_currentTransformationStep = NULL;
    }

    context.m_output.m_messages.append(step->getMessages());

    if (stepResult != HK_SUCCESS)
    {
      if (context.m_canceled)
      {
        context.m_output.m_messages.pushBack(hkvAssetLogMessage(
          HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_WARNING, "Transformation step was canceled."));
      }
      else
      {
        context.m_output.m_messages.pushBack(hkvAssetLogMessage(
          HKV_MESSAGE_CATEGORY_ASSET_TRANSFORMATION, HKV_MESSAGE_SEVERITY_ERROR, "Transformation step failed!"));
      }
      return HK_FAILURE;
    }
  }

  return HK_SUCCESS;
}