
require 'erb'

module Bones::App
class FileManager
  include Bones::Colors

  Error = Class.new(StandardError)

  attr_reader :destination
  attr_accessor :source, :archive, :verbose
  alias :verbose? :verbose

  #
  #
  def initialize( opts = {} )
    self.source = opts[:source]
    self.destination = opts[:destination]
    self.verbose = opts[:verbose]

    @out = opts[:stdout] || $stdout
    @err = opts[:stderr] || $stderr
  end

  # Sets the destination where files will be copied to. At the same time an
  # archive directory is configured. This is simply the destination
  # directory with a '.archive' extension.
  #
  def destination=( str )
    @destination = str
    @archive = str + '.archive' if str
  end

  # If the source is a repository this method returns the type of
  # repository. This will be :git for Git repositories and :svn for
  # Subversion repositories. Otherwise, +nil+ is returned.
  #
  def repository
    return :git if source =~ %r/\.git\/?$/i
    return :svn if source =~ %r/^(svn(\+ssh)?|https?|file):\/\//i
    nil
  end
  alias :repository? :repository

  #
  #
  def archive_destination
    return false unless test(?e, destination)

    archiving(destination)
    FileUtils.rm_rf(archive)
    FileUtils.mv(destination, archive)
    true
  end

  #
  #
  def copy
    if repository?
      _checkout(repository)
    else
      _files_to_copy.each {|fn| _cp(fn)}
    end
    self
  end

  # Gernate a new destination folder by copying files from the source, rename
  # files and directories that contain "NAME", and perform ERB templating on
  # ".bns" files. The _name_ use used for file/folder renaming and ERB
  # templating.
  #
  def template( name )
    name = name.to_s
    return if name.empty?

    if repository?
      _checkout(repository)
    else
      _files_to_copy.each {|fn| _cp(fn, false)}
    end

    self.destination = _rename(destination, name)
    _erb(name)
    self
  end

  #
  #
  def _checkout( repotype )
    case repotype
    when :git
      system('git', 'clone', source, destination)
      FileUtils.rm_rf(File.join(destination, '.git'))
    when :svn
      system('svn', 'export', source, destination)
    else
      raise Error, "Unknown repository type '#{repotype}'."
    end
  end

  #
  #
  def _rename( filename, name )
    newname = filename.gsub(%r/NAME/, name)

    if filename != newname
      raise "cannot rename '#{filename}' to '#{newname}' - file already exists" if test(?e, newname)
      FileUtils.mv(filename, newname)
    end

    if test(?d, newname)
      Dir.glob(File.join(newname, '*')).each {|fn| _rename(fn, name)}
    end
    newname
  end

  #
  #
  def _erb( name )
    binding = _erb_binding(name)
    Dir.glob(File.join(destination, '**', '*'), File::FNM_DOTMATCH).each do |fn|
      next unless test(?f, fn)
      if File.extname(fn) != '.bns'
        creating(fn)
        next
      end

      new_fn = fn.sub(%r/\.bns$/, '')
      creating(new_fn)

      txt = ERB.new(File.read(fn), trim_mode: '-').result(binding)
      File.open(new_fn, 'w') {|fd| fd.write(txt)}
      FileUtils.chmod(File.stat(fn).mode, new_fn)
      FileUtils.rm_f(fn)
    end
    self
  end

  #
  #
  def _erb_binding( name )
    obj = Object.new
    class << obj
      alias :__binding__ :binding
      instance_methods.each {|m| undef_method m unless m[%r/^(__|object_id)/]}
      def binding(name)
        classname = name.tr('-','_').split('_').map {|x| x.capitalize}.join
        __binding__
      end
    end
    obj.binding name
  end

  # Returns a list of the files to copy from the source directory to
  # the destination directory.
  #
  def _files_to_copy
    rgxp = %r/\A#{source}\/?/
    exclude = %r/tmp$|bak$|~$|CVS|\.svn/

    ary = Dir.glob(File.join(source, '**', '*'), File::FNM_DOTMATCH).map do |filename|
      next if exclude =~ filename
      next if test(?d, filename)
      filename.sub rgxp, ''
    end

    ary.compact!
    ary.sort!
    ary
  end

  # Copy a file from the Bones prototype project location to the user
  # specified project location. A message will be displayed to the screen
  # indicating that the file is being created.
  #
  def _cp( file, msg = true )
    dir = File.dirname(file)
    dir = (dir == '.' ? destination : File.join(destination, dir))
    dst = File.join(dir,  File.basename(file))
    src = File.join(source, file)

    (test(?e, dst) ? updating(dst) : creating(dst)) if msg

    FileUtils.mkdir_p(dir)
    FileUtils.cp src, dst

    FileUtils.chmod(File.stat(src).mode, dst)
  end

private

  def archiving( filename )
    return unless verbose?
    @put.puts "    #{colorize('archiving', :cyan)} #{filename}"
  end

  def creating( filename )
    return unless verbose?
    @out.puts "    #{colorize('creating', :green)} #{filename}"
  end

  def updating( filename )
    return unless verbose?
    @out.puts "    #{colorize('updating', :yellow)} #{filename}"
  end

end  # class FileManager
end  # module Bones::App

# EOF
