# coding: utf-8
require "goa_model_gen"

require 'json'

require "active_support/core_ext/string"
require "thor"

require "goa_model_gen/logger"
require "goa_model_gen/config"
require "goa_model_gen/loader"
require "goa_model_gen/generator"
require "goa_model_gen/go_struct"

module GoaModelGen
  class Cli < Thor
    include Thor::Actions

    class_option :version, type: :boolean, aliases: 'v', desc: 'Show version before processing'
    class_option :skip, type: :boolean, aliases: 's', desc: "Skip generate file"
    class_option :force, type: :boolean, aliases: 'f', desc: 'Force overwrite files'
    class_option :keep_editable, type: :boolean, aliases: 'k', default: true, desc: 'Keep user editable file'
    class_option :log_level, type: :string, aliases: 'l', desc: 'Log level, one of  debug,info,warn,error,fatal. The default value is info'
    class_option :config, type: :string, aliases: 'c', default: '.goa_model_gen.yaml', desc: 'Path to config file. You can generate it by config subcommand'

    desc "init", "Generate config file"
    def init(path = '.goa_model_gen.yaml')
      setup
      open(path, 'w'){|f| f.puts(Config.new.fulfill.to_yaml) }
    end

    desc "config", "Show configuration"
    def config(path = '.goa_model_gen.yaml')
      setup
      puts YAML.dump(cfg)
    end

    desc "show FILE1...", "Show model info from definition files"
    def show(*paths)
      setup
      load_types_for(paths) do |source_file|
        puts "types in #{source_file.yaml_path}"
        puts YAML.dump(source_file.types)
      end
    end

    desc "model FILE1...", "Generate model files from definition files"
    def model(*paths)
      setup
      new_generator.process({
        'templates/validator.go.erb' => File.join(cfg.validator_dir, 'validator.go'),
        'templates/model_base.go.erb' => File.join(cfg.model_dir, "model_base.go"),
      })
      load_types_for(paths) do |source_file|
        new_generator.tap{|g| g.source_file = source_file }.process({
          'templates/model.go.erb' => File.join(cfg.model_dir, "#{source_file.basename}.go"),
          'templates/model_validation.go.erb' => File.join(cfg.model_dir, "#{source_file.basename}_validation.go"),
        })
      end
    end

    desc "store FILE1...", "Generate store files from definition files"
    def store(*paths)
      setup
      new_generator.process({
        "templates/goon.go.erb" => File.join(cfg.store_dir, "goon_store", "goon.go"),
      })
      load_types_for(paths) do |source_file|
        source_file.types.select(&:store?).each do |model|
          variables = {
            model: model,
            model_package: model.package,
          }

          new_generator.tap{|g| g.source_file = source_file }.process({
            'templates/store.go.erb' => File.join(cfg.store_dir, model.package_path_name, "store.go"),
            'templates/store_validation.go.erb' => File.join(cfg.store_dir, model.package_path_name, "validation.go"),
          }, variables)
        end
      end
    end

    desc "structs_gen", "Generate go source files to generate structs.json"
    def structs_gen
      setup
      new_generator.process({
        "templates/structs_main.go.erb" => File.join(cfg.structs_gen_dir, "main.go"),
      })
    end

    desc "converter FILE", "Generate converter files from definition file structs.json"
    def converter(path)
      setup
      new_generator.process({
        "templates/converter_base.go.erb" => File.join(cfg.converter_dir, "base.go"),
      })
      structs = JSON.parse(File.read(path))
      (structs['model'] || []).each do |mt|
        m = GoStruct.new(mt)
        pt = (structs['payload'] || []).detect{|t| t["Name"] == "#{m.name}Payload" }
        rt = (structs['result'] || []).detect{|t| t["Name"] == m.name }
        variables = {
          models: (structs['model'] || []),
          payloads: (structs['payload'] || []),
          results: (structs['result'] || []),
          model: m,
          payload: pt ? GoStruct.new(pt) : nil,
          result: rt ? GoStruct.new(rt) : nil,
        }
        new_generator.run('templates/converter.go.erb', File.join(cfg.converter_dir, m.package_path_name, "conv.go"), variables)
      end
    end

    desc "version", "Show version"
    def version
      show_version
    end

    no_commands do
      def setup
        show_version if options[:version]
        GoaModelGen::Logger.setup(options[:log_level] || 'info')
      end

      def show_version
        puts "#{$PROGRAM_NAME} version:#{::GoaModelGen::VERSION}"
      end

      def cfg
        @cfg ||= GoaModelGen::Config.new.load_from(options[:config])
      end

      def new_generator
        GoaModelGen::Generator.new(cfg).tap do |g|
          g.thor = self
          g.force = options[:force]
          g.skip = options[:skip]
          g.keep_editable = options[:keep_editable]
        end
      end

      def load_types_for(paths)
        loader = GoaModelGen::Loader.new(cfg)
        source_files = loader.load_files(paths)
        source_files.each do |source_file|
          yield(source_file)
        end
      end
    end

  end
end
