require 'responders'
require 'uber/inheritable_attr'
require 'roar/rails/responder'
require 'roar/rails/formats'

module Roar::Rails
  module ControllerAdditions
    extend ActiveSupport::Concern

    included do
      extend Uber::InheritableAttr
      inheritable_attr :represents_options
      self.represents_options ||= Formats.new

      self.responder = Roar::Rails::Responder
    end

    module ClassMethods
      def represents(format, options)
        represents_options.add(format, options)
        respond_to format
      end
    end

    # TODO: move into separate class so we don't pollute controller.
    def consume!(model, options={})
      content_type = request.content_type

      format = Mime::Type.lookup(content_type).try(:symbol) or raise UnsupportedMediaType.new("Cannot consume unregistered media type '#{content_type.inspect}'")

      parsing_method = compute_parsing_method(format)
      representer = prepare_model_for(format, model, options)

      representer.send(parsing_method, incoming_string, options) # e.g. from_json("...")
      model
    end

    def prepare_model_for(format, model, options)
      representer = representer_for(format, model, options)
      representer.prepare(model)
    end

    # Central entry-point for finding the appropriate representer.
    def representer_for(format, model, options={})
      options.delete(:represent_with) || self.class.represents_options.for(format, model, controller_path)
    end

  private
    def compute_parsing_method(format)
      "from_#{format}"
    end

    def incoming_string
      body = request.body
      body.rewind
      body.read
    end

    # These methods deal with interfacing between the Roar Response object and
    # ActionController, they simply pass the body of the Roar response up or do nothing
    def _render_option_json(resource, options)
      super(_resource_or_body(resource), options)
    end

    def _render_option_xml(resource, options)
      super(_resource_or_body(resource), options)
    end

    def _render_option_hal(resource, options)
      super(_resource_or_body(resource), options)
    end

    def _resource_or_body(resource)
      resource.is_a?(Roar::Rails::Responder::Response) ? resource.body : resource
    end

    # Include if you intend to use roar-rails with <tt>render json: model</tt>.
    module Render
      def render(options)
        format = options.keys.first
        status = options[:status] || :ok
        super format => prepare_model_for(format, options.values.first, options), :status => status
      end
    end
  end

  class UnsupportedMediaType < StandardError #:nodoc:
  end
end


require_relative 'label'
require 'prawn'
require 'barby'
require 'barby/barcode/code_39'
require 'barby/outputter/prawn_outputter'

# This module is a wrapper for writing confusing ZPL and ZPL2 code
module Easyzpl
  # This is the label template object
  # This gets uploaded and saved on the printer
  class LabelTemplate < Easyzpl::Label
    attr_accessor :variable_fields_count

    # Called when the new method is invoked
    def initialize(name, params = {})
      return if name.nil?
      return if name.strip.empty?

      # Set the DPIs
      self.pdf_dpi = 72
      self.printer_dpi = params[:dots]

      # Set the field orientation
      self.field_orientation = params[:field_orientation]

      # Set the number of variable fields
      self.variable_fields_count = 0

      # Create the array that will hold the data
      self.label_data = []

      # Set the default quantity to one
      self.quantity = 1

      # See if invert is set to true
      self.invert = params[:invert]

      # The start of the label
      label_data.push('^XA')
      label_data.push('^POI') if invert
      label_data.push('^LT' + Integer(params[:offset] * printer_dpi).to_s) unless params[:offset].nil?
      label_data.push('^LL' + Integer(params[:height] * printer_dpi).to_s) unless params[:height].nil?
      label_data.push('^PW' + Integer(params[:width] * printer_dpi).to_s) unless params[:width].nil?
      label_data.push('^FWB') if field_orientation == :landscape
      label_data.push('^DF' + name + '^FS')

      # init_prawn(params)
    end

    # Sets a variable field that can be recalled
    def variable_text_field(x, y, params = {})
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)
      options = { height: 0.1, width: 0.1 }.merge!(params)

      # update the variable field count
      self.variable_fields_count += 1

      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s)

      if params[:orientation] == :landscape
        label_data.push('^A0N,')
      else
        label_data.push('^A0B,')
      end

      label_data.push(Integer(options[:height] * printer_dpi).to_s + ',' +
                      Integer(options[:width] * printer_dpi).to_s +
                       '^FN' + variable_fields_count.to_s + '^FS')

      # return unless label_height > 0 && label_width > 0
      # pdf.text_box '{Variable Field ' + variable_fields_count.to_s + '}',
      #              at: [Integer(x * pdf_dpi), Integer(label_width * pdf_dpi) -
      #              Integer(y * pdf_dpi) -
      #              Integer(options[:height] / 10) * pdf_dpi],
      #              size: Integer(options[:height] * pdf_dpi) if label_height &&
      #              label_width
    end

    # Sets a variable bar code that can be recalled
    def variable_bar_code_39(x, y, params = {})
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)
      options = { height: 0.1, width: 0.1 }.merge!(params)

      # update the variable field count
      self.variable_fields_count += 1

      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s)
      label_data.push('^BY2,2,100')

      if params[:orientation] == :landscape
        label_data.push('^B3B,')
      else
        label_data.push('^B3N,')
      end

      label_data.push('N,' + Integer(options[:height] * printer_dpi).to_s +
                      ',N,N^FN' + variable_fields_count.to_s + '^FS')

      # return unless label_height && label_width
      # options = { height: 20 }.merge(params)
      # draw_bar_code_39('VARIABLEFIELD' + variable_fields_count.to_s,
      #                  Integer(x * pdf_dpi), Integer(y * pdf_dpi),
      #                  Integer(options[:height] * pdf_dpi))
    end

    # This creates a PDF417 bar code, which is very common in the automotive
    # industry.  The format is as follows:
    # ^B7o,h,s,c,r,t
    # o = Orientation
    #   N - normal, R rotated 90 degrees clockwise, I inverted 180 degrees
    #   B - Read from bottom up 270 degrees
    # h = height for individual rows in dots
    def variable_bar_code_pdf417(x, y, params = {})
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)
      options = { height: 0.1, width: 0.1 }.merge!(params)

      # update the variable field count
      self.variable_fields_count += 1

      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s)

      if params[:orientation] == :landscape
        label_data.push('^B7B,')
      else
        label_data.push('^B7N,')
      end

      label_data.push((printer_dpi / 5).to_s + ',0,' + 5.to_s + ',' + 8.to_s +
                      ',N^FN' + variable_fields_count.to_s + '^FS')
    end

    def variable_qr_code(x, y, params = {})
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)
      options = { size: 'medium' }.merge!(params)

      # update the variable field count
      self.variable_fields_count += 1

      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s)

      if params[:orientation] == :landscape
        label_data.push('^BQB,')
      else
        label_data.push('^BQN,')
      end

      case options[:size]
      when 'small'
        label_data.push('1,5')
      when 'medium'
        label_data.push('2,10')
      when 'large'
        label_data.push('4,20')
      end

      label_data.push('^FN' + variable_fields_count.to_s + '^FS')
    end

    def variable_data_matrix(x, y, params = {})
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)
      options = { size: 'medium' }.merge!(params)

      # update the variable field count
      self.variable_fields_count += 1

      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s)

      if params[:orientation] == :landscape || field_orientation == :landscape
        label_data.push('^BXB,')
      else
        label_data.push('^BXN,')
      end

      case options[:size]
      when 'small'
        label_data.push('10,200')
      when 'medium'
        label_data.push('20,200')
      when 'large'
        label_data.push('40,200')
      end

      label_data.push('^FN' + variable_fields_count.to_s + '^FS')
    end
  end
end


require 'prawn'
require 'barby'
require 'barby/barcode/code_39'
require 'barby/outputter/prawn_outputter'

