module PubliSci
  module DataSet
    module ORM
      class DataCube
        extend PubliSci::Dataset::DataCube
        extend PubliSci::Analyzer
        extend PubliSci::Query
        extend PubliSci::RDFParser

        include PubliSci::Dataset::DataCube
        include PubliSci::Analyzer
        include PubliSci::Metadata::Generator
        include PubliSci::Query
        include PubliSci::RDFParser

        attr_accessor :labels
        attr_accessor :dimensions
        attr_accessor :measures
        attr_accessor :obs
        attr_accessor :meta

        def initialize(options={},do_parse = true)
          @dimensions = {}
          @measures = []
          @obs = []
          @generator_options = {}
          @options = {}

          @meta = {}

          parse_options options if do_parse
        end

        def self.load(graph,options={},verbose=false)


          graph = load_string(graph) unless graph =~ /^http/

          # puts get_hashes(execute_from_file('dimension_ranges.rq',graph))
          dimensions = Hash[get_hashes(execute_from_file('dimension_ranges.rq',graph),"to_s").map{|solution|
            #TODO coded properties should be found via SPARQL queries
            if solution[:range].split('/')[-2] == "code"
              type = :coded
            else
              type = solution[:range].to_s
            end
            [solution[:dimension], {type: type}]
          }]
          puts "dimensions: #{dimensions}" if verbose

          codes = execute_from_file('code_resources.rq',graph).to_h.map{|sol|
            [sol[:dimension].to_s, sol[:codeList].to_s, sol[:class].to_s]
          }
          puts "codes: #{codes}" if verbose

          measures = execute_from_file('measures.rq',graph).to_h.map{|m| m[:measure].to_s}
          puts "measures: #{measures}" if verbose

          name = execute_from_file('dataset.rq',graph).to_h.first[:label]
          puts "dataset: #{name}" if verbose

          obs = execute_from_file('observations.rq',graph)
          observations = observation_hash(obs)
          puts "observations: #{observations}" if verbose

          # simple_observations = observation_hash(obs,true)

          labels = execute_from_file('observation_labels.rq', graph)
          labels = Hash[labels.map{|sol|
            [sol[:observation].to_s, sol[:label].to_s]
          }]

          new_opts = {
            measures: measures,
            dimensions: dimensions,
            observations: observations.values,
            name: name,
            labels: labels.values,
            codes: codes
          }

          options = options.merge(new_opts)
          puts "creating #{options}" if verbose
          self.new(options)
        end

        def parse_options(options)
          if options[:dimensions]
            options[:dimensions].each{|name,details|
              add_dimension(name, details[:type] || :coded)
            }
          end

          if options[:measures]
            options[:measures].each{|m| @measures << m}
          end

          if options[:observations]
            options[:observations].each{|obs_data| add_observation obs_data}
          end

          @generator_options = options[:generator_options] if options[:generator_options]
          @options[:skip_metadata] = options[:skip_metadata] if options[:skip_metadata]

          if options[:name]
            @name = options[:name]
          else
            raise "No dataset name specified!"
          end

          if options[:validate_each]
            @options[:validate_each] = options[:validate_each]
          end

          if options[:labels]
            @labels = options[:labels]
          end

          if options[:codes]
            @codes = options[:codes]
          end
        end

        def to_n3

          #create labels if not specified
          unless @labels.is_a?(Array) && @labels.size == @obs.size
            if @labels.is_a? Symbol
              #define some automatic labeling methods
            else
              @labels = (1..@obs.size).to_a.map(&:to_s)
            end
          end
          data = {}


          #collect observation data
          check_integrity(@obs.map{|o| o.data}, @dimensions.keys, @measures)
          @obs.map{|obs|
            (@measures | @dimensions.keys).map{ |component|
             (data[component] ||= []) <<  obs.data[component]
            }
          }


          @codes = @dimensions.map{|d,v| d if v[:type] == :coded}.compact unless @codes
          str = generate(@measures, @dimensions.keys, @codes, data, @labels, @name, @generator_options)
          unless @options[:skip_metadata]
            fields = {
              publishers: publishers(),
              subject: subjects(),
              author: author(),
              description: description(),
              date: date(),
              var: @name,
            }
            # puts basic(fields,@generator_options)
            str += "\n" + basic(fields)
          end
          str
        end

        def add_dimension(name, type=:coded)
          @dimensions[name.to_s] = {type: type}
        end

        def add_measure(name)
          @measures << name
        end

        def add_observation(data)
          data = Hash[data.map{|k,v| [k.to_s, v]}]
          obs = Observation.new(data)
          check_integrity([obs.data],@dimensions.keys,@measures) if @options[:validate_each]
          @obs << obs
        end

        def insert(observation)
          @obs << observation
        end

        def publishers
          @meta[:publishers] ||= []
        end

        def publishers=(publishers)
          @meta[:publishers] = publishers
        end

        def subjects
          @meta[:subject] ||= []
        end

        def subjects=(subjects)
          @meta[:subject]=subjects
        end

        def add_publisher(label,uri)
          publishers << {label: label, uri: uri}
        end

        def add_subject(id)
          subject << id
        end

        def author
          @meta[:creator] ||= ""
        end

        def author=(author)
          @meta[:creator] = author
        end

        def description
          @meta[:description] ||= ""
        end

        def description=(description)
          @meta[:description] = description
        end

        def date
          @meta[:date] ||= "#{Time.now.day}-#{Time.now.month}-#{Time.now.year}"
        end

        def date=(date)
          @meta[:date] =  date
        end

        def to_h
          {
            measures: @measures,
            dimensions: @dimensions,
            observations: @obs.map{|o| o.data}
          }
        end
      end
    end
  end
end