# encoding: utf-8
require 'fedux_org_stdlib/require_files'
require 'fedux_org_stdlib/file_template/exceptions'
require 'fedux_org_stdlib/core_ext/array/list'
require 'fedux_org_stdlib/logging/logger'
require_library %w{ json active_support/core_ext/string/inflections }

module FeduxOrgStdlib
  # This class makes a template file available as an object. You can use
  # whatever template language you prefer. It's up to you to compile the
  # template with a suitable template parser.
  #
  # By default it will look for a suitable template file in the given order:
  #
  # 1. <current working directory>/templates/<template_file>.tt
  # 1. $HOME/.config/<application_name>/templates/<template_file>.tt
  # 2. $HOME/.<application_name>/templates/<template_file>.tt
  # 3. /etc/<application_name>/templates/<template_file>.tt
  #
  # Please keep in mind
  #
  # * application_name: Module of your class, e.g. "MyApplication" becomes
  #   "my_application"
  # * template_file: Singular name of your class and "Template" strip
  #   off, e.g "ClientTemplate" becomes "client.tt"
  #
  # Most conventions defined by me are implemented as separate methods. If one convention
  # is not suitable for your use case, just overwrite the method. 
  #
  # If you prefer to use a different path to the template file or name of the
  # template file one of the following methods needs to be overwritten:
  #
  # * template_file
  # * template_name
  # * application_name
  #
  # If you want the class to look for your template file at a different place
  # overwrite the following method
  #
  # * allowed_template_file_paths
  #
  # Below you find some examples for the usage of the class:
  #
  # @example Create template with one writer and reader
  #   module MyApplication
  #     class ClientTemplate < FileTemplate
  #     end
  #   end
  #
  # @example Template yaml file for the classes above: clients.yaml
  #   <%= hello %>
  #   ---
  #   option1: 'data2'
  class FileTemplate

    attr_reader :working_directory, :output_directory, :logger, :file, :content

    # Create a new instance of template
    #
    # It tries to find a suitable template file. If it doesn't find one
    # the template is empty
    #
    # @param [String] file
    #   Path where template file is stored.
    #
    # @raise [Exceptions::TemplateFileNotReadable]
    #   If an avaiable template file could not be read by the template engine
    #
    # @return [AppTemplate]
    #   The template instance. If the resulting data structure created by the
    #   template_engine does not respond to `:[]` an empty template object will be
    #   created. 
    def initialize(
      file: nil,
      logger: FeduxOrgStdlib::Logging::Logger.new,
      working_directory: Dir.getwd,
      output_directory: nil
    )
      @logger            = logger
      @working_directory = working_directory
      @output_directory  = output_directory || working_directory

      @file ||= available_template_file
        
      fail Exceptions::NoTemplateFileFound, "No template file found at #{allowed_template_file_paths.to_list}, therefor I'm stop working as there are methods which depend on an available template file path." unless @file

      begin
        @content = File.read(@file).chomp
      rescue StandardError => e
        fail Exceptions::TemplateFileNotReadable, JSON.dump(message: e.message, file: @file)
      end
    end

    # Return the path to the preferred template file
    # @return [String]
    #   The path to the preferred template file
    def preferred_template_file
      allowed_template_file_paths[1]
    end

    private

    # The name of the template file
    #
    # @return [String]
    #   The name of the template file. It defaults to `<template_name>.yaml`. If
    #   you want to use a different file name you need to overwrite this
    #   method.
    def template_file
      "#{template_name}#{template_file_suffix}"
    end

    # The suffix of the template file
    #
    # @return [String]
    #   The suffix of the template file
    def template_file_suffix
      '*.tt'
    end

    # The base name of the template
    #
    # @return [String]
    #   This one returns the base name of the template file (without the file
    #   extension). It uses the class name of the template class
    #
    # @example Determine the base name of the template
    #
    #   class ClientTemplate; end
    #
    # This will result in `client` as base name for the template file.
    def template_name
      unless (name = class_name.sub(/Template/, '').underscore.singularize).blank?
        return name
      end

      fail Exceptions::ClassNameIsMissing, JSON.dump(klass: class_name)
    end

    # The name of your application
    #
    # @return [String]
    #  This will strip of the class part of fully qualified class name and
    #  converted it to a path.
    #
    # @example Determine application name
    #
    #   class MyApplication::MyTemplate; end
    #
    # This will be converted to
    #
    #    my_application
    def application_name
      module_name.underscore
    end

    # The paths where to look for the template file
    #
    # @return [Array]
    #   A list of paths where the template object should look for its template
    #   file.
    def allowed_template_file_paths
      paths = []

      paths << resolve_path(working_directory, 'templates', template_file)
      paths << resolve_path('~', '.config', application_name, 'templates', template_file)
      paths << resolve_path('~', format('.%s', application_name), 'templates', template_file)
      paths << resolve_path('/etc', application_name, 'templates', template_file)
      paths << resolve_path(fallback_template_directory, template_file) if fallback_template_directory

      paths
    end

    def resolve_path(*path)
      ::File.expand_path(::File.join(*path))
    end


    # Use this path as fall back path
    def fallback_template_directory; end

    def class_name
      self.class.name.to_s.demodulize
    end

    def module_name
      self.class.to_s.deconstantize
    end

    def available_template_file
      allowed_template_file_paths.map { |f| Dir.glob(f).first }.compact.find { |f| ::File.exists?(f) }
    end

    def self.reserved_key_words
      (methods | instance_methods | private_methods | private_instance_methods ) - (Class.methods | Class.private_methods ) | [:to_s]
    end

    def basename
      File.basename(file, '.tt')
    end

    def extname
      File.extname(basename)
    end

    def extname?(*ext)
      ext.any? { |e| e == extname }
    end

    def proposed_file_name
      template_name
    end

    public

    def proposed_file
      File.join output_directory, proposed_file_name
    end

    def proposed_extname
      ext = File.extname(basename)

      return '.erb' if ext.blank?

      ext
    end
  end
end