# This module is a wrapper for writing confusing ZPL and ZPL2 code
module Easyzpl
  # This is the label object
  class Label
    attr_accessor :invert
    attr_accessor :label_data
    attr_accessor :quantity
    attr_accessor :pdf
    attr_accessor :label_width
    attr_accessor :label_height
    attr_accessor :printer_dpi
    attr_accessor :pdf_dpi
    attr_accessor :field_orientation
    attr_accessor :barcode_default_module_width
    attr_accessor :barcode_default_width_ratio
    attr_accessor :barcode_default_height

    # Called when the new method is invoked
    def initialize(params = {})
      # Create the array that will hold the data
      self.label_data = []

      # Set the default quantity to one
      self.quantity = 1

      # Set the DPIs
      self.pdf_dpi = 72
      self.printer_dpi = params[:dots]

      # Set the field orientation
      self.field_orientation = params[:field_orientation]

      # See if invert is set to true
      self.invert = params[:invert]

      # Set barcode defaults.
      self.barcode_default_module_width = numeric?(params[:barcode_default_module_width]) ?
                                          params[:barcode_default_module_width] : 2
      self.barcode_default_width_ratio  = float?(params[:barcode_default_width_ratio]) ?
                                          params[:barcode_default_width_ratio] : 3.0
      self.barcode_default_height       = numeric?(params[:barcode_default_height]) ?
                                          params[:barcode_default_height] : 10

      # The start of the label
      label_data.push('^XA')
      label_data.push('^POI') if invert
      label_data.push('^LT' + Integer(params[:offset] * printer_dpi).to_s) unless params[:offset].nil?
      label_data.push('^LL' + Integer(params[:height] * printer_dpi).to_s) unless params[:height].nil?
      label_data.push('^PW' + Integer(params[:width] * printer_dpi).to_s) unless params[:width].nil?
      label_data.push('^FWB') if field_orientation == :landscape

      # Initialize Prawn
      # init_prawn(params)
    end

    # Set the number of labels to print
    def change_quantity(q)
      q = 1 unless numeric?(q)
      self.quantity = q
    end

    # Set the home position of the label
    # All other X and Y coordinates are
    # relative to this
    def home_position(x, y)
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)
      label_data.push('^LH' + x.to_s + ',' + y.to_s)
    end

    # Draws a square border on dot in width
    def draw_border(x, y, height, width)
      return unless numeric?(height) && numeric?(width)
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)

      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s + '^GB' +
                      Integer(height * printer_dpi).to_s +
                      ',' + Integer(width * printer_dpi).to_s + ',1^FS')

      # draw_rectangle(x * pdf_dpi, y * pdf_dpi, height * pdf_dpi, width * pdf_dpi)
    end

    # Prints text
    def text_field(text, x, y, params = {})
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)
      options = { height: 0.1,
                  width: 0.1 }.merge!(params)
      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' + Integer(y *
                      printer_dpi).to_s)

      if params[:orientation] == :landscape
        label_data.push('^A0N,')
      else
        label_data.push('^A0B,')
      end

      label_data.push(Integer(options[:height] * printer_dpi).to_s + ',' +
                      Integer(options[:width] * printer_dpi).to_s + '^FD' +
                      text + '^FS')

      # return unless label_height > 0 && label_width > 0
      # pdf.text_box text, at: [x, label_width - y -
      #                    Integer((options[:height] * pdf_dpi) / 10)],
      #                    size: (options[:height] *
      #                    pdf_dpi) if label_height && label_width
    end

    # Prints a bar code in barcode39 font
    def bar_code_39(bar_code_string, x, y, params = {})
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)
      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s + '^B3N,N,20,N,N^FD' +
                      bar_code_string + '^FS')

      # return unless label_height && label_width
      # options = { height: 20 }.merge!(params) { |key, v1, v2| v1 }
      # draw_bar_code_39(bar_code_string, Integer(x * pdf_dpi),
      #                  Integer(y * pdf_dpi), (options[:height] * pdf_dpi))
    end

    # Prints a bar code in barcode39 font
    def bar_code_128(bar_code_string, x, y, params = {})
      x                       = 0 unless numeric?(x)
      y                       = 0 unless numeric?(y)
      height                  = numeric?(params[:height]) ? params[:height] : 0.2
      interpretation          = params[:interpretation] == :true ? 'Y' : 'N'
      interpretation_location = params[:interpretation_location] == :above ? 'Y' : 'N'
      check_digit             = params[:check_digit] == :true ? 'Y' : 'N'
      mode                    = { :ucc_case => 'U',
                                  :auto     => 'A',
                                  :ucc_ean  => 'D' }[params[:mode]] || 'N'
      orientation             = { :portrait => 'R',
                                  90        => 'R',
                                  180       => 'I',
                                  270       => 'B' }[params[:orientation]] || 'N'
      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s + '^BC' +
                      orientation + ',' +
                      Integer(height* printer_dpi).to_s + ',' +
                      interpretation + ',' +
                      interpretation_location + ',' +
                      check_digit + ',' +
                      mode + '^FD' +
                      bar_code_string + '^FS')

      # return unless label_height && label_width
      # options = { height: 20 }.merge!(params) { |key, v1, v2| v1 }
      # draw_bar_code128_(bar_code_string, Integer(x * pdf_dpi),
      #                   Integer(y * pdf_dpi), (options[:height] * pdf_dpi))
    end

    # Prints a bar code in barcode39 font
    def bar_code_qr(bar_code_string, x, y, params = {})
      x                = 0 unless numeric?(x)
      y                = 0 unless numeric?(y)
      magnification    = numeric?(params[:magnification]) ? params[:magnification] : default_qr_code_magnification
      error_correction = { :ultra    => 'H',
                           :high     => 'Q',
                           :standard => 'M',
                           :density  => 'L' }[params[:error_correction]] || (params[:error_correction]).nil? ? 'Q' : 'M'
      mask             = numeric?(params[:mask]) ? params[:mask] : 7
      model            = { 1          => 1,
                           :standard  => 1,
                           2          => 2,
                           :enhanced  => 2 }[params[:model]] || 2
      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s + '^BQN,' +
                      Integer(model).to_s + ',' +
                      Integer(magnification).to_s + ',' +
                      error_correction + ',' +
                      Integer(mask).to_s + '^FD' + error_correction + 'A,' +
                      bar_code_string + '^FS')

      # return unless label_height && label_width
      # options = { height: 20 }.merge!(params) { |key, v1, v2| v1 }
      # draw_bar_code128_(bar_code_string, Integer(x * pdf_dpi),
      #                   Integer(y * pdf_dpi), (options[:height] * pdf_dpi))
    end

    # Prints a bar code in pdf417 font
    def bar_code_pdf417(bar_code_string, x, y, params = {})
      x = 0 unless numeric?(x)
      y = 0 unless numeric?(y)
      label_data.push('^FO' + Integer(x * printer_dpi).to_s + ',' +
                      Integer(y * printer_dpi).to_s + '^B7N,Y,20,N,N^FD' +
                      bar_code_string + '^FS')

      # return unless label_height && label_width
      # options = { height: 20 }.merge!(params)
      # draw_bar_code_39(bar_code_string, Integer(x * pdf_dpi),
      #                  Integer(y * pdf_dpi), (options[:height] * pdf_dpi))
    end

    # Some barcodes, such as QR codes may change document defaults.  These may be reset
    # to the document defaults.
    def reset_barcode_fields_to_default
      label_data.push('^BY' + Integer(self.barcode_default_module_width).to_s + ',' +
                      Float(self.barcode_default_width_ratio).to_s + ',' +
                      Integer(self.barcode_default_height).to_s)
    end

    # Renders the ZPL code as a string
    def to_s
      return '' unless label_data.length > 0
      label_data.map! { |l| "#{l}" }.join('') + '^PQ' + quantity.to_s + '^XZ'
    end

    def to_pdf(filename)
      return unless label_height && label_width
      pdf.render_file(filename)
    end

    protected

    def default_qr_code_magnification
      if self.printer_dpi < 100
        1
      elsif self.printer_dpi >= 1000
        10
      else
        (self.printer_dpi / 100).floor
      end
    end

    def init_prawn(params)
      self.label_width = (params[:width] * pdf_dpi) || 0
      self.label_height = (params[:height] * pdf_dpi) || 0

      return unless label_height > 0 && label_width > 0
      self.pdf = Prawn::Document.new
    end

    # Returns true if a variable is number, false if not
    def numeric?(variable)
      true if Integer(variable) rescue false
    end

    def float?(variable)
      true if Float(variable) rescue false
    end

    # Draws the PDF rectangle (border)
    def draw_rectangle(x, y, height, width)
      return unless label_height > 0 && label_width > 0
      pdf.stroke_axis
      pdf.stroke do
        pdf.rectangle [x * pdf_dpi, label_width - (y * pdf_dpi) -
                      (width * pdf_dpi)], height,
                      (width * pdf_dpi) * -1
      end
    end

    # Draws the PDF bar code 39
    def draw_bar_code_39(bar_code_string, x, y, height)
      return unless label_height > 0 && label_width > 0
      pdf.bounding_box [x, Integer(label_width) - y - (height * pdf_dpi)],
                       width: (height * pdf_dpi) do
        barcode = Barby::Code39.new(bar_code_string)
        barcode.annotate_pdf(pdf, height: (height * pdf_dpi))
      end
    end
  end
end


require_relative 'label'

# This module is a wrapper for writing confusing ZPL and ZPL2 code
module Easyzpl
  # This is the stored label object
  class StoredLabel < Easyzpl::Label
    attr_accessor :variable_fields_count

    # Called when the new method is invoked
    def initialize(name)
      return if name.nil?
      return if name.strip.empty?

      # Set the number of variable fields
      self.variable_fields_count = 0

      # Create the array that will hold the data
      self.label_data = []

      # Set the default quantity to one
      self.quantity = 1

      # The start of the label
      label_data.push('^XA^XF' + name + '^FS')
    end

    # Adds a variable that is to be applied to the saved template
    def add_field(value)
      return if value.nil?
      return if value.strip.empty?

      # Increment the variable field count
      self.variable_fields_count += 1

      # Add the field
      label_data.push('^FN' + variable_fields_count.to_s +
                      '^FD' + value + '^FS')
    end
  end
end


require 'mongoid'
require 'stringex'
require 'mongoid/slug/criteria'
require 'mongoid/slug/index'
require 'mongoid/slug/unique_slug'
require 'mongoid/slug/slug_id_strategy'
require 'mongoid-compatibility'
require 'mongoid/slug/railtie' if defined?(Rails)

