require 'cxxstdlib'
require 'ostruct'
require 'options'
require 'utils/json'

# Inherit from OpenStruct to gain a generic initialization method that takes a
# hash and creates an attribute for each key and value. `Tab.new` probably
# should not be called directly, instead use one of the class methods like
# `Tab.create`.
class Tab < OpenStruct
  FILENAME = 'INSTALL_RECEIPT.json'

  def self.create f, compiler, stdlib, args
    build = f.build.dup
    build.args = args

    sha = HOMEBREW_REPOSITORY.cd do
      `git rev-parse --verify -q HEAD 2>/dev/null`.chuzzle
    end

    Tab.new :used_options => build.used_options,
            :unused_options => build.unused_options,
            :tabfile => f.prefix.join(FILENAME),
            :built_as_bottle => !!ARGV.build_bottle?,
            :poured_from_bottle => false,
            :tapped_from => f.tap,
            :time => Time.now.to_i,
            :HEAD => sha,
            :compiler => compiler,
            :stdlib => stdlib
  end

  def self.from_file path
    tab = Tab.new Utils::JSON.load(File.read(path))
    tab.tabfile = path.realpath
    tab
  end

  def self.for_keg keg
    path = keg.join(FILENAME)

    if path.exist?
      self.from_file(path)
    else
      self.dummy_tab
    end
  end

  def self.for_name name
    for_formula(Formula.factory(name))
  end

  def self.for_formula f
    paths = [f.opt_prefix, f.linked_keg]

    if f.rack.directory? && (dirs = f.rack.subdirs).length == 1
      paths << dirs.first
    end

    paths << f.prefix

    path = paths.map { |pn| pn.join(FILENAME) }.find(&:file?)

    if path
      from_file(path)
    else
      dummy_tab(f)
    end
  end

  def self.dummy_tab f=nil
    Tab.new :used_options => [],
            :unused_options => (f.build.as_flags rescue []),
            :built_as_bottle => false,
            :poured_from_bottle => false,
            :tapped_from => "",
            :time => nil,
            :HEAD => nil,
            :compiler => :clang
  end

  def with? name
    if options.include? "with-#{name}"
      used_options.include? "with-#{name}"
    elsif options.include? "without-#{name}"
      not used_options.include? "without-#{name}"
    else
      false
    end
  end

  def include? opt
    used_options.include? opt
  end

  def universal?
    used_options.include? "universal"
  end

  def used_options
    Options.coerce(super)
  end

  def unused_options
    Options.coerce(super)
  end

  def options
    used_options + unused_options
  end

  def cxxstdlib
    # Older tabs won't have these values, so provide sensible defaults
    lib = stdlib.to_sym if stdlib
    cc = compiler || MacOS.default_compiler
    CxxStdlib.new(lib, cc.to_sym)
  end

  def to_json
    Utils::JSON.dump({
      :used_options => used_options.map(&:to_s),
      :unused_options => unused_options.map(&:to_s),
      :built_as_bottle => built_as_bottle,
      :poured_from_bottle => poured_from_bottle,
      :tapped_from => tapped_from,
      :time => time,
      :HEAD => self.HEAD,
      :stdlib => (stdlib.to_s if stdlib),
      :compiler => (compiler.to_s if compiler)})
  end

  def write
    tabfile.atomic_write(to_json)
  end

  def to_s
    s = []
    case poured_from_bottle
    when true  then s << "Poured from bottle"
    when false then s << "Built from source"
    end
    unless used_options.empty?
      s << "Installed" if s.empty?
      s << "with:"
      s << used_options.to_a.join(", ")
    end
    s.join(" ")
  end
end
