require 'date'

module AMEE
  module Profile
    class Category < AMEE::Profile::Object

      def initialize(data = {})
        @children = data ? data[:children] : []
        @items = data ? data[:items] : []
        @total_amount = data[:total_amount]
        @total_amount_unit = data[:total_amount_unit]
        @start_date = data[:start_date]
        @end_date = data[:end_date]
        @pager = data[:pager]
        super
      end

      attr_reader :children
      attr_reader :items
      attr_reader :total_amount
      attr_reader :total_amount_unit
      attr_reader :pager

      def start_date
        @start_date || profile_date
      end

      def end_date
        @end_date
      end

      def self.parse_json_profile_item(item)
        item_data = {}
        item_data[:values] = {}
        item.each_pair do |key, value|
          case key
            when 'dataItemLabel', 'dataItemUid', 'name', 'path', 'uid'
              item_data[key.to_sym] = value
            when 'dataItem'
              item_data[:dataItemLabel] = value['Label']
              item_data[:dataItemUid] = value['uid']
            when 'label' # ignore these
              nil
            when 'created'
              item_data[:created] = DateTime.parse(value)
            when 'modified'
              item_data[:modified] = DateTime.parse(value)
            when 'validFrom'
              item_data[:validFrom] = DateTime.strptime(value, "%Y%m%d")
            when 'startDate'
              item_data[:startDate] = DateTime.parse(value)
            when 'endDate'
              item_data[:endDate] = DateTime.parse(value) rescue nil
            when 'end'
              item_data[:end] = (value == "true")
            when 'amountPerMonth'
              item_data[:amountPerMonth] = value.to_f
            when 'amount'
              item_data[:amount] = value['value'].to_f
              item_data[:amount_unit] = value['unit']
            when 'amounts'
              # add new code per COM-69 here.
            when 'itemValues'
              value.each do |itemval|
                path = itemval['path'].to_sym
                item_data[:values][path.to_sym] = {}
                item_data[:values][path.to_sym][:name] = itemval['name']
                item_data[:values][path.to_sym][:value] = itemval['value']
                item_data[:values][path.to_sym][:unit] = itemval['unit']
                item_data[:values][path.to_sym][:per_unit] = itemval['perUnit']
              end
            else
              item_data[:values][key.to_sym] = value
          end
        end
        item_data[:path] ||= item_data[:uid] # Fill in path if not retrieved from response
        return item_data
      end

      def self.parse_json_profile_category(category)
        datacat = category['dataCategory'] ? category['dataCategory'] : category
        category_data = {}
        category_data[:name] = datacat['name']
        category_data[:path] = datacat['path']
        category_data[:uid] = datacat['uid']
        category_data[:totalAmountPerMonth] = category['totalAmountPerMonth'].to_f
        category_data[:children] = []
        category_data[:items] = []
        if category['children']
          category['children'].each do |child|
            if child[0] == 'dataCategories'
              child[1].each do |child_cat|
                category_data[:children] << parse_json_profile_category(child_cat)
              end
            end
            if child[0] == 'profileItems' && child[1]['rows']
              child[1]['rows'].each do |child_item|
                category_data[:items] << parse_json_profile_item(child_item)
              end
            end
          end
        end
        return category_data
      end

      def self.from_json(json, options)
        # Parse json
        doc = JSON.parse(json)
        data = {}
        data[:profile_uid] = doc['profile']['uid']
        data[:profile_date] = DateTime.strptime(doc['profileDate'], "%Y%m") rescue nil
        data[:name] = doc['dataCategory']['name']
        data[:path] = doc['path']
        data[:total_amount] = doc['totalAmountPerMonth']
        data[:total_amount_unit] = "kg/month"
        data[:pager] = AMEE::Pager.from_json(doc['children']['pager']) rescue nil
        data[:children] = []
        if doc['children'] && doc['children']['dataCategories']
          doc['children']['dataCategories'].each do |child|
            data[:children] << parse_json_profile_category(child)
          end
        end
        data[:items] = []
        profile_items = []
        profile_items.concat doc['children']['profileItems']['rows'] rescue nil
        profile_items << doc['profileItem'] unless doc['profileItem'].nil?
        profile_items.each do |item|
          data[:items] << parse_json_profile_item(item)
        end
        # Create object
        Category.new(data)
      rescue
        raise AMEE::BadData.new("Couldn't load ProfileCategory from JSON data. Check that your URL is correct.\n#{json}")
      end

      def self.from_v2_json(json, options)
        # Parse json
        doc = JSON.parse(json)
        data = {}
        data[:profile_uid] = doc['profile']['uid']
        data[:start_date] = options[:start_date]
        data[:end_date] = options[:end_date]
        data[:name] = doc['dataCategory']['name']
        data[:path] = doc['path']
        data[:total_amount] = doc['totalAmount']['value'].to_f rescue nil
        data[:total_amount_unit] = doc['totalAmount']['unit'] rescue nil
        data[:pager] = AMEE::Pager.from_json(doc['pager']) rescue nil
        data[:children] = []
        if doc['profileCategories']
          doc['profileCategories'].each do |child|
            data[:children] << parse_json_profile_category(child)
          end
        end
        data[:items] = []
        profile_items = []
        profile_items.concat doc['profileItems'] rescue nil
        profile_items << doc['profileItem'] unless doc['profileItem'].nil?
        profile_items.each do |item|
          data[:items] << parse_json_profile_item(item)
        end
        # Create object
        Category.new(data)
      rescue
        raise AMEE::BadData.new("Couldn't load ProfileCategory from V2 JSON data. Check that your URL is correct.\n#{json}")
      end
            
      def self.parse_xml_profile_item(item)
        item_data = {}
        item_data[:values] = {}
        item.elements.each do |element|
          key = element.name
          value = element.text
          case key.downcase
            when 'dataitemlabel', 'dataitemuid', 'name', 'path'
              item_data[key.to_sym] = value
            when 'dataitem'
              item_data[:dataItemUid] = element.attributes['uid']
            when 'validfrom'
              item_data[:validFrom] = DateTime.strptime(value, "%Y%m%d")
            when 'end'
              item_data[:end] = (value == "true")
            when 'amountpermonth'
              item_data[:amountPerMonth] = value.to_f
            else
              item_data[:values][key.to_sym] = value
          end
        end
        item_data[:uid] = item.attributes['uid'].to_s
        item_data[:created] = DateTime.parse(item.attributes['created'].to_s) rescue nil
        item_data[:modified] = DateTime.parse(item.attributes['modified'].to_s) rescue nil
        item_data[:path] ||= item_data[:uid] # Fill in path if not retrieved from response
        return item_data
      end

      def self.from_v2_batch_json(json)
        # Parse JSON
        doc = JSON.parse(json)
        data = {}
        data[:profileItems] = []
        doc['profileItems'].each do |child|
          profile_item = {}
          profile_item[:uri] = child['uri'].to_s
          profile_item[:uid] = child['uid'].to_s
          data[:profileItems] << profile_item
        end
        return data
      rescue
        raise AMEE::BadData.new("Couldn't load ProfileCategory batch response from V2 JSON data. Check that your URL is correct.\n#{json}")
      end
      
      def self.parse_xml_profile_category(category)
        category_data = {}
        category_data[:name] = category.elements['DataCategory'].elements['Name'].text
        category_data[:path] = category.elements['DataCategory'].elements['Path'].text
        category_data[:uid] = category.elements['DataCategory'].attributes['uid'].to_s
        category_data[:totalAmountPerMonth] = category.elements['TotalAmountPerMonth'].text.to_f rescue nil
        category_data[:children] = []
        category_data[:items] = []
        if category.elements['Children']
          category.elements['Children'].each do |child|
            if child.name == 'ProfileCategories'
              child.each do |child_cat|
                category_data[:children] << parse_xml_profile_category(child_cat)
              end
            end
            if child.name == 'ProfileItems'
              child.each do |child_item|
                category_data[:items] << parse_xml_profile_item(child_item)
              end
            end
          end
        end
        return category_data
      end

      def self.from_xml(xml, options)
        # Parse XML
        doc = REXML::Document.new(xml)
        data = {}
        data[:profile_uid] = REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/Profile/@uid").to_s
        data[:profile_date] = DateTime.strptime(REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/ProfileDate").text, "%Y%m")
        data[:name] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/DataCategory/?ame').text
        data[:path] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/Path | /Resources/ProfileCategoryResource/DataCategory/path').text || ""
        data[:path] = "/#{data[:path]}" if data[:path].slice(0,1) != '/'
        data[:total_amount] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmountPerMonth').text.to_f rescue nil
        data[:total_amount_unit] = "kg/month"
        data[:pager] = AMEE::Pager.from_xml(REXML::XPath.first(doc, '//Pager'))
        data[:children] = []
        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/DataCategory | /Resources/ProfileCategoryResource/Children/DataCategories/DataCategory') do |child|
          category_data = {}
          category_data[:name] = (child.elements['Name'] || child.elements['name']).text
          category_data[:path] = (child.elements['Path'] || child.elements['path']).text
          category_data[:uid] = child.attributes['uid'].to_s
          data[:children] << category_data
        end
        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/ProfileCategory') do |child|
          data[:children] << parse_xml_profile_category(child)
        end
        data[:items] = []
        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileItems/ProfileItem') do |item|
          data[:items] << parse_xml_profile_item(item)
        end
        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItem') do |item|
          data[:items] << parse_xml_profile_item(item)
        end
        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItems/ProfileItem') do |item|
          data[:items] << parse_xml_profile_item(item)
        end
        # Create object
        Category.new(data)
      rescue
        raise AMEE::BadData.new("Couldn't load ProfileCategory from XML data. Check that your URL is correct.\n#{xml}")
      end

      def self.parse_v2_xml_profile_item(item)
        item_data = {}
        item_data[:values] = {}
        item.elements.each do |element|
          key = element.name
          case key.downcase
            when 'name', 'path'
              item_data[key.downcase.to_sym] = element.text
            when 'dataitem'
              item_data[:dataItemLabel] = element.elements['Label'].text
              item_data[:dataItemUid] = element.attributes['uid'].to_s
            when 'validfrom'
              item_data[:validFrom] = DateTime.strptime(element.text, "%Y%m%d")
            when 'startdate'
              item_data[:startDate] = DateTime.parse(element.text)
            when 'enddate'
              item_data[:endDate] = DateTime.parse(element.text) if element.text
            when 'end'
              item_data[:end] = (element.text == "true")
            when 'amount'
              item_data[:amount] = element.text.to_f
              item_data[:amount_unit] = element.attributes['unit'].to_s
            when 'amounts'
              # add new code per COM-69 here.
            when 'itemvalues'
              element.elements.each do |itemvalue|
                path = itemvalue.elements['Path'].text
                item_data[:values][path.to_sym] = {}
                item_data[:values][path.to_sym][:name] = itemvalue.elements['Name'].text
                item_data[:values][path.to_sym][:value] = itemvalue.elements['Value'].text || "0"
                item_data[:values][path.to_sym][:unit] = itemvalue.elements['Unit'].text
                item_data[:values][path.to_sym][:per_unit] = itemvalue.elements['PerUnit'].text
              end
            else
              item_data[:values][key.to_sym] = element.text
          end
        end
        item_data[:uid] = item.attributes['uid'].to_s
        item_data[:created] = DateTime.parse(item.attributes['created'].to_s) rescue nil
        item_data[:modified] = DateTime.parse(item.attributes['modified'].to_s) rescue nil
        item_data[:path] ||= item_data[:uid] # Fill in path if not retrieved from response
        return item_data
      end

      def self.from_v2_xml(xml, options)
        # Parse XML
        doc = REXML::Document.new(xml)
        data = {}
        data[:profile_uid] = REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/Profile/@uid").to_s
        data[:start_date] = options[:start_date]
        data[:end_date] = options[:end_date]
        data[:name] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/DataCategory/Name').text
        data[:path] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/Path').text || ""
        data[:total_amount] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmount').text.to_f rescue nil
        data[:total_amount_unit] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmount/@unit').to_s rescue nil
        data[:pager] = AMEE::Pager.from_xml(REXML::XPath.first(doc, '//Pager'))
        data[:children] = []
        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileCategories/DataCategory') do |child|
          category_data = {}
          category_data[:name] = child.elements['Name'].text
          category_data[:path] = child.elements['Path'].text
          category_data[:uid] = child.attributes['uid'].to_s
          data[:children] << category_data
        end
        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/ProfileCategory') do |child|
          data[:children] << parse_xml_profile_category(child)
        end
        data[:items] = []
        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItems/ProfileItem') do |item|
          data[:items] << parse_v2_xml_profile_item(item)
        end
        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItem') do |item|
          data[:items] << parse_v2_xml_profile_item(item)
        end
        # Create object
        Category.new(data)
      rescue
        raise AMEE::BadData.new("Couldn't load ProfileCategory from V2 XML data. Check that your URL is correct.\n#{xml}")
      end

      def self.from_v2_batch_xml(xml)
        # Parse XML
        doc = REXML::Document.new(xml)
        data = {}
        data[:profileItems] = []
        REXML::XPath.each(doc, '/Resources/ProfileItems/ProfileItem') do |child|
          profile_item = {}
          profile_item[:uri] = child.attributes['uri'].to_s
          profile_item[:uid] = child.attributes['uid'].to_s
          data[:profileItems] << profile_item
        end
        return data
      rescue
        raise AMEE::BadData.new("Couldn't load ProfileCategory batch response from V2 XML data. Check that your URL is correct.\n#{xml}")
      end
      
      def self.from_v2_atom(response, options)
        # Parse XML
        doc = REXML::Document.new(response)
        data = {}
        data[:profile_uid] = REXML::XPath.first(doc, "/feed/@xml:base").to_s.match("/profiles/(.*?)/")[1]
        data[:start_date] = options[:start_date]
        data[:end_date] = options[:end_date]
        data[:name] = REXML::XPath.first(doc, '/feed/amee:name').text
        data[:path] = REXML::XPath.first(doc, "/feed/@xml:base").to_s.match("/profiles/.*?(/.*)")[1]
        data[:total_amount] = REXML::XPath.first(doc, '/feed/amee:totalAmount').text.to_f rescue nil
        data[:total_amount_unit] = REXML::XPath.first(doc, '/feed/amee:totalAmount/@unit').to_s rescue nil
        data[:children] = []
        REXML::XPath.each(doc, '/feed/amee:categories/amee:category') do |child|
          category_data = {}
#          category_data[:path] = child.text
          category_data[:path] = child.text
#          category_data[:uid] = child.attributes['uid'].to_s
          data[:children] << category_data
        end
#        REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/ProfileCategory') do |child|
#          data[:children] << parse_xml_profile_category(child)
#        end
        data[:items] = []
        REXML::XPath.each(doc, '/feed/entry') do |entry|
          item = {}
          item[:uid] = entry.elements['id'].text.match("urn:item:(.*)")[1]
          item[:name] = entry.elements['title'].text
          item[:path] = item[:uid]
#          data[:dataItemLabel].should == "gas"
#          data[:dataItemUid].should == "66056991EE23"
          item[:amount] = entry.elements['amee:amount'].text.to_f rescue nil
          item[:amount_unit] = entry.elements['amee:amount'].attributes['unit'].to_s rescue nil
          item[:startDate] = DateTime.parse(entry.elements['amee:startDate'].text)
          item[:endDate] = DateTime.parse(entry.elements['amee:endDate'].text) rescue nil
          item[:values] = {}
          entry.elements.each do |itemvalue|
            if itemvalue.name == 'itemValue'
              path = itemvalue.elements['link'].attributes['href'].to_s.match(".*/(.*)")[1]
              x = {}
              x[:path] = path
              x[:name] = itemvalue.elements['amee:name'].text
              x[:value] = itemvalue.elements['amee:value'].text unless itemvalue.elements['amee:value'].text == "N/A"
              x[:value] ||= "0"
              x[:unit] = itemvalue.elements['amee:unit'].text rescue nil
              x[:per_unit] = itemvalue.elements['amee:perUnit'].text rescue nil
              item[:values][path.to_sym] = x
            end
          end
          data[:items] << item
        end
        # Create object
        Category.new(data)
      rescue
        raise AMEE::BadData.new("Couldn't load ProfileCategory from V2 Atom data. Check that your URL is correct.\n#{response}")
      end

      def self.get_history(connection, path, num_months, end_date = Date.today, items_per_page = 10)
        month = end_date.month
        year = end_date.year
        history = []
        num_months.times do
          date = Date.new(year, month)
          data = self.get(connection, path, :start_date => date, :itemsPerPage => items_per_page)
          # If we get no data items back, there is no data at all before this date, so don't bother fetching it
          if data.items.empty?
            (num_months - history.size).times do
              history << Category.new(:children => [], :items => [])
            end
            break
          else
            history << data
          end
          month -= 1
          if (month == 0)
            year -= 1
            month = 12
          end
        end
        return history.reverse
      end

      def self.parse(connection, response, options)
        # Parse data from response
        if response.is_v2_json?
          cat = Category.from_v2_json(response, options)
        elsif response.is_json?
          cat = Category.from_json(response, options)
        elsif response.is_v2_atom?
          cat = Category.from_v2_atom(response, options)
        elsif response.is_v2_xml?
          cat = Category.from_v2_xml(response, options)
        elsif response.is_xml?
          cat = Category.from_xml(response, options)
        end
        # Store connection in object for future use
        cat.connection = connection
        # Done
        return cat
      end

      def self.parse_batch(connection, response)
        if response.is_v2_json?
          return Category.from_v2_batch_json(response)
        elsif response.is_v2_xml?
          return Category.from_v2_batch_xml(response)
        else
          return self.parse(connection, response, nil)
        end
      end
      
      def self.get(connection, path, orig_options = {})
        unless orig_options.is_a?(Hash)
          raise AMEE::ArgumentError.new("Third argument must be a hash of options!")
        end
        # Convert to AMEE options
        options = orig_options.clone
        if options[:start_date] && connection.version < 2
          options[:profileDate] = options[:start_date].amee1_month 
        elsif options[:start_date] && connection.version >= 2
          options[:startDate] = options[:start_date].xmlschema
        end
        options.delete(:start_date)
        if options[:end_date] && connection.version >= 2
          options[:endDate] = options[:end_date].xmlschema
        end
        options.delete(:end_date)
        if options[:duration] && connection.version >= 2
          options[:duration] = "PT#{options[:duration] * 86400}S"
        end
        # Load data from path
        response = connection.get(path, options).body
        return Category.parse(connection, response, orig_options)
      rescue
        raise AMEE::BadData.new("Couldn't load ProfileCategory. Check that your URL is correct.\n#{response}")
      end

      def child(child_path)
        AMEE::Profile::Category.get(connection, "#{full_path}/#{child_path}")
      end

      def item(options)
        # Search fields - from most specific to least specific
        item = items.find{ |x| x[:uid] == options[:uid] || x[:name] == options[:name] || x[:dataItemUid] == options[:dataItemUid] || x[:dataItemLabel] == options[:dataItemLabel] }
        # Pass through some options
        new_opts = {}
        new_opts[:returnUnit] = options[:returnUnit] if options[:returnUnit]
        new_opts[:returnPerUnit] = options[:returnPerUnit] if options[:returnPerUnit]
        new_opts[:format] = options[:format] if options[:format]
        item ? AMEE::Profile::Item.get(connection, "#{full_path}/#{item[:path]}", new_opts) : nil
      end

    end
  end
end