module Mongoid
  # Slugs your Mongoid model.
  module Slug
    extend ActiveSupport::Concern

    MONGO_INDEX_KEY_LIMIT_BYTES = 1024

    included do
      cattr_accessor :slug_reserved_words,
                     :slug_scope,
                     :slugged_attributes,
                     :slug_url_builder,
                     :slug_history,
                     :slug_by_model_type,
                     :slug_max_length

      # field :_slugs, type: Array, default: [], localize: false
      # alias_attribute :slugs, :_slugs
    end

    class << self
      attr_accessor :default_slug
      def configure(&block)
        instance_eval(&block)
      end

      def slug(&block)
        @default_slug = block if block_given?
      end
    end

    module ClassMethods
      # @overload slug(*fields)
      #   Sets one ore more fields as source of slug.
      #   @param [Array] fields One or more fields the slug should be based on.
      #   @yield If given, the block is used to build a custom slug.
      #
      # @overload slug(*fields, options)
      #   Sets one ore more fields as source of slug.
      #   @param [Array] fields One or more fields the slug should be based on.
      #   @param [Hash] options
      #   @param options [Boolean] :history Whether a history of changes to
      #   the slug should be retained. When searched by slug, the document now
      #   matches both past and present slugs.
      #   @param options [Boolean] :permanent Whether the slug should be
      #   immutable. Defaults to `false`.
      #   @param options [Array] :reserve` A list of reserved slugs
      #   @param options :scope [Symbol] a reference association or field to
      #   scope the slug by. Embedded documents are, by default, scoped by
      #   their parent.
      #   @param options :max_length [Integer] the maximum length of the text portion of the slug
      #   @yield If given, a block is used to build a slug.
      #
      # @example A custom builder
      #   class Person
      #     include Mongoid::Document
      #     include Mongoid::Slug
      #
      #     field :names, :type => Array
      #     slug :names do |doc|
      #       doc.names.join(' ')
      #     end
      #   end
      #
      def slug(*fields, &block)
        options = fields.extract_options!

        self.slug_scope            = options[:scope]
        self.slug_reserved_words   = options[:reserve] || Set.new(%w[new edit])
        self.slugged_attributes    = fields.map(&:to_s)
        self.slug_history          = options[:history]
        self.slug_by_model_type    = options[:by_model_type]
        self.slug_max_length       = options.key?(:max_length) ? options[:max_length] : MONGO_INDEX_KEY_LIMIT_BYTES - 32

        field :_slugs, type: Array, localize: options[:localize]
        alias_attribute :slugs, :_slugs

        # Set index
        index(*Mongoid::Slug::Index.build_index(slug_scope_key, slug_by_model_type)) unless embedded?

        self.slug_url_builder = block_given? ? block : default_slug_url_builder

        #-- always create slug on create
        #-- do not create new slug on update if the slug is permanent
        if options[:permanent]
          set_callback :create, :before, :build_slug
        else
          set_callback :save, :before, :build_slug, if: :slug_should_be_rebuilt?
        end
      end

      def default_slug_url_builder
        Mongoid::Slug.default_slug || ->(cur_object) { cur_object.slug_builder.to_url }
      end

      def look_like_slugs?(*args)
        with_default_scope.look_like_slugs?(*args)
      end

      # Returns the scope key for indexing, considering associations
      #
      # @return [ Array<Document>, Document ]
      def slug_scope_key
        return nil unless slug_scope
        reflect_on_association(slug_scope).try(:key) || slug_scope
      end

      # Find documents by slugs.
      #
      # A document matches if any of its slugs match one of the supplied params.
      #
      # A document matching multiple supplied params will be returned only once.
      #
      # If any supplied param does not match a document a Mongoid::Errors::DocumentNotFound will be raised.
      #
      # @example Find by a slug.
      #   Model.find_by_slug!('some-slug')
      #
      # @example Find by multiple slugs.
      #   Model.find_by_slug!('some-slug', 'some-other-slug')
      #
      # @param [ Array<Object> ] args The slugs to search for.
      #
      # @return [ Array<Document>, Document ] The matching document(s).
      def find_by_slug!(*args)
        with_default_scope.find_by_slug!(*args)
      end

      def queryable
        current_scope || Criteria.new(self) # Use Mongoid::Slug::Criteria for slugged documents.
      end

      private

      if Mongoid::Compatibility::Version.mongoid5_or_newer? && Threaded.method(:current_scope).arity == -1
        def current_scope
          Threaded.current_scope(self)
        end
      elsif Mongoid::Compatibility::Version.mongoid5_or_newer?
        def current_scope
          Threaded.current_scope
        end
      else
        def current_scope
          scope_stack.last
        end
      end
    end

    # Builds a new slug.
    #
    # @return [true]
    def build_slug
      if localized?
        begin
          orig_locale = I18n.locale
          all_locales.each do |target_locale|
            I18n.locale = target_locale
            apply_slug
          end
        ensure
          I18n.locale = orig_locale
        end
      else
        apply_slug
      end
      true
    end

    def apply_slug
      new_slug = find_unique_slug

      # skip slug generation and use Mongoid id
      # to find document instead
      return true if new_slug.size.zero?

      # avoid duplicate slugs
      _slugs.delete(new_slug) if _slugs

      if !!slug_history && _slugs.is_a?(Array)
        append_slug(new_slug)
      else
        self._slugs = [new_slug]
      end
    end

    # Builds slug then atomically sets it in the database.
    #
    # This method is adapted to use the :set method variants from both
    # Mongoid 3 (two args) and Mongoid 4 (hash arg)
    def set_slug!
      build_slug
      method(:set).arity == 1 ? set(_slugs: _slugs) : set(:_slugs, _slugs)
    end

    # Atomically unsets the slug field in the database. It is important to unset
    # the field for the sparse index on slugs.
    #
    # This also resets the in-memory value of the slug field to its default (empty array)
    def unset_slug!
      unset(:_slugs)
      clear_slug!
    end

    # Rolls back the slug value from the Mongoid changeset.
    def reset_slug!
      reset__slugs!
    end

    # Sets the slug to its default value.
    def clear_slug!
      self._slugs = []
    end

    # Finds a unique slug, were specified string used to generate a slug.
    #
    # Returned slug will the same as the specified string when there are no
    # duplicates.
    #
    # @return [String] A unique slug
    def find_unique_slug
      UniqueSlug.new(self).find_unique
    end

    # @return [Boolean] Whether the slug requires to be rebuilt
    def slug_should_be_rebuilt?
      new_record? || _slugs_changed? || slugged_attributes_changed?
    end

    def slugged_attributes_changed?
      slugged_attributes.any? { |f| attribute_changed? f.to_s }
    end

    # @return [String] A string which Action Pack uses for constructing an URL
    # to this record.
    def to_param
      slug || super
    end

    # @return [String] the slug, or nil if the document does not have a slug.
    def slug
      return _slugs.last if _slugs
      _id.to_s
    end

    def slug_builder
      cur_slug = nil
      if new_with_slugs? || persisted_with_slug_changes?
        # user defined slug
        cur_slug = _slugs.last
      end
      # generate slug if the slug is not user defined or does not exist
      cur_slug || pre_slug_string
    end

    private

    def append_slug(value)
      if localized?
        # This is necessary for the scenario in which the slugged locale is not yet present
        # but the default locale is. In this situation, self._slugs falls back to the default
        # which is undesired
        current_slugs = _slugs_translations.fetch(I18n.locale.to_s, [])
        current_slugs << value
        self._slugs_translations = _slugs_translations.merge(I18n.locale.to_s => current_slugs)
      else
        _slugs << value
      end
    end

    # Returns true if object is a new record and slugs are present
    def new_with_slugs?
      if localized?
        # We need to check if slugs are present for the locale without falling back
        # to a default
        new_record? && _slugs_translations.fetch(I18n.locale.to_s, []).any?
      else
        new_record? && _slugs.present?
      end
    end

    # Returns true if object has been persisted and has changes in the slug
    def persisted_with_slug_changes?
      if localized?
        changes = _slugs_change
        return (persisted? && false) if changes.nil?

        # ensure we check for changes only between the same locale
        original = changes.first.try(:fetch, I18n.locale.to_s, nil)
        compare = changes.last.try(:fetch, I18n.locale.to_s, nil)
        persisted? && original != compare
      else
        persisted? && _slugs_changed?
      end
    end

    def localized?
      fields['_slugs'].options[:localize]
    rescue StandardError
      false
    end

    # Return all possible locales for model
    # Avoiding usage of I18n.available_locales in case the user hasn't set it properly, or is
    # doing something crazy, but at the same time we need a fallback in case the model doesn't
    # have any localized attributes at all (extreme edge case).
    def all_locales
      locales = slugged_attributes
                .map { |attr| send("#{attr}_translations").keys if respond_to?("#{attr}_translations") }
                .flatten.compact.uniq
      locales = I18n.available_locales if locales.empty?
      locales
    end

    def pre_slug_string
      slugged_attributes.map { |f| send f }.join ' '
    end
  end
end


require 'securerandom'

module RequestStoreRails

  # Public: Middleware that takes care of setting the thread-local variable
  # :request_id, which enables RequestLocals to associate threads and requests.
  class Middleware

    def initialize(app)
      @app = app
    end

    # Internal: Assigns the :request_id thread-local variable, and cleans up all
    # the request-local variables after the request.
    def call(env)
      Thread.current[:request_id] = extract_request_id(env)
      @app.call(env)
    ensure
      RequestLocals.clear!
      Thread.current[:request_id] = nil
    end

  protected

    # Internal: Extracts the request id from the environment, or generates one.
    def extract_request_id(env)
      env['action_dispatch.request_id'] || env['HTTP_X_REQUEST_ID'] || SecureRandom.hex(16)
    end
  end
end


module ETL
  module Util
    # Return the distance of time in words from the given from_time to the specified to_time. If to_time
    # is not specified then Time.now is used. By default seconds are included...set the include_seconds
    # argument to false to disable the seconds.
    def distance_of_time_in_words(from_time, to_time=Time.now)
      from_time = from_time.to_time if from_time.respond_to?(:to_time)
      to_time = to_time.to_time if to_time.respond_to?(:to_time)
      seconds = (to_time - from_time).round
      distance_in_days = (seconds/(60*60*24)).round
      seconds = seconds % (60*60*24)
      distance_in_hours = (seconds/(60*60)).round
      seconds = seconds % (60*60)
      distance_in_minutes = (seconds/60).round
      seconds = seconds % 60
      distance_in_seconds = seconds

      s = ''
      s << "#{distance_in_days} days," if distance_in_days > 0
      s << "#{distance_in_hours} hours, " if distance_in_hours > 0
      s << "#{distance_in_minutes} minutes, " if distance_in_minutes > 0
      s << "#{distance_in_seconds} seconds"
      s
    end

    # Get the approximate disntance of time in words from the given from_time
    # to the the given to_time. If to_time is not specified then it is set
    # to Time.now. By default seconds are included...set the include_seconds
    # argument to false to disable the seconds.
    def approximate_distance_of_time_in_words(from_time, to_time=Time.now, include_seconds=true)
      from_time = from_time.to_time if from_time.respond_to?(:to_time)
      to_time = to_time.to_time if to_time.respond_to?(:to_time)
      distance_in_minutes = (((to_time - from_time).abs)/60).round
      distance_in_seconds = ((to_time - from_time).abs).round

      case distance_in_minutes
        when 0..1
          return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds
        case distance_in_seconds
          when 0..4   then 'less than 5 seconds'
          when 5..9   then 'less than 10 seconds'
          when 10..19 then 'less than 20 seconds'
          when 20..39 then 'half a minute'
          when 40..59 then 'less than a minute'
          else             '1 minute'
        end
        when 2..44           then "#{distance_in_minutes} minutes"
        when 45..89          then 'about 1 hour'
        when 90..1439        then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
        when 1440..2879      then '1 day'
        when 2880..43199     then "#{(distance_in_minutes / 1440).round} days"
        when 43200..86399    then 'about 1 month'
        when 86400..525959   then "#{(distance_in_minutes / 43200).round} months"
        when 525960..1051919 then 'about 1 year'
        else                      "over #{(distance_in_minutes / 525960).round} years"
      end
    end
  end
end


