require 'pathname'
require 'fileutils'
require 'erb'

module Mimi
  class CLI
    class AppGenerator
      attr_reader :app_name, :target_path
      FILE_MAPPINGS = {
        'app/models/README.md' => 'app/models/README.md',
        'app/messaging/README.md' => 'app/messaging/README.md',
        'bin/start' => 'bin/start',
        'config/manifest.yml' => 'config/manifest.yml',
        'lib/file_name.rb' => 'lib/#{file_dirname}/#{file_basename}.rb',
        'lib/file_name/application.rb' => 'lib/#{file_name}/application.rb',
        'lib/file_name/version.rb' => 'lib/#{file_name}/version.rb',
        'lib/boot.rb' => 'lib/boot.rb',
        'spec/fixtures/' => 'spec/fixtures/',
        'spec/lib/app_name_spec.rb' => 'spec/lib/#{app_name}_spec.rb',
        'spec/lib/app_name/' => 'spec/lib/#{app_name}/',
        'spec/support/envvars.rb' => 'spec/support/envvars.rb',
        'spec/support/fixtures.rb' => 'spec/support/fixtures.rb',
        'spec/spec_helper.rb' => 'spec/spec_helper.rb',
        'Gemfile' => 'Gemfile',
        'Rakefile' => 'Rakefile',
        'app.env' => '.env',
        'app.gitignore' => '.gitignore',
        'app.rspec' => '.rspec'
      }
      FILE_MAPPINGS_EXEC = ['bin/start']

      APP_TEMPLATE_PATH = Pathname.new(__FILE__).dirname.join('template')

      def initialize(app_name, target_path = nil)
        @app_name = app_name
        @target_path = Pathname.new(target_path).expand_path
      end

      # Returns application name as class name.
      #
      # @example
      #   "some-my_app" -> "Some::MyApp"
      #
      def module_name
        name_parts = app_name.split('-').map do |word|
          word.gsub(/(^|_)(\w)/) { |m| m.chars.last.upcase }
        end
        name_parts.join('::')
      end

      # Returns module name split into parts
      #
      def module_name_parts
        module_name.split('::')
      end

      # Returns application name as file name.
      #
      # @example
      #   "some-my_app" -> "some/my_app"
      #
      def file_name
        app_name.split('-').join('/')
      end

      # Returns base part of the application name as file name.
      #
      # @example
      #   "some-my_app" -> "my_app"
      #
      def file_basename
        app_name.split('-').last
      end

      # Returns directory part of the application name as file name.
      #
      # @example
      #   "some-my_app" -> "some"
      #
      def file_dirname
        app_name.split('-')[0..-2].join('/')
      end

      def app_root_path
        target_path.join(app_name).expand_path
      end

      def generate(opts = {})
        raise "Application already exists: #{app_root_path}" if app_root_path.exist?
        install_path(app_root_path) unless opts[:dry_run]
        FILE_MAPPINGS.each do |from_file, to_file|
          to_file = eval "\"#{to_file}\""
          from_path = APP_TEMPLATE_PATH.join(from_file)
          to_path = app_root_path.join(to_file)
          puts "  #{from_path} -> #{to_path}"
          next if opts[:dry_run]
          if from_path.directory?
            install_path(to_path)
          else
            file_contents = File.read(from_path)
            file_processed = ERB.new(file_contents).result(binding)
            install_file(to_path, file_processed, FILE_MAPPINGS_EXEC.include?(from_file))
          end
        end
      end

      def install_path(path)
        FileUtils.mkdir_p(path)
      end

      def install_file(path, contents, executable = false)
        FileUtils.mkdir_p(path.dirname)
        File.open(path, 'w') do |f|
          f.puts contents
        end
        FileUtils.chmod(0755, path, verbose: true) if executable
      end
    end # class AppGenerator
  end # class CLI
end # module Mimi
