       @_loadManufacturersXML @_options.manufacturers
       @_loadCategoriesXML @_options.categories
       @_fetchProductTypes()
+      @_fetchCategories()
       ],
-      (mappingData, productsData, manufacturersData, categoriesData, productTypesData) =>
-        @logger.debug "mappingData:\n#{mappingData}"
-        @logger.debug "productsData:\n#{utils.pretty productsData}"
-        @logger.debug "productTypesData:\n#{utils.pretty productTypesData}"
-        @mapping = JSON.parse mappingData
-        @toBeImported = _.size(productsData.Products?.Product)
-        productType = @_getProductType productTypesData
-        manufacturers = @_processManufacturers(manufacturersData, productType) if manufacturersData
-        @_updateProductType(productType, manufacturers) if manufacturers
-        @_updateCategories categoriesData if categoriesData
-        products = @_buildProductsData(productsData, productType)
-        @logger.info "[Products] About to create '#{_.size products}' products."
-        @_batch _.map(products, (p) => @_createProduct p), 100
+      (mapping, productsXML, manufacturersXML, categoriesXML, fetchedProductTypes, fetchedCategories) =>
+        @logger.debug "mapping:\n#{mapping}"
+        @logger.debug "productsXML:\n#{utils.pretty productsXML}"
+        @logger.debug "fetchedProductTypes:\n#{utils.pretty fetchedProductTypes}"
+        @mapping = JSON.parse mapping
+        @productsXML = productsXML
+        @categoriesXML = categoriesXML
+        @fetchedCategories = @_transformByCategoryExternalId fetchedCategories.results
+        @toBeImported = _.size(productsXML.Products?.Product)
+        @productType = @_getProductType fetchedProductTypes
+        manufacturers = @_buildManufacturers(manufacturersXML, @productType) if manufacturersXML
+        @_updateProductType(@productType, manufacturers) if manufacturers
+    .then (productTypeUpdateResult) =>
+      @categories = @_buildCategories(@categoriesXML) if @categoriesXML
+      creates = @_buildCategoryCreates(@categories, @fetchedCategories) if @categories
+      @_batch(_.map(creates, (c) => @_createCategory c), 100) if creates
+    .then (createCategoriesResult) =>
+      # fetch created categories to get id's used for parent reference creation
+      @_fetchCategories()
+    .then (fetchedCategories) =>
+      @fetchedCategories = @_transformByCategoryExternalId fetchedCategories.result
+      updates = @_buildCategoryUpdates(@categories, @fetchedCategories) if @categories
+      @_batch(_.map(updates, (c) => @_updateCategory c), 100) if updates
+    .then (updateCategoriesResult) =>
+      @logger.info '[Products] Products import started...'
+      products = @_buildProducts(@productsXML, @productType, @fetchedCategories)
+      @logger.info "[Products] About to create '#{_.size products}' products."
+      @_batch(_.map(products, (p) => @_createProduct p), 100) if products
     .fail (error) =>
       @logger.error "Error on execute method; #{error}"
       @logger.error "Error stack: #{error.stack}" if error.stack
       @_processResult(callback)
     .done (result) =>
       @_processResult(callback)