module ETL #:nodoc:

  class Base < ActiveRecord::Base
  end

  # The main ETL engine clas
  class Engine
    include ETL::Util

    class << self
      # Initialization that is run when a job is executed.
      #
      # Options:
      # * <tt>:limit</tt>: Limit the number of records returned from sources
      # * <tt>:offset</tt>: Specify the records for data from sources
      # * <tt>:log_write_mode</tt>: If true then the log will write, otherwise it will append
      # * <tt>:skip_bulk_import</tt>: Set to true to skip bulk import
      # * <tt>:read_locally</tt>: Set to true to read from the local cache
      # * <tt>:rails_root</tt>: Set to the rails root to boot rails
      def init(options={})
        unless @initialized
          puts "initializing ETL engine\n\n"
          @limit = options[:limit]
          @offset = options[:offset]
          @log_write_mode = 'w' if options[:newlog]
          @skip_bulk_import = options[:skip_bulk_import]
          @read_locally = options[:read_locally]
          @rails_root = options[:rails_root]

          require File.join(@rails_root, 'config/environment') if @rails_root
          options[:config] ||= 'database.yml'
          options[:config] = 'config/database.yml' unless File.exist?(options[:config])
          database_configuration = YAML::load(ERB.new(IO.read(options[:config])).result + "\n")
          ActiveRecord::Base.configurations.merge!(database_configuration)
          ETL::Base.configurations = HashWithIndifferentAccess.new(database_configuration)
          #puts "configurations in init: #{ActiveRecord::Base.configurations.inspect}"

          require 'etl/execution'
          ETL::Execution::Base.establish_connection :etl_execution
          ETL::Execution::Execution.migrate

          @initialized = true
        end
      end

      # Process the specified file. Acceptable values for file are:
      # * Path to a file
      # * File object
      # * ETL::Control::Control instance
      # * ETL::Batch::Batch instance
      #
      # The process command will accept either a .ctl or .ctl.rb for a Control file or a .ebf
      #  or .ebf.rb for an ETL Batch File.
      def process(file)
        new().process(file)
      end

      attr_accessor :timestamped_log

      # Accessor for the log write mode. Default is 'a' for append.
      attr_accessor :log_write_mode
      def log_write_mode
        @log_write_mode ||= 'a'
      end

      # A logger for the engine
      attr_accessor :logger

      def logger #:nodoc:
        unless @logger
          if timestamped_log
            @logger = Logger.new("etl_#{timestamp}.log")
          else
            @logger = Logger.new(File.open('etl.log', log_write_mode))
          end
          @logger.level = Logger::WARN
          @logger.formatter = Logger::Formatter.new
        end
        @logger
      end

      # Get a timestamp value as a string
      def timestamp
        Time.now.strftime("%Y%m%d%H%M%S")
      end

      # exit code to be passed to the command line
      attr_accessor :exit_code

      # The current source
      attr_accessor :current_source

      # The current source row
      attr_accessor :current_source_row

      # The current destination
      attr_accessor :current_destination

      # Set to true to activate realtime activity. This will cause certain
      # information messages to be printed to STDOUT
      attr_accessor :realtime_activity

      # Accessor for the total number of rows read from sources
      attr_accessor :rows_read
      def rows_read
        @rows_read ||= 0
      end

      # Accessor for the total number of rows processed
      attr_accessor :rows_written
      def rows_written
        @rows_written ||= 0
      end

      # Access the current ETL::Execution::Job instance
      attr_accessor :job

      # Access the current ETL::Execution::Batch instance
      attr_accessor :batch

      # The limit on rows to load from the source, useful for testing the ETL
      # process prior to executing the entire batch. Default value is nil and
      # indicates that there is no limit
      attr_accessor :limit

      # The offset for the source to begin at, useful for testing the ETL
      # process prior to executing the entire batch. Default value is nil and
      # indicates that there is no offset
      attr_accessor :offset

      # Set to true to skip all bulk importing
      attr_accessor :skip_bulk_import

      # Set to true to read locally from the last source cache files
      attr_accessor :read_locally

      # Accessor for the average rows per second processed
      attr_accessor :average_rows_per_second

      # Get a named connection
      def connection(name)
        logger.debug "Retrieving connection #{name}"
        conn = connections[name] ||= establish_connection(name)
        #conn.verify!(ActiveRecord::Base.verification_timeout)
        conn.reconnect! unless conn.active?
        conn
      end

      # Set to true to use temp tables
      attr_accessor :use_temp_tables

      # Get a registry of temp tables
      def temp_tables
        @temp_tables ||= {}
      end

      # Called when a batch job finishes, allowing for cleanup to occur
      def finish
        temp_tables.each do |temp_table, mapping|
          actual_table = mapping[:table]
          #puts "move #{temp_table} to #{actual_table}"
          conn = mapping[:connection]
          conn.transaction do
            conn.rename_table(actual_table, "#{actual_table}_old")
            conn.rename_table(temp_table, actual_table)
            conn.drop_table("#{actual_table}_old")
          end
        end
      end

      # Return true if using temp tables
      def use_temp_tables?
        use_temp_tables ? true : false
      end

      # Modify the table name if necessary
      def table(table_name, connection)
        if use_temp_tables?
          temp_table_name = "tmp_#{table_name}"

          if temp_tables[temp_table_name].nil?
            # Create the temp table and add it to the mapping
            begin connection.drop_table(temp_table_name); rescue; end
            connection.copy_table(table_name, temp_table_name)
            temp_tables[temp_table_name] = {
              :table => table_name,
              :connection => connection
            }
          end

          temp_table_name
        else
          table_name
        end
      end

      protected
      # Hash of database connections that can be used throughout the ETL
      # process
      def connections
        @connections ||= {}
      end

      # Establish the named connection and return the database specific connection
      def establish_connection(name)
        raise ETL::ETLError, "Connection with no name requested. Is there a missing :target parameter somewhere?" if name.blank?

        logger.debug "Establishing connection to #{name}"
        conn_config = ETL::Base.configurations[name.to_s]
        raise ETL::ETLError, "Cannot find connection named #{name.inspect}" unless conn_config
        connection_method = "#{conn_config['adapter']}_connection"
        ETL::Base.send(connection_method, conn_config)
      end
    end # class << self

    # Say the specified message, with a newline
    def say(message)
      say_without_newline(message + "\n")
    end

    # Say the specified message without a newline
    def say_without_newline(message)
      if ETL::Engine.realtime_activity
        $stdout.print message
        $stdout.flush
      end
    end

    # Say the message on its own line
    def say_on_own_line(message)
      say("\n" + message)
    end

    # Array of errors encountered during execution of the ETL process
    def errors
      @errors ||= []
    end

    # First attempt at centralizing error notifications
    def track_error(control, msg)
      errors << msg
      control.error_handlers.each do |handler|
        handler.call(msg)
      end
    end

    # Get a Hash of benchmark values where each value represents the total
    # amount of time in seconds spent processing in that portion of the ETL
    # pipeline. Keys include:
    # * <tt>:transforms</tt>
    # * <tt>:after_reads</tt>
    # * <tt>:before_writes</tt>
    # * <tt>:writes</tt>
    def benchmarks
      @benchmarks ||= {
        :transforms => 0,
        :after_reads => 0,
        :before_writes => 0,
        :writes => 0,
      }
    end

    # Process a file, control object or batch object. Acceptable values for
    # file are:
    # * Path to a file
    # * File object
    # * ETL::Control::Control instance
    # * ETL::Batch::Batch instance
    def process(file)
      case file
        when String
          process(File.new(file))
        when File
          case file.path
            when /\.ctl(\.rb)?$/; process_control(file)
            when /\.ebf(\.rb)?$/; process_batch(file)
            else raise RuntimeError, "Unsupported file type - #{file.path}"
          end
        when ETL::Control::Control
          process_control(file)
        when ETL::Batch::Batch
          process_batch(file)
      else
        raise RuntimeError, "Process object must be a String, File, Control
        instance or Batch instance"
      end
    end

    protected
    # Process the specified batch file
    def process_batch(batch)
      batch = ETL::Batch::Batch.resolve(batch, self)
      say "Processing batch #{batch.file}"

      ETL::Engine.batch = ETL::Execution::Batch.create!(
        :batch_file => batch.file,
        :status => 'executing'
      )

      batch.execute

      ETL::Engine.batch.completed_at = Time.now
      ETL::Engine.batch.status = (errors.length > 0 ? 'completed with errors' : 'completed')
      ETL::Engine.batch.save!
    end

    # Process the specified control file
    def process_control(control)
      control = ETL::Control::Control.resolve(control)
      say_on_own_line "Processing control #{control.file}"

      ETL::Engine.job = ETL::Execution::Job.new.tap do |job|
        job.control_file = control.file
        job.status = 'executing'
        job.batch_id = ETL::Engine.batch ? ETL::Engine.batch.id : nil
        job.save!
      end

      execute_dependencies(control)

      start_time = Time.now
      pre_process(control)
      sources = control.sources
      destinations = control.destinations

      say "Skipping bulk import" if Engine.skip_bulk_import

      sources.each do |source|
        Engine.current_source = source
        Engine.logger.debug "Processing source #{source.inspect}"
        say "Source: #{source}"
        say "Limiting enabled: #{Engine.limit}" if Engine.limit != nil
        say "Offset enabled: #{Engine.offset}" if Engine.offset != nil
        source.each_with_index do |row, index|
          # Break out of the row loop if the +Engine.limit+ is specified and
          # the number of rows read exceeds that value.
          if Engine.limit != nil && Engine.rows_read >= Engine.limit
            puts "Reached limit of #{Engine.limit}"
            break
          end

          Engine.logger.debug "Row #{index}: #{row.inspect}"
          Engine.rows_read += 1
          Engine.current_source_row = index + 1
          say_without_newline "." if Engine.realtime_activity && index > 0 && index % 1000 == 0

          # At this point a single row may be turned into multiple rows via row
          # processors all code after this line should work with the array of
          # rows rather than the single row
          rows = [row]

          t = Benchmark.realtime do
            begin
              Engine.logger.debug "Processing after read"
              control.after_read_processors.each do |processor|
                processed_rows = []
                rows.each do |row|
                  processed_rows << processor.process(row) unless empty_row?(row)
                end
                rows = processed_rows.flatten.compact
              end
            rescue => e
              msg = "Error processing rows after read from #{Engine.current_source} on line #{Engine.current_source_row}: #{e}"
              # TODO - track more information: row if possible, full exception...
              track_error(control, msg)
              Engine.logger.error(msg)
              e.backtrace.each { |line| Engine.logger.error(line) }
              exceeded_error_threshold?(control) ? break : next
            end
          end
          benchmarks[:after_reads] += t unless t.nil?

          t = Benchmark.realtime do
            begin
              Engine.logger.debug "Executing transforms"
              rows.each do |row|
                # only do the transform if there is a row
                unless empty_row?(row)
                  control.transforms.each do |transform|
                    name = transform.name.to_sym
                    row[name] = transform.transform(name, row[name], row)
                  end
                end
              end
            rescue ResolverError => e
              Engine.logger.error(e.message)
              track_error(control, e.message)
            rescue => e
              msg = "Error transforming from #{Engine.current_source} on line #{Engine.current_source_row}: #{e}"
              track_error(control, msg)
              Engine.logger.error(msg)
              e.backtrace.each { |line| Engine.logger.error(line) }
            ensure
              begin
                exceeded_error_threshold?(control) ? break : next
              rescue => inner_error
                puts inner_error
              end
            end
          end
          benchmarks[:transforms] += t unless t.nil?

          t = Benchmark.realtime do
            begin
              # execute row-level "before write" processing
              Engine.logger.debug "Processing before write"
              control.before_write_processors.each do |processor|
                processed_rows = []
                rows.each do |row|
                  processed_rows << processor.process(row) unless empty_row?(row)
                end
                rows = processed_rows.flatten.compact
              end
            rescue => e
              msg = "Error processing rows before write from #{Engine.current_source} on line #{Engine.current_source_row}: #{e}"
              track_error(control, msg)
              Engine.logger.error(msg)
              e.backtrace.each { |line| Engine.logger.error(line) }
              exceeded_error_threshold?(control) ? break : next
            end
          end
          benchmarks[:before_writes] += t unless t.nil?

          t = Benchmark.realtime do
            begin
              # write the row to the destination
              destinations.each_with_index do |destination, index|
                Engine.current_destination = destination
                rows.each do |row|
                  destination.write(row)
                  Engine.rows_written += 1 if index == 0
                end
              end
            rescue => e
              msg = "Error writing to #{Engine.current_destination}: #{e}"
              track_error(control, msg)
              Engine.logger.error msg
              e.backtrace.each { |line| Engine.logger.error(line) }
              exceeded_error_threshold?(control) ? break : next
            end
          end
          benchmarks[:writes] += t unless t.nil?
        end

        if exceeded_error_threshold?(control)
          say_on_own_line "Exiting due to exceeding error threshold: #{control.error_threshold}"
          ETL::Engine.exit_code = 1
        end

      end

      destinations.each do |destination|
        destination.close
      end

      say_on_own_line "Executing before post-process screens"
      begin
        execute_screens(control)
      rescue FatalScreenError => e
        say "Fatal screen error during job execution: #{e.message}"
        ETL::Engine.exit_code = 2
      rescue ScreenError => e
        say "Screen error during job execution: #{e.message}"
        return
      else
        say "Screens passed"
      end

      post_process(control)

      if sources.length > 0
        say_on_own_line "Read #{Engine.rows_read} lines from sources"
      end
      if destinations.length > 0
        say "Wrote #{Engine.rows_written} lines to destinations"
      end

      say_on_own_line "Executing after post-process screens"
      begin
        execute_screens(control, :after_post_process)
      rescue FatalScreenError => e
        say "Fatal screen error during job execution: #{e.message}"
        ETL::Engine.exit_code = 3
      rescue ScreenError => e
        say "Screen error during job execution: #{e.message}"
        return
      else
        say "Screens passed"
      end

      say_on_own_line "Completed #{control.file} in #{distance_of_time_in_words(start_time)} with #{errors.length} errors."
      say "Processing average: #{Engine.average_rows_per_second} rows/sec)"

      say "Avg after_reads: #{Engine.rows_read/benchmarks[:after_reads]} rows/sec" if benchmarks[:after_reads] > 0
      say "Avg before_writes: #{Engine.rows_read/benchmarks[:before_writes]} rows/sec" if benchmarks[:before_writes] > 0
      say "Avg transforms: #{Engine.rows_read/benchmarks[:transforms]} rows/sec" if benchmarks[:transforms] > 0
      say "Avg writes: #{Engine.rows_read/benchmarks[:writes]} rows/sec" if benchmarks[:writes] > 0

      # say "Avg time writing execution records: #{ETL::Execution::Record.average_time_spent}"
      #
      # ETL::Transform::Transform.benchmarks.each do |klass, t|
