require 'yaml'
module TSV

  TSV_SERIALIZER = YAML
  SERIALIZED_NIL = TSV_SERIALIZER.dump nil

  attr_accessor :unnamed, :serializer_module, :entity_options, :entity_templates

  def annotate(tsv)
    TSV.setup(tsv, :key_field => key_field, :fields => fields, :namespace => namespace, :entity_options => entity_options, :type => type, :filename => filename, :identifiers => identifiers)
  end

  def entity_options
    if @entity_options.nil?
      @entity_options = namespace ? {:namespace => namespace, :organism => namespace} : {}
      @entity_templates = nil
    end
    @entity_options
  end

  def entity_options=(options)
    @entity_options = options
    @entity_templates = nil
  end


  def entity_templates
    @entity_templates ||= {}
  end

  def prepare_entity(entity, field, options = {})
    return entity if entity.nil?
    return entity unless defined? Entity
    entity = entity if options.delete :dup_array
    if (template = entity_templates[field]) and template.respond_to?(:annotate)
      entity = template.annotate(entity.frozen? ? entity.dup : entity)
      entity.extend AnnotatedArray if Array === entity
      entity
    else
      if entity_templates.include? field
        entity
      else
        template = Misc.prepare_entity("TEMPLATE", field, options)
        if template.respond_to?(:annotate)
          entity_templates[field] = template
          entity = template.annotate(entity.frozen? ? entity.dup : entity)
          entity.extend AnnotatedArray if Array === entity
          entity
        else
          entity_templates[field] = nil
          entity
        end
      end
    end
  end

  def setup_array(*args)
    res = NamedArray.setup(*args)
    res.instance_variable_set(:@entity_templates, entity_templates)
    res
  end

  def with_unnamed
    saved_unnamed = @unnamed 
    @unnamed = true
    res = yield
    @unnamed = saved_unnamed
    res
  end

  def with_monitor(value = true)
    saved_monitor = @monitor
    @monitor = value
    res = yield
    @monitor = saved_monitor
    res
  end

  def self.extended(data)
    #setup_chains(data)

    if not data.respond_to? :write
      class << data
        attr_accessor :writable

        def close
        end

        def read(force = false)
          @writable = false
          self
        end

        def write(force = false)
          @writable = true
          self
        end

        def write?
          @writable
        end
      end
    end

    if not data.respond_to? :serialized_get
      #class << data
      #  alias serialized_get []
      #  alias serialized_set []=
      #end
    end
  end

  KEY_PREFIX = "__tsv_hash_"

  ENTRIES = []
  ENTRY_KEYS = []

  #{{{ Chained Methods
  def empty?
    length == 0
  end

  def [](key, clean = false)
    value = (self.respond_to?(:serialized_get) and not clean) ? serialized_get(key) : super(key)
    return value if value.nil? or @unnamed or clean == :entry_key or fields.nil?

    case type
    when :double, :list
      setup_array value, fields, key, entity_options, entity_templates
    when :flat, :single
      value = value.dup if value.frozen?

      value = prepare_entity(value, fields.first, entity_options)
    end
    value
  end

  def []=(key, value, clean = false)
    return super(key, value) if clean or not self.respond_to?(:serialized_set)
    serialized_set(key, value)
  end

  def keys
    keys = super - ENTRY_KEYS
    return keys if @unnamed or key_field.nil?

    prepare_entity(keys, key_field, entity_options.merge(:dup_array => true))
  end

  def values
    values = chunked_values_at(keys)
    return values if @unnamed or fields.nil?

    case type
    when :double, :list
      values.each{|value| setup_array value, fields, nil, entity_options}
    when :single
      values = prepare_entity(values, fields.first, entity_options)
    when :flat
      values = values.collect{|v| prepare_entity(v, fields.first, entity_options)}
    end
      
    values
  end

  def each
    fields = self.fields

    serializer = self.serializer
    serializer_module = SERIALIZER_ALIAS[serializer] unless serializer.nil?
    super do |key, value|
      next if ENTRY_KEYS.include? key

      # TODO Update this to be more efficient
      value = serializer_module.load(value) unless serializer.nil? or FalseClass === serializer

      # Annotated with Entity and NamedArray
      if not @unnamed
        if not fields.nil? 
          case type
          when :double, :list
            setup_array value, fields, key, entity_options, entity_templates if Array === value
          when :flat, :single
            prepare_entity(value, fields.first, entity_options)
          end
        end
        key = prepare_entity(key, key_field, entity_options)
      end

      yield key, value if block_given?
      [key, value]
    end
  end

  def collect
    serializer = self.serializer
    serializer_module = SERIALIZER_ALIAS[serializer] unless serializer.nil?
    super do |key, value|
      next if ENTRY_KEYS.include? key

      # TODO Update this to be more efficient
      value = serializer_module.load(value) unless serializer.nil?

      # Annotated with Entity and NamedArray
      if not @unnamed
        if not fields.nil? 
          case type
          when :double, :list
            setup_array value, fields, key, entity_options if Array === value 
          when :flat, :single
            value = prepare_entity(value, fields.first, entity_options)
          end
        end
        key = prepare_entity(key, key_field, entity_options)
      end

      if block_given?
        yield key, value
      else
        [key, value]
      end
    end
  end

  def size
    super - ENTRY_KEYS.select{|k| self.include? k}.length
  end

  def length
    keys.length
  end

  def values_at(*keys)
    keys.collect do |key|
      self[key]
    end
  end

  def chunked_values_at(keys, max = 5000)
    Misc.ordered_divide(keys, max).inject([]) do |acc,c|
      new = self.values_at(*c)
      new.annotate acc if new.respond_to? :annotate and acc.empty?
      acc.concat(new)
    end
  end

  #{{{ Sorting

  def sort_by(field = nil, just_keys = false, &block)
    field = :all if field.nil?
    if field == :all
      elems = collect
    else
      elems = []
      case type
      when :single
        through :key, field do |key, field|
          elems << [key, field]
        end
      when :list, :flat
        through :key, field do |key, fields|
          elems << [key, fields.first]
        end
      when :double
        through :key, field do |key, fields|
          elems << [key, fields.first]
        end
      end
    end

    if not block_given?
      if fields == :all
        if just_keys
          keys = elems.sort_by{|key, value| key }.collect{|key, values| key}
          keys = prepare_entity(keys, key_field, entity_options.merge(:dup_array => true))
        else
          elems.sort_by{|key, value| key }
        end
      else
        sorted = elems.sort do |a, b| 
          a_value = a.last
          b_value = b.last
          case
          when ((a_value.nil? or (a_value.respond_to?(:empty?) and a_value.empty?)) and (b_value.nil? or (b_value.respond_to?(:empty?) and b_value.empty?)))
            0
          when (a_value.nil? or (a_value.respond_to?(:empty?) and a_value.empty?))
            -1
          when (b_value.nil? or (b_value.respond_to?(:empty?) and b_value.empty?))
            1
          when Array === a_value
            if a_value.length == 1 and b_value.length == 1
              a_value.first <=> b_value.first
            else
              a_value.length <=> b_value.length
            end
          else
            a_value <=> b_value
          end
        end
        if just_keys
          #keys = elems.sort_by{|key, value| value }.collect{|key, value| key}
          keys = sorted.collect{|key, value| key}
          keys = prepare_entity(keys, key_field, entity_options.merge(:dup_array => true))
          keys
        else
          sorted.collect{|key, value| [key, self[key]]}
        end
      end
    else
      if just_keys
        elems.sort_by(&block).collect{|key, value| key}
      else
        elems.sort_by(&block).collect{|key, value| [key, self[key]]}
      end
    end
  end

  def tsv_sort(&block)
    collect.sort &block
  end

  # Starts in page 1
  def page(pnum, psize, field = nil, just_keys = false, reverse = false, &block)
    pstart = psize * (pnum - 1)
    pend = psize * pnum - 1
    field = :key if field == "key"
    keys = sort_by(field || :key, true, &block)
    keys.reverse! if reverse

    if just_keys
      keys[pstart..pend]
    else
      select :key => keys[pstart..pend]
    end
  end


  def self.entry(*entries)
    entries = entries.collect{|entry| entry.to_s}
    ENTRIES.concat entries
    entries.each do |entry|
      key = KEY_PREFIX + entry
      ENTRY_KEYS << key
      line = __LINE__; self.module_eval "