-
 
   _processResult: (callback) ->
     endTime = new Date().getTime()

          deferred.resolve message
    deferred.promise

  _batch: (list, numberOfParallelRequest) =>
    current = _.take list, numberOfParallelRequest
    Q.all(current)
    .then (result) =>
      if _.size(current) < numberOfParallelRequest
        @success = true
      else
        @_batch _.tail(list, numberOfParallelRequest), numberOfParallelRequest
    .fail (error) =>
      @logger.error "Error batch processing; #{error}"
      @logger.error "Error stack: #{error.stack}" if error.stack
      @success = false

  _buildProductsData: (data, productType) =>
    products = []
    _.each data.Products?.Product, (p) =>
      names = @_getLocalizedValue(p.Descriptions[0], 'Title')

        version: productType.version
        actions: actions
    else
      @logger.info "[Manufacturers] No update action for manufacturers attribute '#{attributeName}' required."

  _buildCategories: (data) ->
    @logger.info '[Categories] Categories import started...'
    @logger.debug "[Categories] data:\n#{utils.pretty data}"
    count = _.size(data.Categories?.Category)
    if count
      @logger.info "[Categories] Categories found: '#{count}'"
    else
      @logger.info '[Categories] No categories found or undefined. Please check manufacturers input XML.'
      return
    categoriesData = data.Categories.Category
    # get root categories only
    rootsData = _.filter categoriesData, (c) -> c.ParentId is undefined
    # get subcategories only
    subcategoriesData = _.reject categoriesData, (c) -> c.ParentId is undefined
    # create a list of categories with data prepared for category creation
    roots = @_getCategoryKeyValuePairs(rootsData)
    list = [roots]
    # recursively build categories data
    @_createSubcategories(roots, subcategoriesData, list)
    categories = _.flatten list
    @logger.debug "[Categories] Roots count: '#{_.size roots}'"
    @logger.info "[Categories] Import candidates count: '#{_.size categories}'"
    @logger.debug "[Categories] Import candidates data: '#{utils.pretty categories}'"

    categories

  # TODO: do not use slug as external category id (required Sphere support of custom attributes on category)
  _buildCategoryCreates: (data, fetchedCategories) =>
    creates = []
    _.each data, (c) ->
      exists = _.has(fetchedCategories, c.id)
      # we are interested in new categories only
      if not exists
        name = c.name
        slug = c.slug
        # set category external id (over slug as workaround)
        slug.nl = c.id
        create =
          name: name
          slug: slug
        creates.push create

    count = _.size(creates)
    if count > 0
      @logger.info "[Categories] Create count: '#{count}'"
      creates
    else
      @logger.info "[Categories] No category create required."
      null

  _buildCategoryUpdates: (data, fetchedCategories) ->
    updates = []
    _.each data, (c) =>
      actions = []
      newName = c.name
      oldCategory = @_getCategoryByExternalId(c.id, fetchedCategories)
      oldName = oldCategory.name

      # check if category name changed
      if not _.isEqual(newName, oldName)
        action =
          action: "changeName"
          name: newName
        actions.push action

      parentId = c.parentId
      parentCategory = @_getCategoryByExternalId(parentId, fetchedCategories) if parentId
      # check if category need to be assigned to parent
      if parentCategory
        action =
          action: "changeParent"
          parent:
            typeId: "category"
            id: parentCategory.id
        actions.push action

      if _.size(actions) > 0
        wrapper =
          id: oldCategory.id
          actions:
            version: oldCategory.version
            actions: actions
        updates.push wrapper

    count = _.size(updates)
    if count > 0
      @logger.info "[Categories] Update count: '#{count}'"
      updates
    else
      @logger.info "[Categories] No category update required."
      null

  _transformByCategoryExternalId: (fetchedCategories) =>
    fetchedCats = {}
    _.each fetchedCategories, (el) ->
      externalId = el.slug.nl
      throw new Error "[Categories] Slug for language 'nl' (workaround: used as 'externalId') in MC is empty; Category id: '#{el.id}'" if not externalId
      fetchedCats[externalId] = el
    fetchedCats

  _getCategoryByExternalId: (id, fetchedCats) ->
    category = fetchedCats[id]
    throw new Error "[Categories] Unexpected error. Parent category with externalId: '#{id}' not found." if not category
    category

  _createSubcategories: (categories, subcategoriesData, list) ->
    _.each categories, (category) =>
      categoryId = category.id
      subcategories = _.filter subcategoriesData, (c) -> c.ParentId[0] is categoryId
      if _.size(subcategories) > 0
        subs = @_getCategoryKeyValuePairs(subcategories, categoryId)
        list.push subs
        @_createSubcategories(subs, subcategoriesData, list)

  _getCategoryKeyValuePairs: (categories, parentId) ->
    container = []
    _.each categories, (c) =>
      names = @_getLocalizedValue(c.Translations[0], 'Name')
      slugs = @_getLocalizedSlugs(names)
      category =
        id: c.CategoryId[0]
        name: names
        slug: slugs
      category.parentId = parentId if parentId
      container.push category
    container

  _fetchCategories: ->
    deferred = Q.defer()
    @rest.GET '/categories', (error, response, body) ->
      if error
        deferred.reject error
      else
        deferred.resolve body
    deferred.promise

  _createCategory: (payload) ->
    deferred = Q.defer()
    @rest.POST '/categories', payload, (error, response, body) ->
      if error
        deferred.reject "HTTP error on new category creation; Error: #{error}; Request body: \n #{utils.pretty payload} \n\n Response body: '#{utils.pretty body}'"
      else
        if response.statusCode isnt 201
          message = "Error on new category creation; Request body: \n #{utils.pretty payload} \n\n Response body: '#{utils.pretty body}'"
          deferred.reject message
        else
          message = 'New category created.'
          deferred.resolve message
    deferred.promise

  _updateCategory: (payload) ->
    deferred = Q.defer()
    @rest.POST "/categories/#{payload.id}", payload.actions, (error, response, body) ->
      if error
        deferred.reject "HTTP error on category update; Error: #{error}; Request body: \n #{utils.pretty payload} \n\n Response body: '#{utils.pretty body}'"
      else
        if response.statusCode isnt 200
          message = "Error on category type update; Request body: \n #{utils.pretty payload} \n\n Response body: '#{utils.pretty body}'"
          deferred.reject message
        else
          message = 'Category type updated.'
          deferred.resolve message
    deferred.promise

  _fetchProductTypes: ->
    deferred = Q.defer()