#         say "Avg #{klass}: #{Engine.rows_read/t} rows/sec"
#       end

      ActiveRecord::Base.verify_active_connections!
      ETL::Engine.job.completed_at = Time.now
      ETL::Engine.job.status = (errors.length > 0 ? 'completed with errors' : 'completed')
      ETL::Engine.job.save!
    end

    def empty_row?(row)
      # unsure about why it should respond to :[] - keeping it just in case for the moment
      row.nil? || !row.respond_to?(:[])
    end

    private
    # Return true if the error threshold is exceeded
    def exceeded_error_threshold?(control)
      errors.length > control.error_threshold
    end

    # Execute all preprocessors
    def pre_process(control)
      Engine.logger.debug "Pre-processing #{control.file}"
      control.pre_processors.each do |processor|
        processor.process
      end
      Engine.logger.debug "Pre-processing complete"
    end

    # Execute all postprocessors
    def post_process(control)
      say_on_own_line "Executing post processes"
      Engine.logger.debug "Post-processing #{control.file}"
      control.post_processors.each do |processor|
        processor.process
      end
      Engine.logger.debug "Post-processing complete"
      say "Post-processing complete"
    end

    # Execute all dependencies
    def execute_dependencies(control)
      Engine.logger.debug "Executing dependencies"
      control.dependencies.flatten.each do |dependency|
        case dependency
        when Symbol
          f = dependency.to_s + '.ctl'
          Engine.logger.debug "Executing dependency: #{f}"
          say "Executing dependency: #{f}"
          process(f)
        when String
          Engine.logger.debug "Executing dependency: #{f}"
          say "Executing dependency: #{f}"
          process(dependency)
        else
          raise "Invalid dependency type: #{dependency.class}"
        end
      end
    end

    # Execute all screens
    def execute_screens(control, timing = :before_post_process)
      screens = case timing
        when :after_post_process
          control.after_post_process_screens
        else # default to before post-process screens
          control.screens
        end
      [:fatal,:error,:warn].each do |type|
        screens[type].each do |block|
          begin
            block.call
          rescue => e
            case type
            when :fatal
              raise FatalScreenError, e
            when :error
              raise ScreenError, e
            when :warn
              say "Screen warning: #{e}"
            end
          end
        end
      end
    end
  end
end


# -*- encoding : utf-8 -*-
module RedisModelExtension

  # == Class Initialize
  # redis_field - defines fields to be stored into redis
  # redis_alias - defines aliases for finding models
  # redis_key - defines wich fields will be in redis key
  # redis_key_normalize - normalization of redis key (downcase, transliterate)
  # redis_save_fields_with_nil - enable/disable save of nil fields into redis
  module ClassInitialize
    VALID_NORMALIZATIONS = [:downcase, :transliterate]

    #add new field which will be saved into redis
    # * name - name of your variable
    # * type - type of your variable (:integer, :float, :string, :array, :hash)
    # * (default) - default value of your variable
    def redis_field name, type, default = nil
      redis_user_field_config << name

      # remember field to save into redis
      redis_fields_config[name] = type

      # remember field default value
      redis_fields_defaults_config[name] = default

      define_attribute_method name
    end

    def set_redis_autoincrement_key
      @redis_key_config ||= [:id]

      # get value
      define_method :id do
        value_get :id
      end

      # value exists? (not nil and not blank?)
      define_method "id?" do
        value_get(:id) && !value_get(:id).blank? ? true : false
      end

      # set value
      define_method "id=" do |new_value|
        value_set :id, new_value
      end
      private :id= #set it as private

      redis_fields_config[:id] = :autoincrement
    end

    def remove_redis_autoincrement_key
      # remove get value
      remove_method :id

      # remove value exists? (not nil and not blank?)
      remove_method "id?"

      # remove set value
      remove_method "id="

      redis_fields_config.delete(:id)
    end

    # set redis key which will be used for storing model
    def redis_key *fields
      @redis_key_config = fields.flatten

      validate_redis_key

      #own specification of redis key - delete autoincrement
      remove_redis_autoincrement_key unless redis_user_field_config.include?(:id) || @redis_key_config.include?(:id)

      # automaticaly add all fields from key to validation
      # if any of fields in redis key is nil
      # then prevent to save it
      @redis_key_config.each do |field|
        validates field, :presence => :true if field != :id
      end
    end

    # set redis model to normalize redis keys
    def redis_key_normalize *metrics
      @redis_key_normalize_conf ||= []
      metrics.each do |metric|
        raise ArgumentError, "Please provide valid normalization: #{VALID_NORMALIZATIONS.join(", ")}" unless VALID_NORMALIZATIONS.include?(metric)
        @redis_key_normalize_conf << metric
      end
    end

    # store informations about redis aliases
    def redis_alias name, main_fields, name_of_field_for_order = nil, name_of_field_for_args = nil
      #set fields if they are not allready set!
      if name_of_field_for_order && name_of_field_for_args
        redis_field name_of_field_for_order, :array, [] unless redis_fields_config.has_key?(name_of_field_for_order)
        redis_field name_of_field_for_args, :hash, {} unless redis_fields_config.has_key?(name_of_field_for_args)
      end

      @redis_alias_config ||= {}
      #add specification of dynamic alias
      @redis_alias_config[name] = {
        main_fields: main_fields,
        order_field: name_of_field_for_order,
        args_field: name_of_field_for_args,
      }

      #create alias methods for find and get (find_by_name, get_by_name)
      create_class_alias_method(name)
    end

    # store informations about saving nil values
    def redis_save_fields_with_nil store
      @redis_save_fields_with_nil_conf = store
    end

    # store informations about redis key normalization
    def redis_key_normalize_conf
      @redis_key_normalize_conf ||= []
    end

    private

    def create_class_alias_method(name)
      self.class.instance_eval do
        define_method("find_by_#{name}") do |args|
          find_by_alias(name, args)
        end
        define_method("get_by_#{name}") do |args|
          get_by_alias(name, args)
        end
      end
    end

  end

  module Initialize
    extend ActiveSupport::Concern

    included do
      redis_save_fields_with_nil true
      set_redis_autoincrement_key

      include ActiveModel::AttributeMethods
      attribute_method_suffix '' if ActiveModel::VERSION::MINOR < 2
      attribute_method_suffix '='
      attribute_method_suffix '?'
    end

    # initialize instance
    def initialize(args={})
      args.symbolize_keys!
      # look for fields in input hash
      redis_fields_config.each do |key, type|
        # disable to set nonexisting ID!
        raise ArgumentError, "You cannot specify #{key} (it is auto incremented)" if args[key] && type == :autoincrement && get_last_id.to_i < args[key].to_i

        # input hash has known field
        value = if args.has_key?(key)
          value_parse(args[key], type)
        else #there is no value set default valued
          redis_fields_defaults_config[key]
        end
        send(:attribute=, key, value)
      end

      return self
    end

  end