attr_accessor :#{entry}

def #{ entry }
  if not defined? @#{entry}
    # @#{entry} = (value = self.clean_get_brackets('#{key}')).nil? ? nil : TSV_SERIALIZER.load(value)
    @#{entry} = (value = self.send(:[], '#{key}', :entry_key)).nil? ? nil : TSV_SERIALIZER.load(value)
  end
  @#{entry}
end


if '#{entry}' == 'serializer'

  def #{ entry }=(value)
    @#{entry} = value
    #self.tsv_clean_set_brackets '#{key}', value.nil? ? SERIALIZED_NIL : value.to_yaml
    self.send(:[]=, '#{key}', value.nil? ? SERIALIZED_NIL : value.to_yaml, true)

    return if value.nil?

    self.serializer_module = SERIALIZER_ALIAS[value.to_sym]

    if serializer_module.nil?
      #class << self
      #  alias serialized_get tsv_clean_get_brackets
      #  alias serialized_set tsv_clean_set_brackets
      #end

    else
      class << self

        define_method :serialized_get do |key|
          return nil unless self.include? key
          res = self.send(:[], key, true)
          return res if res.nil?
          self.serializer_module.load(res)
        end

        define_method :serialized_set do |key, value|
          if value.nil?
            self.send(:[]=, key, value, true)
            #tsv_clean_set_brackets key, value
          else
            self.send(:[]=, key, self.serializer_module.dump(value), true)
            #tsv_clean_set_brackets key, self.serializer_module.dump(value)
          end
        end
      end
    end

  end
