# Copyright:: (c) Autotelik Media Ltd 2010
# Author ::   Tom Statter
# Date ::     Aug 2010
# License::   MIT ?
#
# Details::   Specific over-rides/additions to support Spree Products
#
require 'loader_base'
require 'csv_loader'
require 'excel_loader'
require 'image_loader'

module DataShift

  module Spree

    class ProductLoader < LoaderBase

      include DataShift::CsvLoading
      include DataShift::ExcelLoading
      include DataShift::ImageLoading

      def initialize(product = nil)
        super( Product, product, :instance_methods => true  )
        raise "Failed to create Product for loading" unless @load_object
      end


      # Based on filename call appropriate loading function
      # Currently supports :
      #   Excel/Open Office files saved as .xls
      #   CSV files
      def perform_load( file_name, options = {} )

        ext = File.extname(file_name)
          
        if(ext == '.xls')
          raise DataShift::BadRuby, "Please install and use JRuby for loading .xls files" unless(Guards::jruby?)
          perform_excel_load(file_name, options)
        elsif(ext == '.csv')
          perform_csv_load(file_name, options)
        else
          raise DataShift::UnsupportedFileType, "#{ext} files not supported - Try CSV or OpenOffice/Excel .xls"
        end
      end
      
      # Over ride base class process with some Spree::Product specifics
      #
      # What process a value string from a column, assigning value(s) to correct association on Product.
      # Method map represents a column from a file and it's correlated Product association.
      # Value string which may contain multiple values for a collection (has_many) association.
      #
      def process()

        # Special cases for Products, generally where a simple one stage lookup won't suffice
        # otherwise simply use default processing from base class
        if((@current_method_detail.operator?('variants') || @current_method_detail.operator?('option_types')) && current_value)

          add_options

        elsif(@current_method_detail.operator?('taxons') && current_value)

          add_taxons

        elsif(@current_method_detail.operator?('product_properties') && current_value)

          add_properties

        elsif(@current_method_detail.operator?('images') && current_value)

          add_images
          
        elsif(@current_method_detail.operator?('count_on_hand') || @current_method_detail.operator?('on_hand') )

          # Unless we can save here, in danger of count_on_hand getting wiped out.
          # If we set (on_hand or count_on_hand) on an unsaved object, during next subsequent save
          # looks like some validation code or something calls Variant.on_hand= with 0
          # If we save first, then our values seem to stick

          # TODO smart column ordering to ensure always valid - if we always make it very last column might not get wiped ?
          # 
          save_if_new
         
          # Spree has some stock management stuff going on, so dont usually assign to column vut use
          # on_hand and on_hand=
          if(@load_object.variants.size > 0 && current_value.include?(LoaderBase::multi_assoc_delim))

            #puts "DEBUG: COUNT_ON_HAND PER VARIANT",current_value.is_a?(String),
              #&& current_value.is_a?(String) && current_value.include?(LoaderBase::multi_assoc_delim))
            # Check if we processed Option Types and assign count per option
            values = current_value.to_s.split(LoaderBase::multi_assoc_delim)

            if(@load_object.variants.size == values.size)
              @load_object.variants.each_with_index {|v, i| v.on_hand = values[i]; v.save; }
            else
              puts "WARNING: Count on hand entries did not match number of Variants - None Set"
            end
          else
            #puts "DEBUG: COUNT_ON_HAND #{current_value.to_i}"
            load_object.on_hand = current_value.to_i
          end

        else
          super
        end
      end

      private

      # Special case for OptionTypes as it's two stage process
      # First add the possible option_types to Product, then we are able
      # to define Variants on those options values.
      #
      def add_options
        # TODO smart column ordering to ensure always valid by time we get to associations
        save_if_new

        option_types = current_value.split( LoaderBase::multi_assoc_delim )

        option_types.each do |ostr|
          oname, value_str = ostr.split(LoaderBase::name_value_delim)

          option_type = OptionType.find_by_name(oname)

          unless option_type
            option_type = OptionType.create( :name => oname, :presentation => oname.humanize)
            # TODO - dynamic creation should be an option

            unless option_type
              puts "WARNING: OptionType #{oname} NOT found - Not set Product"
              next
            end
          end

          @load_object.option_types << option_type unless @load_object.option_types.include?(option_type)

          # Can be simply list of OptionTypes, some or all without values
          next unless(value_str)

          # Now get the value(s) for the option e.g red,blue,green for OptType 'colour'
          ovalues = value_str.split(',')

          ovalues.each_with_index do |ovname, i|
            ovname.strip!
            ov = OptionValue.find_or_create_by_name(ovname)
            if ov
              object = Variant.create( :product => @load_object, :sku => "#{@load_object.sku}_#{i}", :price => @load_object.price, :available_on => @load_object.available_on)
              #puts "DEBUG: Create New Variant: #{object.inspect}"
              object.option_values << ov
              #@load_object.variants << object
            else
              puts "WARNING: Option #{ovname} NOT FOUND - No Variant created"
            end
          end
        end

      end

    
      # Special case for Images
      # A list of paths to Images with a optional 'alt' value - supplied in form :
      #   path:alt|path2:alt2|path_3:alt3 etc
      #
      def add_images
        # TODO smart column ordering to ensure always valid by time we get to associations
        save_if_new

        images = current_value.split(LoaderBase::multi_assoc_delim)

        images.each do |image|
          
          img_path, alt_text = image.split(LoaderBase::name_value_delim)
          
          image = create_image(img_path, @load_object, :alt => alt_text)
        end
      
      end

      
      # Special case for ProductProperties since it can have additional value applied.
      # A list of Properties with a optional Value - supplied in form :
      #   property.name:value|property.name|property.name:value
      #
      def add_properties
        # TODO smart column ordering to ensure always valid by time we get to associations
        save_if_new

        property_list = current_value.split(LoaderBase::multi_assoc_delim)

        property_list.each do |pstr|
          pname, pvalue = pstr.split(LoaderBase::name_value_delim)
          property = Property.find_by_name(pname)

          unless property
            property = Property.create( :name => pname, :presentation => pname.humanize)
          end

          if(property)
            @load_object.product_properties << ProductProperty.create( :property => property, :value => pvalue)
          else
            puts "WARNING: Property #{pname} NOT found - Not set Product"
          end
         
        end
      
      end

      
      def add_taxons
        # TODO smart column ordering to ensure always valid by time we get to associations
        save_if_new

        name_list = current_value.split(LoaderBase::multi_assoc_delim)

        taxons = name_list.collect do |t|

          taxon = Taxon.find_by_name(t)

          unless taxon
            parent = Taxonomy.find_by_name(t)

            begin
              if(parent)
                # not sure this can happen but just incase we get a weird situation where we have
                # a taxonomy without a root named the same - create the child taxon we require
                taxon = Taxon.create(:name => t, :taxonomy_id => parent.id)
              else
                parent = Taxonomy.create!( :name => t )

                taxon = parent.root
              end

            rescue => e
              e.backtrace
              e.inspect
              puts "ERROR : Cannot assign Taxon ['#{t}'] to Product ['#{load_object.name}']"
              next
            end
          end
          taxon
        end

        taxons.compact!

        @load_object.taxons << taxons unless(taxons.empty?)

      end

    end
  end
end