end


# -*- encoding : utf-8 -*-
module RedisModelExtension
  module StoreKeys

    # store old arguments, need's to be called in find/get initialization
    # will remember old arguments and remember redis keys
    # if some fileds in redis key will change, then do rename
    # without this you can end up with old and new saved object!
    def store_keys
      store_redis_keys
    end

    private

    # set old arguments
    def store_redis_keys
      args = to_arg
      #store main key
      redis_old_keys[:key] = self.class.generate_key(args) #store main key

      #store alias keys
      redis_old_keys[:aliases] = []
      redis_alias_config.each do |alias_name, fields|
        redis_old_keys[:aliases] << redis_alias_key(alias_name) if valid_alias_key? alias_name
      end
    end

    # get old arguments
    def redis_old_keys
      @redis_old_keys ||= {:key => nil, :aliases => []}
    end

  end
end


# -*- encoding : utf-8 -*-
module RedisModelExtension

  # == Old Initialize
  # port for old initialize method to new structure
  module ClassOldInitialize
    TYPE_TRANSLATIONS = {
      :integer => :to_i,
      :string => :to_s,
      :bool => :to_bool,
      :symbol => :to_sym,
      :array => :to_array,
      :hash => :to_hash,
      :time => :to_time,
      :date => :to_date
    }
    # old method to initialize redis model extenstion
    # Usage:
    #  REDIS_MODEL_CONF = {
    #   :fields => {
    #     :integer => :to_i,
    #     :boolean => :to_bool,
    #     :string => :to_s,
    #     :symbol => :to_sym,
    #    },
    #    :required => [:integer, :string],
    #    :redis_key => [:string, :symbol],
    #    :redis_aliases => {
    #      :token => [:symbol]
    #    },
    #    # (default is true) if true all nil values will not be saved into redis,
    #    # there should be problem when you want to set some value to nil and same
    #    # it will not be saved (use false to prevent this)
    #    :reject_nil_values => false
    # }
    # include RedisModel
    # initialize_redis_model_methods REDIS_MODEL_CONF
    def initialize_redis_model_methods conf
      puts "WARNING: This initilization method is deprecated and will be removed in future! \n Please read documentation how to change your model to use new initialization methods"

      remove_redis_autoincrement_key

      @conf = {:reject_nil_values => true}.merge(conf)
      #take all fields and make methods for them
      conf[:fields].each do |name, action|
        redis_fields_config[name] = TYPE_TRANSLATIONS.invert[action]
        redis_fields_defaults_config[name] = nil

        # define getter method for field
        define_method "#{name}" do
          value_get name
        end

        # define setter method for field
        define_method "#{name}=" do |new_value|
          value_set name, new_value
        end

        # define exists? method for field
        define_method "#{name}?" do
          value_get(name) && !value_get(name).blank? ? true : false
        end
      end

      # save nil values?
      redis_save_fields_with_nil false if !conf.has_key?(:reject_nil_values) || conf[:reject_nil_values] == true

      # save into class config about redis key
      @redis_key_config = conf[:redis_key]

      #validate presence of all fields in key
      @required_config = (@redis_key_config | conf[:required])
      (@redis_key_config | conf[:required]).each do |field|
        validates field, :presence => :true
      end

      # save into class config about redis keys
      @redis_alias_config = {}
      conf[:redis_aliases].each do |key, fields|
        @redis_alias_config[key] = {
          main_fields: fields,
          order_field: nil,
          args_field: nil,
        }
      end
    end

    # get config hash
    def conf
      fields = {}
      redis_fields_config.each do |key, type|
        fields[key] = TYPE_TRANSLATIONS[type] if TYPE_TRANSLATIONS.has_key?(type)
      end
      {
        fields: fields,
        required: @required_config.sort,
        redis_key: redis_key_config,
        redis_aliases: redis_alias_config.inject({}){|o,(k,v)| o[k] = v[:main_fields]; o},
        reject_nil_values: !redis_save_fields_with_nil_conf,
      }
    end

  end

end


# -*- encoding : utf-8 -*-
module RedisModelExtension

  # == Class Redis Key
  # generators of redis keys
  # used for creating keys for search and save of models & aliases
  #
  module ClassRedisKey

    # Generates redis key for storing object
    # * will produce something like: your_class:key:field_value1:field_value2...
    # (depending on your redis_key setting)
    def generate_key args = {}, key = "key"
      #normalize input hash of arguments
      args = HashWithIndifferentAccess.new(args)

      out = "#{self.name.to_s.underscore.to_sym}:#{key}"
      redis_key_config.each do |key|
        out += add_item_to_redis_key args, key
      end
      out
    end

    # Generates redis key for storing indexes for dynamic aliases
    # will produce something like: your_class:dynamic:name_of_your_dynami_alias:field_value2:field_value1...
    # (field values are sorted by fields order)
    # * dynamic_alias_name (Symbol) - name of your dynamic alias
    # * args (Hash) - arguments of your model
    # * field_order (Array of symbols) - order of fields (ex. [:field2, :field1])
    # * field_args (Hash) - hash of values for aliasing (ex. {:field1 => "field_value1", :field2 => "field_value2"})
    def generate_alias_key alias_name, args = {}
      #check if asked dynamic alias exists
      raise ArgumentError, "Unknown dynamic alias: '#{alias_name}', use: #{redis_alias_config.keys.join(", ")} " unless redis_alias_config.has_key?(alias_name.to_sym)

      #normalize input hash of arguments
      args = HashWithIndifferentAccess.new(args)

      # prepare class name + dynamic + alias name
      out = "#{self.name.to_s.underscore.to_sym}:alias:#{alias_name}"

      #get config
      config = redis_alias_config[alias_name.to_sym]

      # use all specified keys
      config[:main_fields].each do |key|
        out += add_item_to_redis_key args, key
      end

      #is alias dynamic?
      if config[:order_field] && config[:args_field]
        #check if input arguments has order field
        if args.has_key?(config[:order_field]) && args[config[:order_field]] && args.has_key?(config[:args_field]) && args[config[:args_field]]
          #use filed order from defined field in args
          args[config[:order_field]].each do |key|
            out += add_item_to_redis_key args[config[:args_field]], key
          end
        else
          # use global search
          out += ":*"
        end
      end

      out
    end

    # Check if key by arguments exists in db
    def exists? args = {}
      RedisModelExtension::Database.redis.exists(self.name.constantize.generate_key(args))
    end

    #Check if key by alias name and arguments exists in db
    def alias_exists? alias_name, args = {}
      RedisModelExtension::Database.redis.exists(self.name.constantize.generate_alias_key(alias_name, args))
    end

    private

    # return one item of redis key (will decide to input value or to add * for search)
    def add_item_to_redis_key args, key
      if args.has_key?(key) && !args[key].nil?
        key = ":#{args[key]}"
        key = key.mb_chars.downcase if redis_key_normalize_conf.include?(:downcase)
        key = ActiveSupport::Inflector::transliterate(key) if redis_key_normalize_conf.include?(:transliterate)
        key
      else
        ":*"
      end
    end

  end

  # == Redis Key
  # aliases for instance to class generators of redis keys
  #
  module RedisKey

    # get redis key for instance
    def redis_key
      self.class.generate_key(to_arg)
    end

    # get redis key for instance alias
    def redis_alias_key alias_name
      self.class.generate_alias_key(alias_name, to_arg)
    end

    # pointer to exists?
    def exists?
      self.class.exists? to_arg
    end

    # pointer to alias_exists?
    def alias_exists? alias_name
      self.class.alias_exists? alias_name, to_arg
    end

  end
end


# -*- encoding : utf-8 -*-
module RedisModelExtension

  module ClassValidations

    # Validates if key by arguments is valid
    # (all needed fields are not nil!)
    def valid_key? args = {}
      #normalize input hash of arguments
      args = HashWithIndifferentAccess.new(args)

      redis_key_config.each do |key|
        return false unless valid_item_for_redis_key? args, key
      end
      return true
    end

    # Validates if key by alias name and arguments is valid
    # (all needed fields are not nil!)
    def valid_alias_key? alias_name, args = {}
      raise ArgumentError, "Unknown dynamic alias, use: #{redis_alias_config.keys.join(", ")}" unless redis_alias_config.has_key?(alias_name.to_sym)

      #normalize input hash of arguments
      args = HashWithIndifferentAccess.new(args)

      config = redis_alias_config[alias_name.to_sym]

      # use all specified keys
      config[:main_fields].each do |key|
        return false unless valid_item_for_redis_key? args, key
      end

      # is dynamic alias?
        if config[:order_field] && config[:args_field]
        #check if input arguments has order field
        if args.has_key?(config[:order_field]) && args[config[:order_field]] && args.has_key?(config[:args_field]) && args[config[:args_field]]
          #use filed order from defined field in args
          args[config[:order_field]].each do |key|
            return false unless valid_item_for_redis_key? args[config[:args_field]], key
          end
        else
          return false
        end
      end

      return true
    end

    # validate one item of redis key
    def valid_item_for_redis_key? args, key
      (args.has_key?(key) && !args[key].nil?) || redis_fields_config[key] == :autoincrement
    end

    private

    #look for bad cofiguration in redis key and raise argument error
    def validate_redis_key
      valid_fields = redis_fields_config.select{|k,v| v != :array && v != :hash  }.keys
      bad_fields = redis_key_config - valid_fields
      raise ArgumentError, "Sorry, but you cannot use as redis key [nonexisting | array | hash] fields: [#{bad_fields.join(",")}], availible are: #{valid_fields.join(", ")}" unless bad_fields.size == 0
    end

  end

  module Validations

    #pointer to validation
    def valid_key?
      self.class.valid_key? to_arg
    end

    #pointer to validation
    def valid_alias_key? alias_name
      self.class.valid_alias_key? alias_name, to_arg
    end

    def error
      self.errors
    end
  end