else
  def #{ entry }=(value)
    @#{entry} = value
    self.send(:[]=, '#{key}', value.nil? ? SERIALIZED_NIL : value.to_yaml, true)
    #self.tsv_clean_set_brackets '#{key}', value.nil? ? SERIALIZED_NIL : value.to_yaml
  end
end
  ", __FILE__, line
    end
  end

  entry :key_field, 
    :type,
    :fields,
    :cast,
    :identifiers,
    :namespace,
    :filename,
    :serializer

  def fields
    #@fields ||= TSV_SERIALIZER.load(self.tsv_clean_get_brackets("__tsv_hash_fields") || SERIALIZED_NIL)
    @fields ||= TSV_SERIALIZER.load(self.send(:[], "__tsv_hash_fields", :entry_key) || SERIALIZED_NIL)
    if true or @fields.nil? or @unnamed
      @fields
    else
      @named_fields ||= NamedArray.setup @fields, @fields, nil, entity_options, entity_templates
    end
  end

  def namespace=(value)
    #self.tsv_clean_set_brackets "__tsv_hash_namespace", value.nil? ? SERIALIZED_NIL : value.to_yaml
    self.send(:[]=, "__tsv_hash_namespace", value.nil? ? SERIALIZED_NIL : value.to_yaml, true)
    @namespace = value
    @entity_options = nil
  end

  def fields=(value)
    #self.tsv_clean_set_brackets "__tsv_hash_fields", value.nil? ? SERIALIZED_NIL : value.to_yaml
    self.send(:[]=, "__tsv_hash_fields", value.nil? ? SERIALIZED_NIL : value.to_yaml, true)
    @fields = value
    @named_fields = nil
  end

  def self.zip_fields(list, fields = nil)
    return [] if list.nil? || list.empty?
    fields ||= list.fields if list.respond_to? :fields
    zipped = list[0].zip(*list[1..-1])
    zipped = zipped.collect{|v| setup_array(v, fields)} if fields 
    zipped 
  end

  def identifier_files
    case
    when (identifiers and TSV === identifiers)
      [identifiers]
    when (identifiers and Array === identifiers)
      case
      when (TSV === identifiers.first or identifiers.empty?)
        identifiers
      else
        identifiers.collect{|f| Path === f ? f : Path.setup(f, nil, namespace)}
      end
    when identifiers
      [ Path === identifiers ? identifiers : Path.setup(identifiers, nil, namespace) ]
    when Path === filename
      filename.identifier_files
    when filename
      Path.setup(filename.dup).identifier_files
    else
      []
    end
  end

  def options
    options = {}
    ENTRIES.each do |entry|
      options[entry] = self.send(entry)
    end
    IndiferentHash.setup options
  end


  def all_fields
    return nil if key_field.nil? or fields.nil?
    [key_field] + fields
  end

  def values_to_s(values)
    case values
    when nil
      if fields.nil? or fields.empty?
        "\n"
      else
        "\t" << ([""] * fields.length) * "\t" << "\n"
      end
    when Array
      "\t" << values.collect{|v| Array === v ? v * "|" : v} * "\t" << "\n"
    else
      "\t" << values.to_s << "\n"
    end
  end

  def to_s(keys = nil, no_options = false)
    if FalseClass === keys or TrueClass === keys
      no_options = keys
      keys = nil
    end

    if keys == :sort
      with_unnamed do
        keys = self.keys.sort
      end
    end

    str = ""

    str << "#: " << Misc.hash2string((ENTRIES - ["key_field", "fields"]).collect{|key| [key.to_sym, self.send(key)]}) << "\n" unless no_options
    if fields
      str << "#" << key_field << "\t" << fields * "\t" << "\n"
    end

    with_unnamed do
      if keys.nil?
        each do |key, values|
          key = key.to_s if Symbol === key
          str << key.to_s
          str << values_to_s(values)
        end
      else
        keys.zip(values_at(*keys)).each do |key, values|
          key = key.to_s if Symbol === key
          str << key.to_s << values_to_s(values)
        end
      end

    end
    str
  end

  def value_peek
    peek = {}
    i = 0
    begin
      through do |k,v|
        peek[k] = v 
        i += 1
        raise "STOP" if i > 10
      end
    rescue
    end
    peek
  end

  def summary
    key = nil
    values = nil
    self.each do |k, v|
      key = k
      values = v
      break
    end
    with_unnamed do
      <<-EOF
Key field = #{key_field || "*No key field*"}
Fields = #{fields ? Misc.fingerprint(fields) : "*No field info*"}
Type = #{type}
Size = #{size}
namespace = #{namespace}
Example:
  - #{key} -- #{Misc.fingerprint values }
      EOF
    end
  end

  def to_hash
    new = self.dup
    ENTRY_KEYS.each{|entry| new.delete entry}
    new
  end
end