require 'yaml'
#require 'facet/hash/traverse'
require 'facet/hash/graph'
require 'facet/string/tabto'
require 'facet/dir/self/ascend'

#require 'facet/basicobject'
require 'facet/opencascade'

# Project information.
#
# ProjectInfo is a Singleton. Access it via the
# ProjectInfo.instance method.

class ProjectInfo

  include Enumerable

  INFO_FILES = [ 'ProjectInfo','projectinfo' ]

  class << self

    # Singelton Pattern.

    private :new

    def instance( &block )
      @instance ||= block_given? ? new( &block ) : load
    end

    # Move to file's location.

    def prime_location( fpath=nil )
      if fpath
        Dir.chdir( File.dirname( fpath ) )
      else
        fpath = find
        if fpath
          Dir.chdir( File.dirname( fpath ) )
        end
      end
    end

    # Find project information file.

    def find
      info_dir, info_file = nil, nil
      Dir.ascend(Dir.pwd) do |info_dir|
        info_file = INFO_FILES.find{ |f| File.file?( File.join( info_dir, f ) ) }
        break if info_file
      end
      return nil unless info_file
      return File.join( info_dir, info_file )
    end

    # Load the project information from a file. Generally
    # no file needs to be specified; the file will be found
    # by ascending up the current path until a default
    # file name is found (eg. ProjectInfo or Reapfile).

    def load( fpath=nil )
      unless fpath
        fpath = find
        if fpath
          Dir.chdir( File.dirname( fpath ) )
        end
      end
      new.read( fpath )
    end

    def exist?
      @instance.info_file ? true : false
    end
    alias_method :exists?, :exist?

  end


  attr_reader :info, :info_stream, :info_dir, :info_file

  def initialize( &block )
    @info = {}
    @info_stream = nil

#    define( &block ) if block_given?
  end

  # Define project information programmatically.

#   def define( &block )
#     return unless block
#
#     @info = HashBuilder.new( &block  ).to_h
#     @info_stream = @info.to_yaml
#     @info_dir = Dir.pwd #?
#     @info_file = nil
#
#     #validate
#     defaults
#
#     self
#   end

  # Load project information from YAML file.

  def read( fpath )
    return self unless fpath

    @info_dir = File.dirname( fpath )
    @info_file = fpath #File.basename( fpath )
    @info_stream = File.read( fpath ).strip
    @info = YAML::load( info_stream )
    @info = @info.graph{ |k,v| [k.to_s.downcase, v] }

    #validate
    defaults
=begin
    # register the task entries
    @info.each do |key, val|
      #value = @info[key] = {} if value.nil?
      if Reap::TaskDef === val
        val.name = key.to_s
      end
    end
=end
    self
  end

#   # Update project information.
# 
#   def update( info=nil, &block )
#     if info
#       @info.update( info.traverse{ |k,v| [k.to_s.downcase, v] } )
#     end
#     if block_given?
#       @info.update( HashBuilder.new( &block  ).to_h )
#     end
#     @info_stream = @info.to_yaml
# 
#     #validate
#     defaults
# 
#     self
#   end

  # Project information file exists? (may need to change to info exists?)

  def exists?
    @info_file
  end
  alias_method :exist?, :exists?

  # Validate project information.

  def validate #( *fields )
    val = true
    (puts "NAME is required in project information file."; val=false) unless info['name']
    (puts "VERSION is required in project information file."; val=false) unless info['version']
    exit -1 unless val
  end

  # Project information defaults.

  def defaults
    self['title']       ||= self['name'].capitalize
    self['series']      ||= '1'
    self['date']        ||= Time.now.strftime("%Y-%m-%d")
    self['author']      ||= "Anonymous"
    self['maintainer']  ||= self['author']
    self['arch']        ||= 'Any'
    self['license']     ||= 'Ruby/GPL'
    self['status']      ||= 'Beta'
    self['project']     ||= self['rubyforge'] ? self['rubyforge']['project'] : nil
    self['homepage']    ||= self['rubyforge'] ? self['rubyforge']['homepage'] : nil
    self['trunk']       ||= 'trunk' if File.directory?( File.join( @info_dir, 'trunk' ))
  end

  # Convert to a CascadinOpenObject.

  def to_opencascade
    OpenCascade.new( @info )
  end

  # Information fetch.

  def [](name)
    @info[name]
  end

  # Information store.

  def []=(name, x)
    @info[name] = x
  end

  def each( &block )
    @info.each( &block )
  end

  # Information to hash.

  def to_h
    @info.dup
  end

end