end


# -*- encoding : utf-8 -*-
module RedisModelExtension

  # == Attribues
  # modul for easier getting all attributes
  # also for setting ang getting value instance variable
  module Attributes

    #take all arguments and send them out
    def to_arg
      redis_fields_config.inject({}) do |args, (key, type)|
        args[key] = self.send(key)
        args
      end
    end

    alias :args :to_arg
    alias :attributes :to_arg

    #put arguments into json
    def to_json(*args)
      to_arg.to_json(*args)
    end

    private

    # get value from instance variable
    def value_get name
      instance_variable_get(:"@#{name}")
    end
    alias :attribute :value_get

    # set value into instance variable
    def value_set name, value
      instance_variable_set(:"@#{name}", value_parse(value, redis_fields_defaults_config[name]))
    end
    alias :attribute= :value_set

    def attribute?(name)
      value_get(name) && !value_get(name).blank? ? true : false
    end

  end
end


# -*- encoding : utf-8 -*-
module RedisModelExtension

  # == Get & Find
  # * Model.all => Array of all instances
  # * Model.find(1) => Array of one instance with id 1
  # * Model.get(1) => Array of one instance with id 1
  # * Model.find( id: 1 ) => Array of one instance with id 1
  # * Model.find( field: "test" ) => Array of all instances with field == test [field must be in redis key]
  module ClassGetFind

    ######################################
    #  FIND METHODS
    ######################################

    #Find method for searching in redis
    # * args (Integer) - search by id
    # * args (Hash) - search by arguments in redis_key
    def find(args = {})
      # when argument is integer - search by id
      args = { id: args } if args.is_a?(Integer)
      #normalize input hash of arguments
      args = HashWithIndifferentAccess.new(args)

      out = []
      klass = self.name.constantize
      search_key = klass.generate_key(args)
      #is key specified directly? -> no needs of looking for other keys! -> faster
      unless search_key =~ /\*/
        out << klass.new_by_key(search_key) if klass.exists?(args)
      else
        RedisModelExtension::Database.redis.keys(search_key).each do |key|
          out << klass.new_by_key(key)
        end
      end
      out
    end
    alias :all :find

    #Find method for searching in redis
    def find_by_alias(alias_name, args = {})
      #check if asked dynamic alias exists
      raise ArgumentError, "Unknown dynamic alias: '#{alias_name}', use: #{redis_alias_config.keys.join(", ")} " unless redis_alias_config.has_key?(alias_name.to_sym)

      #normalize input hash of arguments
      args = HashWithIndifferentAccess.new(args)

      out = []
      klass = self.name.constantize
      search_key = klass.generate_alias_key(alias_name, args)
      #is key specified directly? -> no needs of looking for other keys! -> faster
      unless search_key =~ /\*/
        out = klass.get_by_alias(alias_name, args) if klass.alias_exists?(alias_name, args)
      else
        RedisModelExtension::Database.redis.keys(search_key).each do |key|
          out << klass.get_by_alias_key(key)
        end
      end
      out.flatten
    end

    ######################################
    #  GET BY ARGUMENTS
    ######################################

    #fastest method to get object from redis by getting it by arguments
    # * args (Integer) - search by id
    # * args (Hash) - search by arguments in redis_key
    def get(args = {})
      # when argument is integer - search by id
      args = { id: args } if args.is_a?(Integer)

      #normalize input hash of arguments
      args = HashWithIndifferentAccess.new(args)

      klass = self.name.constantize
      if klass.valid_key?(args) && klass.exists?(args)
        klass.new_by_key(klass.generate_key(args))
      else
        nil
      end
    end

    ######################################
    #  GET BY REDIS KEYS
    ######################################

    #fastest method to get object from redis by getting it by dynamic alias and arguments
    def get_by_alias(alias_name, args = {})
      #check if asked dynamic alias exists
      raise ArgumentError, "Unknown dynamic alias: '#{alias_name}', use: #{redis_alias_config.keys.join(", ")} " unless redis_alias_config.has_key?(alias_name.to_sym)

      #normalize input hash of arguments
      args = HashWithIndifferentAccess.new(args)

      klass = self.name.constantize
      if klass.valid_alias_key?(alias_name, args) && klass.alias_exists?(alias_name, args)
        out = []
        RedisModelExtension::Database.redis.smembers(klass.generate_alias_key(alias_name, args)).each do |key|
          item = klass.new_by_key(key)
          out << item if item
        end
        return out
      end
      nil
    end

    ######################################
    #  GET BY REDIS KEYS
    ######################################

    #if you know redis key and would like to get object
    def get_by_redis_key(redis_key)
      if redis_key.is_a?(String)
        klass = self.name.constantize
        klass.new_by_key(redis_key)
      else
        nil
      end
    end

    #fastest method to get object from redis by getting it by alias and arguments
    def get_by_alias_key(alias_key)
      klass = self.name.constantize
      if RedisModelExtension::Database.redis.exists(alias_key)
        out = []
        RedisModelExtension::Database.redis.smembers(alias_key).each do |key|
          item = klass.new_by_key(key)
          out << item if item
        end
        return out
      end
      nil
    end

    ######################################
    #  CREATE NEW OBJECT BY HASH VALUES
    ######################################

    # read all data from redis and create new instance (used for Find & Get method)
    def new_by_key(key)
      args = RedisModelExtension::Database.redis.hgetall(key)
      return nil unless args && args.any?
      args.symbolize_keys!

      new_instance = new(args)
      new_instance.store_keys

      return new_instance
    end

  end
end


# -*- encoding : utf-8 -*-
module RedisModelExtension
  module ValueTransform

    # choose right type of value and then transform it for redis
    def value_to_redis name, value
      if redis_fields_config.has_key?(name)
        value_transform value, redis_fields_config[name]
      else
        value
      end
    end

    # convert value for valid format which can be saved in redis
    def value_transform value, type
      return nil if value.nil? || value.to_s.size == 0
      case type
      when :integer then value.to_i
      when :autoincrement then value.to_i
      when :string then value.to_s
      when :float then value.to_f
      when :bool then value.to_s
      when :symbol then value.to_s
      when :marshal then Marshal.dump(value)
      when :array then Yajl::Encoder.encode(value)
      when :hash then Yajl::Encoder.encode(value)
      when :time then Time.parse(value.to_s).strftime("%Y.%m.%d %H:%M:%S")
      when :date then Date.parse(value.to_s).strftime("%Y-%m-%d")
      else value
      end
    end

    # convert value from redis into valid format in ruby
    def value_parse value, type
      return nil if value.nil? || value.to_s.size == 0
      case type
      when :integer then value.to_i
      when :autoincrement then value.to_i
      when :string then value.to_s
      when :float then value.to_f
      when :bool then value.to_s.to_bool
      when :symbol then value.to_s.to_sym
      when :marshal then value.is_a?(String) ? Marshal.load(value) : value
      when :array then value.is_a?(String) ? Yajl::Parser.parse(value) : value
      when :hash then value.is_a?(String) ? Hashr.new(Yajl::Parser.parse(value)) : Hashr.new(value)
      when :time then value.is_a?(String) ? Time.parse(value) : value
      when :date then value.is_a?(String) ? Date.parse(value) : value
      else value
      end
    end

  end
end


# -*- encoding : utf-8 -*-
module RedisModelExtension

  module ClassCreate

    # create instance and save it
    def create args = {}
      instance = self.new(args)
      instance.save
      return instance
    end

  end

  module SaveDestroy

    # save method - save all attributes (fields) and create aliases
    def save
      perform = lambda do
        # can be saved into redis?
        if valid?

          #autoicrement id
          self.send("id=", increment_id) if redis_key_config.include?(:id) && !self.id?

          #generate key (possibly new)
          generated_key = redis_key

          #take care about renaming saved hash in redis (if key changed)
          if redis_old_keys[:key] && redis_old_keys[:key] !=  generated_key && RedisModelExtension::Database.redis.exists(redis_old_keys[:key])
            RedisModelExtension::Database.redis.rename(redis_old_keys[:key], generated_key)
          end

          #ignore nil values for save
          args = self.class.redis_save_fields_with_nil_conf ? to_arg : to_arg.reject{|k,v| v.nil?}

          #perform save to redis hash
          RedisModelExtension::Database.redis.hmset(generated_key, *args.inject([]){ |arr,kv| arr + [kv[0], value_to_redis(kv[0], kv[1])]})

          # destroy aliases
          destroy_aliases!
          create_aliases

          #after save make sure instance remember old key to know if it needs to be ranamed
          store_keys
        end
      end

      run_callbacks :save do
        unless exists?
          run_callbacks :create do
            perform.()
          end
        else
          perform.()
        end
      end
      unless errors.any?
        return self
      else
        return false
      end
    end

    # create aliases (create key value [STRING] key is alias redis key and value is redis key)
    def create_aliases
      main_key = redis_key
      redis_alias_config.each do |alias_name, fields|
        RedisModelExtension::Database.redis.sadd(redis_alias_key(alias_name), main_key) if valid_alias_key? alias_name
      end
    end

    # update multiple attrubutes at once
    def update args
      args.each do |key, value|
        method = "#{key}=".to_sym
        if self.respond_to? method
          self.send(method, value)
        end
      end
    end

    #remove record form database
    def destroy!
      if exists?
        run_callbacks :destroy do
          #destroy main object
          RedisModelExtension::Database.redis.del(redis_key)
          destroy_aliases!
        end
      end
    end

    alias :destroy :destroy!

    # remove all aliases
    def destroy_aliases!
      #do it only if it is existing object!
      if redis_old_keys[:aliases].size > 0
        redis_old_keys[:aliases].each do |alias_key|
          RedisModelExtension::Database.redis.srem alias_key, redis_old_keys[:key]
          #delete alias with 0 keys
          RedisModelExtension::Database.redis.del(alias_key) if RedisModelExtension::Database.redis.scard(alias_key).to_i == 0
        end
      end
    end

  end
end


require 'commander'

module Firim
  class CommandsGenerator
    include Commander::Methods

    # def self.start
    #   FastlaneCore::UpdateChecker.start_looking_for_update('deliver')
    #   self.new.run
    # ensure
    #   FastlaneCore::UpdateChecker.show_update_status('deliver', Deliver::VERSION)
    # end

    def self.start
      self.new.run
    end

    def run
      program :version, Firim::VERSION
      program :description, 'fir.im command tool'
      program :help, 'Author', 'Hailong Wang <whlsxl+g@gmail.com>'
      program :help, 'GitHub', 'https://github.com/whlsxl/firim'
      program :help_formatter, :compact

      FastlaneCore::CommanderGenerator.new.generate(Firim::Options.available_options)
      global_option('--verbose') { $verbose = true }

      always_trace!

      command :run do |c|
        c.syntax = 'firim'
        c.description = 'Upload binary and infomations to Fir.im'
        c.action do |args, options|
          options = FastlaneCore::Configuration.create(Firim::Options.available_options, options.__hash__)
          loaded = options.load_configuration_file("Firimfile")
          loaded = true if options[:firim_api_token]
          unless loaded
            if UI.confirm("No firim configuration found in the current directory. Do you want to setup firim?")
              require 'deliver/setup'
              Firim::Setup.new.run(options)
              return 0
            end
          end
          Firim::Runner.new(options).run
        end
      end

      command :init do |c|
        c.syntax = 'firim init'
        c.description = 'Create the initial `Firimfile` configuration'
        c.action do |args, options|
          if File.exist?("Firimfile") or File.exist?("fastlane/Firimfile")
            UI.important("You already got a running firim setup in this directory")
            return 0
          end

          require 'firim/setup'
          options = FastlaneCore::Configuration.create(Firim::Options.available_options, options.__hash__)
          Firim::Setup.new.run(options)
        end
      end

      command :addtoken do |c|
        c.syntax = 'firim addtoken'
        c.description = 'Add a firim api token to keychain. username is not necessary, Just a sign for multiple api token.'

        c.option '--username username', String, 'Username to add(not necessary).'
        c.option '--token token', String, 'API token to add'

        c.action do |args, options|
          username = options.username || ask('Username(not necessary): ')
          token = options.token || ask('Password: ') { |q| q.echo = '*'}

          add(username, token)

          puts "Token #{username || '`default`'}: #{'*' * token.length} added to keychain."
        end
      end

      # Command to remove credential from Keychain
      command :removetoken do |c|
        c.syntax = 'firim removetoken'
        c.description = 'Removes a firim token from the keychain.'

        c.option '--username username', String, 'Username to remove(or default).'

        c.action do |args, options|
          username = options.username || ask('Username: ')

          remove(username)
        end
      end

      default_command :run

      run!
    end

    private

    # Add entry to Apple Keychain
    def add(username, token)
      Firim::AccountManager.new(
        user: username,
        token: token
      ).add_to_keychain
    end

    # Remove entry from Apple Keychain using AccountManager
    def remove(username)
      Firim::AccountManager.new(
        user: username
      ).remove_from_keychain
    end
  end
end


require 'httparty'
require 'dingbot/configuration'
require 'dingbot/message/base'
require 'dingbot/message/text'
require 'dingbot/message/markdown'

module DingBot
  # @private
  class Client
    include HTTParty

    format :json
    headers "Content-Type" => "application/json"

    attr_accessor :access_token

    # @private
    attr_accessor(*Configuration::VALID_OPTIONS_KEYS)

    # Creates a new API.
    # @raise [Error:MissingCredentials]
    def initialize(options={})
      options = DingBot.options.merge(options)
      (Configuration::VALID_OPTIONS_KEYS).each do |key|
        send("#{key}=", options[key]) if options[key]
      end
    end

    # Parse response body.
    def self.parse(body)
      begin
        decode(body)
      rescue => e
        raise Error::Parsing.new "Couldn't parse a response body"
      end
    end

    # Decodes a JSON response into Ruby object.
    def self.decode(response)
      JSON.load response
    rescue JSON::ParserError
      raise Error::Parsing.new "The response is not a valid JSON"
    end

    def send_msg(message)
      validate self.class.post(@endpoint, {query: {access_token: @access_token}, body: message.to_json})
    end

    def send_text(content)
      message = DingBot::Message::Text.new(content)
      send_msg(message)
    end

    def send_markdown(title, text)
      message = DingBot::Message::Markdown.new(title, text)
      send_msg(message)
    end

    # Checks the response code for common errors.
    # Returns parsed response for successful requests.
    def validate(response)
      error_klass = case response.code
                      when 400 then
                        Error::BadRequest
                      when 401 then
                        Error::Unauthorized
                      when 403 then
                        Error::Forbidden
                      when 404 then
                        Error::NotFound
                      when 405 then
                        Error::MethodNotAllowed
                      when 409 then
                        Error::Conflict
                      when 422 then
                        Error::Unprocessable
                      when 500 then
                        Error::InternalServerError
                      when 502 then
                        Error::BadGateway
                      when 503 then
                        Error::ServiceUnavailable
                    end

      fail error_klass.new(response) if error_klass

      parsed = response.parsed_response
      parsed.client = self if parsed.respond_to?(:client=)
      parsed.parse_headers!(response.headers) if parsed.respond_to?(:parse_headers!)
      parsed
    end

  end
end


require 'json'
require 'net/http'
require 'uri'

require_relative 'logger'

# Connects to nsqlookup's to find the nsqd instances for a given topic
module Nsq
  class Discovery
    include Nsq::AttributeLogger

    # lookupd addresses must be formatted like so: '<host>:<http-port>'
    def initialize(lookupds)
      @lookupds = lookupds
    end

    # Returns an array of nsqds instances
    #
    # nsqd instances returned are strings in this format: '<host>:<tcp-port>'
    #
    #     discovery.nsqds
    #     #=> ['127.0.0.1:4150', '127.0.0.1:4152']
    #
    # If all nsqlookupd's are unreachable, raises Nsq::DiscoveryException
    #
    def nsqds
      gather_nsqds_from_all_lookupds do |lookupd|
        get_nsqds(lookupd)
      end
    end

    # Returns an array of nsqds instances that have messages for
    # that topic.
    #
    # nsqd instances returned are strings in this format: '<host>:<tcp-port>'
    #
    #     discovery.nsqds_for_topic('a-topic')
    #     #=> ['127.0.0.1:4150', '127.0.0.1:4152']
    #
    # If all nsqlookupd's are unreachable, raises Nsq::DiscoveryException
    #
    def nsqds_for_topic(topic)
      gather_nsqds_from_all_lookupds do |lookupd|
        get_nsqds(lookupd, topic)
      end
    end

    private

    def gather_nsqds_from_all_lookupds
      nsqd_list = @lookupds.map do |lookupd|
        yield(lookupd)
      end.flatten

      # All nsqlookupds were unreachable, raise an error!
      if nsqd_list.length > 0 && nsqd_list.all? { |nsqd| nsqd.nil? }
        raise DiscoveryException
      end

      nsqd_list.compact.uniq
    end

    # Returns an array of nsqd addresses
    # If there's an error, return nil
    def get_nsqds(lookupd, topic = nil)
      uri_scheme = 'http://' unless lookupd.match(%r(https?://))
      uri = URI.parse("#{uri_scheme}#{lookupd}")

      uri.query = "ts=#{Time.now.to_i}"
      if topic
        uri.path = '/lookup'
        uri.query += "&topic=#{URI.escape(topic)}"
      else
        uri.path = '/nodes'
      end

      begin
        body = Net::HTTP.get(uri)
        data = JSON.parse(body)
        producers = data['producers'] || # v1.0.0-compat
                      (data['data'] && data['data']['producers'])

        if producers
          producers.map do |producer|
            "#{producer['broadcast_address']}:#{producer['tcp_port']}"
          end
        else
          []
        end
      rescue Exception => e
        error "Error during discovery for #{lookupd}: #{e}"
        nil
      end
    end

  end
end


require_relative 'discovery'
require_relative 'connection'
require_relative 'logger'

module Nsq
  class ClientBase
    include Nsq::AttributeLogger
    @@log_attributes = [:topic]

    attr_reader :topic
    attr_reader :connections

    def connected?
      @connections.values.any?(&:connected?)
    end

    def terminate
      @discovery_thread.kill if @discovery_thread
      drop_all_connections
    end

    private

    # discovers nsqds from an nsqlookupd repeatedly
    #
    #   opts:
    #     nsqlookups: ['127.0.0.1:4161'],
    #     topic: 'topic-to-find-nsqds-for',
    #     interval: 60
    #
    def discover_repeatedly(opts = {})
      @discovery_thread = Thread.new do

        @discovery = Discovery.new(opts[:nsqlookupds])

        loop do
          begin
            nsqds = nsqds_from_lookupd(opts[:topic])
            drop_and_add_connections(nsqds)
          rescue DiscoveryException
            # We can't connect to any nsqlookupds. That's okay, we'll just
            # leave our current nsqd connections alone and try again later.
            warn 'Could not connect to any nsqlookupd instances in discovery loop'
          end
          sleep opts[:interval]
        end

      end

      @discovery_thread.abort_on_exception = true
    end

    def nsqds_from_lookupd(topic = nil)
      if topic
        @discovery.nsqds_for_topic(topic)
      else
        @discovery.nsqds
      end
    end

    def drop_and_add_connections(nsqds)
      # drop nsqd connections that are no longer in lookupd
      missing_nsqds = @connections.keys - nsqds
      missing_nsqds.each do |nsqd|
        drop_connection(nsqd)
      end

      # add new ones
      new_nsqds = nsqds - @connections.keys
      new_nsqds.each do |nsqd|
        begin
          add_connection(nsqd)
        rescue Exception => ex
          error "Failed to connect to nsqd @ #{nsqd}: #{ex}"
        end
      end

      # balance RDY state amongst the connections
      connections_changed
    end

    def add_connection(nsqd, options = {})
      info "+ Adding connection #{nsqd}"
      host, port = nsqd.split(':')
      connection = Connection.new({
        host: host,
        port: port,
        ssl_context: @ssl_context,
        tls_options: @tls_options,
        tls_v1: @tls_v1
      }.merge(options))
      @connections[nsqd] = connection
    end

    def drop_connection(nsqd)
      info "- Dropping connection #{nsqd}"
      connection = @connections.delete(nsqd)
      connection.close if connection
      connections_changed
    end

    def drop_all_connections
      @connections.keys.each do |nsqd|
        drop_connection(nsqd)
      end
    end

    # optional subclass hook
    def connections_changed
    end
  end
end