module Clipster
  class Clip < ActiveRecord::Base
    include ActionView::Helpers::DateHelper

    before_create :default_values

    self.primary_key = :id

    attr_accessible :clip, :language, :title, :private, :expires, :lifespan
    cattr_reader :lifespans
    cattr_accessor :current_user, :lifespan

    belongs_to :user, :class_name => Clipster.config.user_class.to_s if Clipster.config.associates_clip_with_user

    # Define all supported lifespans and their time offset
    @@lifespans = [["Forever", nil],
                   ["An Hour", :hours=>1],
                   ["A Day", :days=>1],
                   ["A Week", :days=>7],
                   ["A Month", :months=>1],
                   ["A Year", :years=>1]]

    validates :clip, :length => {:minimum   => 3}
    validates :title, :length => {:minimum   => 1}
    validates :language, :inclusion => { :in => CodeRay::Scanners.list.map(&:to_s),
          :message => "%{value} is not supported, please choose from: " +
          CodeRay::Scanners.list.map(&:to_s).to_s }
    validates :lifespan, :inclusion => { :in => @@lifespans.flatten,
          :message => "%{value} is not supported, please choose from:" +
          lifespans.map(&:first).to_s}

    # Search all public clips using title, language, and content
    def self.search(term)
      where("(title LIKE :term or language LIKE :term or clip LIKE :term) and (expires is null OR expires > :now)",{
          :term => "#{term}%".gsub('*','%').gsub(/%+/, '%'),
          :now => DateTime.now
      })
    end

    # All clips that are public, language specific, and not expired
    def self.language_for_public(lang)
      where("private = :private AND
             language = :lang AND
             (expires is null OR expires > :now)",{
          :private => false,
          :lang => lang,
          :now  => DateTime.now
      })
    end

    # All clips that are public, and not expired
    def self.public
      where("private = :private AND
             (expires is null OR expires > :now)",{
          :private => false,
          :now  => DateTime.now
      })
    end

    # Setter to convert user's choice of 'A Week', etc. to an actual DateTime
    def lifespan=(lifespan)
      @lifespan = lifespan
      @@lifespans.each_with_index do |span, index|
        if span[0] == lifespan && lifespan != "Forever"
          self.expires = DateTime.now.advance(@@lifespans[index][1])
        end
      end
    end

    def lifespan
      @lifespan
    end

    # Getter to convert an expire date to '1 Month', '1 Year', etc.
    def expires_in_words
      unless self.expires.nil?
        time_ago_in_words(self.expires).humanize
      else
        "The end of time"
      end
    end

    # Utility method called by either cron job or when an expired clip is accessed
    def Clip.delete_expired
      Clip.destroy_all(["expires is not null AND expires <= ?", DateTime.now])
    end

    # Creates the div for the clip
    def div
      cr_scanner = CodeRay.scan(self.clip, self.language)
      # Only show line numbers if its greater than 1
      if cr_scanner.loc <= 1
        return cr_scanner.div
      else
        return cr_scanner.div(:line_numbers => :table)
      end
    end

    protected
      def default_values
        self.user_id = self.current_user if Clipster.config.associates_clip_with_user
        self.id = Time.now.to_f.to_s.gsub('.','').to_i.to_s(36)
      end
  end
end


# Htmless
# Copyright (C) 2015 Petr Chalupa <git@pitr.ch>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
# USA

module Htmless
  module Helper

    # adds instance method to the class. Method accepts any instance of builder and returns it after rendering.
    # @param [Symbol] method_name
    # @yield [self] builder_block is evaluated inside builder and accepts instance of a rendered object as parameter
    # @example
    #   class User
    #   # ...
    #     include HammerBuilder::Helper
    #
    #     builder :menu do |user|
    #       li user.name
    #     end
    #   end
    #
    #   User.new.menu(HammerBuilder::Standard.get).to_html! #=> "
Name
"
    def builder(method_name, &builder_block)
      define_method(method_name) do |builder, *args|
        builder.dive(self, *args, &builder_block)
      end
    end
  end
end


# Htmless
# Copyright (C) 2015 Petr Chalupa <git@pitr.ch>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
# USA

require 'cgi'
#require 'active_support/core_ext/string/inflections'

require 'htmless/dynamic_classes'
require 'htmless/strings_injector'
require 'htmless/data'
require 'htmless/data/html5'
require "htmless/pool"
require "htmless/helper"

module Htmless

# Abstract implementation of Builder
  class Abstract
    extend DynamicClasses

    require "htmless/abstract/abstract_tag"
    require "htmless/abstract/abstract_single_tag"
    require "htmless/abstract/abstract_double_tag"

    def self.strings_injector
      @strings_injector ||= StringsInjector.new do
        add :lt, '<'
        add :gt, '>'
        add :slash_lt, '</'
        add :slash_gt, ' />'
        add :dash, '-'
        add :underscore, '_'
        add :space, ' '
        add :spaces, Array.new(300) { |i| ('  ' * i).freeze }
        add :newline, "\n"
        add :quote, '"'
        add :eql, '='
        add :eql_quote, self[:eql] + self[:quote]
        add :comment_start, ''
        add :cdata_start, '<![CDATA['
        add :cdata_end, ']]>'
      end
    end

    # << faster then +
    # yield faster then block.call
    # accessing ivar and constant is faster then accesing hash or cvar
    # class_eval faster then define_method
    # beware of strings in methods -> creates a lot of garbage

    def self.tags=(tags)
      @tags = tags
    end

    def self.tags
      @tags or (superclass.tags if superclass.respond_to? :tags)
    end

    def tags
      self.class.tags
    end

    self.tags = []

    protected

    # defines instance method for +tag+ in builder
    def self.define_tag(tag)
      tag = tag.to_s
      class_eval <<-RUBY, __FILE__, __LINE__ + 1
        def #{tag}(*args, &block)
          flush
          @_#{tag}.open(*args, &block)
        end
      RUBY
      self.tags += [tag]
    end

    public

    # current tag being builded
    attr_accessor :_current
    alias_method :current, :_current
    alias_method :current=, :_current=

    # creates a new builder
    # This is quite expensive, HammerBuilder::Pool should be used
    def initialize()
      @_output  = ""
      @_stack   = []
      @_current = nil

      self.class.strings_injector.inject_to self

      # tag classes initialization
      tags.each do |klass|
        instance_variable_set(:"@_#{klass}", self.class.dynamic_classes[camelize_string(klass).to_sym].new(self))
      end
    end

    # escapes +text+ to output
    def text(text)
      flush
      @_output << CGI.escapeHTML(text.to_s)
    end

    # unescaped +text+ to output
    def raw(text)
      flush
      @_output << text.to_s
    end

    # inserts +comment+
    def comment(comment)
      flush
      @_output << @_str_comment_start << comment.to_s << @_str_comment_end
    end

    # insersts CDATA with +content+
    def cdata(content)
      flush
      @_output << @_str_cdata_start << content.to_s << @_str_cdata_end
    end

    # renders html5 doc type
    # @example
    #   html5 # => <!DOCTYPE html>
    def html5
      raw "<!DOCTYPE html>\n"
    end

    # resets the builder to the state after creation - much faster then creating a new one
    def reset
      flush
      @_output.clear
      @_stack.clear
      self
    end

    #def capture
    #  flush
    #  _output = @_output.clone
    #  _stack  = @_stack.clone
    #  @_output.clear
    #  @_stack.clear
    #  yield
    #  to_html
    #ensure
    #  @_output.replace _output
    #  @_stack.replace _stack
    #end

    # enables you to evaluate +block+ inside the builder with +variables+
    # @example
    #  HammerBuilder::Formatted.new.go_in('asd') do |string|
    #    div string
    #  end.to_html #=> "
asd
"
    #
    def go_in(*variables, &block)
      instance_exec *variables, &block
      self
    end

    alias_method :dive, :go_in

    # sets instance variables when block is yielded
    # @param [Hash{String => Object}] instance_variables hash of names and values to set
    # @yield block when variables are set, variables are cleaned up afterwards
    def set_variables(instance_variables)
      instance_variables.each { |name, value| instance_variable_set("@#{name}", value) }
      yield(self)
      instance_variables.each { |name, _| remove_instance_variable("@#{name}") }
      self
    end

    # @return [String] output
    def to_html()
      flush
      @_output.clone
    end

    # flushes open tag
    # @api private
    def flush
      if @_current
        @_current.flush
        @_current = nil
      end
    end

    # renders +object+ with +method+
    # @param [Object] object an object to render
    # @param [Symbol] method a method name which is used for rendering
    # @param args arguments passed to rendering method
    # @yield block passed to rendering method
    def render(object, method, *args, &block)
      object.__send__ method, self, *args, &block
      self
    end

    alias_method :r, :render

    # renders js
    # @option options [Boolean] :cdata (false) should cdata be used?
    # @example
    #   js 'a_js_function();' #=>
    def js(js, options = {})
      use_cdata = options.delete(:cdata) || false
      script({ :type => "text/javascript" }.merge(options)) { use_cdata ? cdata(js) : text(js) }
    end

    # joins and renders +collection+ with +glue+
    # @param [Array<Proc, Object>] collection of objects or lambdas
    # @param [Proc, String] glue can be String which is rendered with #text or block to render
    # @yield how to render objects from +collection+, Proc in collection does not use this block
    # @example
    #   join([1, 1.2], lambda { text ', ' }) {|o| text o }        # => "1, 1.2"
    #   join([1, 1.2], ', ') {|o| text o }                        # => "1, 1.2"
    #   join([->{ text 1 }, 1.2], ', ') {|o| text o }             # => "1, 1.2"
    def join(collection, glue = nil, &it)
      # TODO as helper? two block method call #join(collection, &item).with(&glue)
      glue_block = case glue
                     when String
                       lambda { text glue }
                     when Proc
                       glue
                     else
                       lambda {}
                   end

      collection.each_with_index do |obj, i|
        glue_block.call() if i > 0
        obj.is_a?(Proc) ? obj.call : it.call(obj)
      end
    end

    private

    def self.camelize_string(term)
      term.
          to_s.
          sub(/^[a-z\d]*/) { $&.capitalize }.
          gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.
          gsub('/', '::')
    end

    def camelize_string(term)
      self.class.camelize_string term
    end

  end
end


module RestoreBundledWith
  # The git repository
  class Repository < RestoreFromRepository::Repository
    LOCK_FILE = 'Gemfile.lock'.freeze

    # @param file [String] target file
    # @param ref [String] git ref
    # @param new_line [String] file's ending new line
    #
    # @return [String] target file contents
    def fetch_file(file = Lock::FILE_NAME, ref = REF, new_line = NEW_LINE)
      super(file, ref, new_line)
    end
  end
end


# Htmless
# Copyright (C) 2015 Petr Chalupa <git@pitr.ch>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
# USA

module Htmless

  # Creating builder instances is expensive, therefore you can use Pool to go around that
  # @example
  #   pool = Pool.new Formatted
  #   pool.get.go_in do
  #     # some rendering
  #   end.to_xhtml! # => output and releases the builder to pool
  class Pool

    module Helper
      def release
        @_origin.release self
      end

      # @return [String] output and releases the builder to pool
      def to_html!
        to_html
      ensure
        release
      end
    end

    attr_reader :klass

    def initialize(klass)
      @klass = klass
      @pool  = []
      klass.send :include, Helper
    end

    # This the preferred way of getting new Builder. If you forget to release it, it does not matter -
    # builder gets GCed after you lose reference
    # @return [Abstract]
    def get
      if @pool.empty?
        @klass.new.instance_exec(self) { |origin| @_origin = origin; self }
      else
        @pool.pop
      end
    end

    # returns +builder+ back into pool *DONT* forget to lose the reference to the +builder+
    # @param [Abstract]
    def release(builder)
      raise TypeError unless builder.is_a? @klass
      builder.reset
      @pool.push builder
      nil
    end

    def size
      @pool.size
    end
  end

  class SynchronizedPool < Pool
    def initialize(klass)
      super(klass)
      @mutex = Mutex.new
    end

    def get
      @mutex.synchronize { super }
    end

    def release(builder)
      @mutex.synchronize { super(builder) }
    end

  end
end


require 'fiddle'
require 'fiddle/import'

module Console

    # Implements Windows-specific platform functionality when running
    # under MRI. Uses Fiddle, since MRI does not provide FFI functionality
    # as standard, and furthermore requires a Ruby DevKit installation to
    # install the ffi gem. This is a royal pain, so we instead use Fiddle
    # to invoke the Windows console API.
    module Windows

        extend Fiddle::Importer

        dlload 'kernel32.dll'

        # Constant representing STDOUT (0xFFFFFFF5)
        # We can't use this however, as it overflows to a Bignum!
        STD_OUTPUT_HANDLE = -11

        # Wrap the need to call #call on a Fiddle::Function
        def self.attach_function(name, sig)
            func = extern(sig, :stdcall)
            define_singleton_method(name){ |*args| func.call(*args) }
        end

        # FFI structure used to get/set information about the current console
        # window buffer
        BufferInfo = struct([
            'short width',
            'short height',
            'short cursor_x',
            'short cursor_y',
            'short text_attributes',
            'short window_left',
            'short window_top',
            'short window_right',
            'short window_bottom',
            'short max_width',
            'short max_height'
        ])

        class BufferInfo
            def [](name)
                self.send(name)
            end
        end

        attach_function :get_std_handle, 'void* GetStdHandle(int)'
        attach_function :get_console_screen_buffer_info, 'int GetConsoleScreenBufferInfo(*void, *void)'
        attach_function :set_console_cursor_position, 'int SetConsoleCursorPosition(*void, int)'
        attach_function :set_console_text_attribute, 'int SetConsoleTextAttribute(*void, short)'
        attach_function :set_console_title, 'int SetConsoleTitleA(*void)'

        # Retrieve a BufferInfo object
        def buffer_info
            @buffer_info ||= BufferInfo.malloc
        end
        module_function :buffer_info
        private :buffer_info

        # Sets the cursor position to the specified +x+ and +y+ locations in the
        # console output buffer. If +y+ is nil, the cursor is positioned at +x+ on
        # the current line.
        def set_cursor_position(x, y)
            if stdout && x && y
                coord = y << 16 | x
                self.set_console_cursor_position(stdout, coord) == 0
            end
        end
        module_function :set_cursor_position

    end

end


require 'isbm_adaptor/client'
require 'isbm_adaptor/duration'

module IsbmAdaptor
  class ProviderPublication < IsbmAdaptor::Client

    # Creates a new ISBM ProviderPublication client.
    #
    # @param endpoint [String] the SOAP endpoint URI
    # @option options [Array<String>] :wsse_auth username and password, i.e. [username, password]
    # @option options [Object] :logger (Rails.logger or $stdout) location where log should be output
    # @option options [Boolean] :log (true) specify whether requests are logged
    # @option options [Boolean] :pretty_print_xml (false) specify whether request and response XML are formatted
    def initialize(endpoint, options = {})
      super('ProviderPublicationService.wsdl', endpoint, options)
    end

    # Opens a publication session for a channel.
    #
    # @param uri [String] the channel URI
    # @return [String] the session id
    # @raise [ArgumentError] if uri is blank
    def open_session(uri)
      validate_presence_of uri, 'Channel URI'

      response = @client.call(:open_publication_session, message: { 'ChannelURI' => uri })

      response.to_hash[:open_publication_session_response][:session_id].to_s
    end

    # Posts a publication message.
    #
    # @param session_id [String] the session id
    # @param content [String] a valid XML string as message contents
    # @param topics [Array<String>, String] a collection of topics or single topic
    # @param expiry [Duration] when the message should expire
    # @return [String] the message id
    # @raise [ArgumentError] if session_id, content or topics are blank, or
    #   content is not valid XML
    def post_publication(session_id, content, topics, expiry = nil)
      validate_presence_of session_id, 'Session Id'
      validate_presence_of content, 'Content'
      validate_presence_of topics, 'Topics'
      validate_xml content

      topics = [topics].flatten

      # Use Builder to generate XML body as we need to concatenate XML message content
      xml = Builder::XmlMarkup.new
      xml.isbm :SessionID, session_id
      xml.isbm :MessageContent do
        xml << content
      end
      topics.each do |topic|
        xml.isbm :Topic, topic
      end
      duration = expiry.to_s
      xml.isbm :Expiry, duration unless duration.nil?

      response = @client.call(:post_publication, message: xml.target!)

      response.to_hash[:post_publication_response][:message_id].to_s
    end

    # Expires a posted publication message.
    #
    # @param session_id [String] the session id used to post the publication
    # @param message_id [String] the message id received after posting the publication
    # @return [void]
    # @raise [ArgumentError] if session_id or message_id are blank
    def expire_publication(session_id, message_id)
      validate_presence_of session_id, 'Session Id'
      validate_presence_of message_id, 'Message Id'

      @client.call(:expire_publication, message: { 'SessionID' => session_id, 'MessageID' => message_id })

      return true
    end

    # Closes a publication session.
    #
    # @param session_id [String] the session id
    # @return [void]
    # @raise [ArgumentError] if session_id is blank
    def close_session(session_id)
      validate_presence_of session_id, 'Session Id'

      @client.call(:close_publication_session, message: { 'SessionID' => session_id })

      return true
    end
  end
end


require 'isbm_adaptor/message'

module IsbmAdaptor
  class Client
    # Creates a new ISBM client.
    #
    # @param wsdl_file [String] the filename of the WSDL
    # @param endpoint [String] the SOAP endpoint URI
    # @option options [Object] :logger (Rails.logger or $stdout) location where log should be output
    # @option options [Boolean] :log (true) specify whether requests are logged
    # @option options [Boolean] :pretty_print_xml (false) specify whether request and response XML are formatted
    def initialize(wsdl_file, endpoint, options = {})
      options[:wsdl] = wsdl_dir + wsdl_file
      options[:endpoint] = endpoint
      default_savon_options(options)
      @client = Savon.client(options)
    end

    # Validates the presence of the passed value.
    #
    # @param value [Object] object to validate presence
    # @param name [String] name of value to include in error message if not present
    # @return [void]
    # @raise [ArgumentError] if value is not present
    def validate_presence_of(value, name)
      if value.respond_to?(:each)
        value.each do |v|
          if v.blank?
            raise ArgumentError, "Values in #{name} must not be blank"
          end
        end
      else
        if value.blank?
          raise ArgumentError, "#{name} must not be blank"
        end
      end
    end

    # Validates the inclusion of the passed value in a given set.
    #
    # @param value [Object] object to validate inclusion
    # @param set [Array] set to validate inclusion
    # @param name [String] name of value to include in error message if not present
    # @return [void]
    # @raise [ArgumentError] if value is not included
    def validate_inclusion_in(value, set, name)
      unless set.include?(value)
        raise ArgumentError, "#{value} is not a valid #{name}. Must be #{set[0..-2].join(', ')} or #{set.last}."
      end
    end

    # Validates the well formedness of the XML string and raises an error if
    # any errors are encountered.
    #
    # @param xml [String] the XML string to parse
    # @return [void]
    def validate_xml(xml)
      doc = Nokogiri.XML(xml)
      raise ArgumentError, "XML is not well formed: #{xml}" unless doc.errors.empty?
    end

    # Creates an IsbmAdaptor::Message from a ISBM response.
    #
    # @param response [Savon::Response] the ISBM response
    # @return [IsbmAdaptor::Message] the extracted message
    def extract_message(response)
      # Extract the message element
      # e.g. /Envelope/Body/ReadPublicationResponse/PublicationMessage
      soap_ns = 'http://schemas.xmlsoap.org/soap/envelope/'
      message = response.doc.xpath('s:Envelope/s:Body', s: soap_ns).first.first_element_child.first_element_child

      return nil unless message

      id = message.element_children[0].text
      content = message.element_children[1].first_element_child
      topics = message.element_children[2..-1].map {|e| e.text}

      # Retain any ancestor namespaces in case they are applicable for the element
      # and/or children. This is because content.to_xml does not output ancestor
      # namespaces.
      # There may be unnecessary namespaces carried across (e.g. ISBM, SOAP), but we
      # can't tell if the content uses them without parsing the content itself.
      content.namespaces.each do |key, value|
        # Don't replace default namespace if it already exists
        next if key == 'xmlns' && content['xmlns']
        content[key] = value
      end

      # Wrap content in a separate Nokogiri document. This allows the ability to
      # validate the content against a schema.
      doc = Nokogiri::XML(content.to_xml)

      IsbmAdaptor::Message.new(id, doc, topics)
    end

    private

    # Indicates the directory of the ISBM WSDL files
    #
    # @return [String] directory of ISBM WSDL files
    def wsdl_dir
      File.expand_path(File.dirname(__FILE__)) + '/../../wsdls/'
    end

    # Sets default values for certain Savon options.
    #
    # @param options [Hash] the options to set defaults on
    # @return [Hash] options hash with defaults set
    def default_savon_options(options)
      options[:logger] = Rails.logger if options[:logger].nil? && defined?(Rails)
      options[:log] = false if options[:log].nil?
      options[:pretty_print_xml] = true if options[:pretty_print_xml].nil?
    end
  end
end


require 'isbm_adaptor/client'

module IsbmAdaptor
  class ConsumerPublication < IsbmAdaptor::Client

    # Creates a new ISBM ConsumerPublication client.
    #
    # @param endpoint [String] the SOAP endpoint URI
    # @option options [Array<String>] :wsse_auth username and password, i.e. [username, password]
    # @option options [Object] :logger (Rails.logger or $stdout) location where log should be output
    # @option options [Boolean] :log (true) specify whether requests are logged
    # @option options [Boolean] :pretty_print_xml (false) specify whether request and response XML are formatted
    def initialize(endpoint, options = {})
      super('ConsumerPublicationService.wsdl', endpoint, options)
    end

    # Opens a subscription session for a channel.
    #
    # @param uri [String] the channel URI
    # @param topics [Array<String>, String] a collection of topics or single topic
    # @param listener_url [String] the URL for notification callbacks
    # @param xpath_expression [String] the XPath filter expression
    # @param xpath_namespaces [Array<Hash>] the prefixes and namespaces used by the XPath expression. The hash key
    #   represents the namespace prefix while the value represents the namespace name. For example,
    #   ["xs" => "http://www.w3.org/2001/XMLSchema", "isbm" => "http://www.openoandm.org/xml/ISBM/"]
    # @return [String] the session id
    # @raise [ArgumentError] if uri or topics are blank
    def open_session(uri, topics, listener_url = nil, xpath_expression = nil, xpath_namespaces = [])
      validate_presence_of uri, 'Channel URI'
      validate_presence_of topics, 'Topics'
      validate_presence_of xpath_expression, 'XPath Expression' if xpath_namespaces.present?

      topics = [topics].flatten

      # Use Builder to generate XML body as we may have multiple Topic elements
      xml = Builder::XmlMarkup.new
      xml.isbm :ChannelURI, uri
      topics.each do |topic|
        xml.isbm :Topic, topic
      end
      xml.isbm :ListenerURL, listener_url unless listener_url.nil?
      xml.isbm :XPathExpression, xpath_expression unless xpath_expression.nil?
      xpath_namespaces.each do |prefix, name|
        xml.isbm :XPathNamespace do
          xml.isbm :NamespacePrefix, prefix
          xml.isbm :NamespaceName, name
        end
      end

      response = @client.call(:open_subscription_session, message: xml.target!)

      response.to_hash[:open_subscription_session_response][:session_id].to_s
    end

    # Reads the first message, if any, in the session queue.
    #
    # @param session_id [String] the session id
    # @return [Message] first message in session queue. nil if no message.
    # @raise [ArgumentError] if session_id is blank
    def read_publication(session_id)
      validate_presence_of session_id, 'Session Id'

      response = @client.call(:read_publication, message: { 'SessionID' => session_id })

      extract_message(response)
    end

    # Removes the first message, if any, in the session queue.
    #
    # @param session_id [String] the session id
    # @return [void]
    # @raise [ArgumentError] if session_id is blank
    def remove_publication(session_id)
      validate_presence_of session_id, 'Session Id'

      @client.call(:remove_publication, message: { 'SessionID' => session_id })

      return true
    end

    # Closes a subscription session.
    #
    # @param session_id [String] the session id
    # @return [void]
    # @raise [ArgumentError] if session_id is blank
    def close_session(session_id)
      validate_presence_of session_id, 'Session Id'

      @client.call(:close_subscription_session, message: { 'SessionID' => session_id })

      return true
    end
  end
end


require 'isbm_adaptor/client'

module IsbmAdaptor
  class ConsumerRequest < IsbmAdaptor::Client

    # Creates a new ISBM ConsumerRequest client.
    #
    # @param endpoint [String] the SOAP endpoint URI
    # @option options [Array<String>] :wsse_auth username and password, i.e. [username, password]
    # @option options [Object] :logger (Rails.logger or $stdout) location where log should be output
    # @option options [Boolean] :log (true) specify whether requests are logged
    # @option options [Boolean] :pretty_print_xml (false) specify whether request and response XML are formatted
    def initialize(endpoint, options = {})
      super('ConsumerRequestService.wsdl', endpoint, options)
    end

    # Opens a consumer request session for a channel for posting requests and
    # reading responses.
    #
    # @param uri [String] the channel URI
    # @param listener_url [String] the URL for notification callbacks
    # @return [String] the session id
    # @raise [ArgumentError] if uri is blank
    def open_session(uri, listener_url = nil)
      validate_presence_of uri, 'Channel URI'

      message = { 'ChannelURI' => uri }
      message['ListenerURL'] = listener_url if listener_url

      response = @client.call(:open_consumer_request_session, message: message)

      response.to_hash[:open_consumer_request_session_response][:session_id].to_s
    end

    # Posts a request message on a channel.
    #
    # @param session_id [String] the session id
    # @param content [String] a valid XML string as message contents
    # @param topic [String] the topic
    # @param expiry [Duration] when the message should expire
    # @return [String] the request message id
    # @raise [ArgumentError] if session_id, content or topics are blank, or
    #   content is not valid XML
    def post_request(session_id, content, topic, expiry = nil)
      validate_presence_of session_id, 'Session Id'
      validate_presence_of content, 'Content'
      validate_presence_of topic, 'Topic'
      validate_xml content

      # Use Builder to generate XML body as we need to concatenate XML message content
      xml = Builder::XmlMarkup.new
      xml.isbm :SessionID, session_id
      xml.isbm :MessageContent do
        xml << content
      end
      xml.isbm :Topic, topic
      duration = expiry.to_s
      xml.isbm :Expiry, duration unless duration.nil?

      response = @client.call(:post_request, message: xml.target!)

      response.to_hash[:post_request_response][:message_id].to_s
    end

    # Expires a posted request message.
    #
    # @param session_id [String] the session id used to post the request
    # @param message_id [String] the message id received after posting the request
    # @return [void]
    # @raise [ArgumentError] if session_id or message_id are blank
    def expire_request(session_id, message_id)
      validate_presence_of session_id, 'Session Id'
      validate_presence_of message_id, 'Message Id'

      @client.call(:expire_request, message: { 'SessionID' => session_id, 'MessageID' => message_id })

      return true
    end

    # Returns the first response message, if any, in the message queue
    # associated with the request.
    #
    # @param session_id [String] the session id
    # @param request_message_id [String] the id of the original request message
    # @return [Message] the first message in the queue for the session.
    #   nil if no message.
    # @raise [ArgumentError] if session_id or request_message_id are blank
    def read_response(session_id, request_message_id)
      validate_presence_of session_id, 'Session Id'
      validate_presence_of request_message_id, 'Request Message Id'

      message = { 'SessionID' => session_id, 'RequestMessageID' => request_message_id }
      response = @client.call(:read_response, message: message)

      extract_message(response)
    end

    # Deletes the first response message, if any, in the message queue
    # associated with the request.
    #
    # @param session_id [String] the session id
    # @param request_message_id [String] the id of the original request message
    # @return [void]
    # @raise [ArgumentError] if session_id is blank
    def remove_response(session_id, request_message_id)
      validate_presence_of session_id, 'Session Id'
      validate_presence_of request_message_id, 'Request Message Id'

      message = { 'SessionID' => session_id, 'RequestMessageID' => request_message_id }
      @client.call(:remove_response, message: message)

      return true
    end

    # Closes a consumer request session.
    #
    # @param session_id [String] the session id
    # @return [void]
    # @raise [ArgumentError] if session_id is blank
    def close_session(session_id)
      validate_presence_of session_id, 'Session Id'

      @client.call(:close_consumer_request_session, message: { 'SessionID' => session_id })

      return true
    end
  end
end


require 'isbm_adaptor/client'

module IsbmAdaptor
  class ProviderRequest < IsbmAdaptor::Client

    # Creates a new ISBM ProviderRequest client.
    #
    # @param endpoint [String] the SOAP endpoint URI
    # @option options [Array<String>] :wsse_auth username and password, i.e. [username, password]
    # @option options [Object] :logger (Rails.logger or $stdout) location where log should be output
    # @option options [Boolean] :log (true) specify whether requests are logged
    # @option options [Boolean] :pretty_print_xml (false) specify whether request and response XML are formatted
    def initialize(endpoint, options = {})
      super('ProviderRequestService.wsdl', endpoint, options)
    end

    # Opens a provider request session for a channel for reading requests and
    # posting responses.
    #
    # @param uri [String] the channel URI
    # @param topics [Array<String>, String] a collection of topics or single topic
    # @param listener_url [String] the URL for notification callbacks
    # @param xpath_expression [String] the XPath filter expression
    # @param xpath_namespaces [Array<Hash>] the prefixes and namespaces used by the XPath expression. The hash key
    #   represents the namespace prefix while the value represents the namespace name. For example,
    #   ["xs" => "http://www.w3.org/2001/XMLSchema", "isbm" => "http://www.openoandm.org/xml/ISBM/"]
    # @return [String] the session id
    # @raise [ArgumentError] if uri or topics are blank
    def open_session(uri, topics, listener_url = nil, xpath_expression = nil, xpath_namespaces = [])
      validate_presence_of uri, 'Channel URI'
      validate_presence_of topics, 'Topics'
      validate_presence_of xpath_expression, 'XPath Expression' if xpath_namespaces.present?

      topics = [topics].flatten

      # Use Builder to generate XML body as we may have multiple Topic elements
      xml = Builder::XmlMarkup.new
      xml.isbm :ChannelURI, uri
      topics.each do |topic|
        xml.isbm :Topic, topic
      end
      xml.isbm :ListenerURL, listener_url unless listener_url.nil?
      xml.isbm :XPathExpression, xpath_expression unless xpath_expression.nil?
      xpath_namespaces.each do |prefix, name|
        xml.isbm :XPathNamespace do
          xml.isbm :NamespacePrefix, prefix
          xml.isbm :NamespaceName, name
        end
      end

      response = @client.call(:open_provider_request_session, message: xml.target!)

      response.to_hash[:open_provider_request_session_response][:session_id].to_s
    end

    # Returns the first request message in the message queue for the session.
    # Note: this service does not remove the message from the message queue.
    #
    # @param session_id [String] the session id
    # @return [Message] the first message in the queue for the session.
    #   nil if no message.
    # @raise [ArgumentError] if session_id is blank
    def read_request(session_id)
      validate_presence_of session_id, 'Session Id'

      message = { 'SessionID' => session_id }
      response = @client.call(:read_request, message: message)

      extract_message(response)
    end

    # Deletes the first request message, if any, in the message queue for the session.
    #
    # @param session_id [String] the session id
    # @return [void]
    # @raise [ArgumentError] if session_id is blank
    def remove_request(session_id)
      validate_presence_of session_id, 'Session Id'

      @client.call(:remove_request, message: { 'SessionID' => session_id })

      return true
    end

    # Posts a response message on a channel.
    #
    # @param session_id [String] the session id
    # @param request_message_id [String] the id of the original request message
    # @param content [String] a valid XML string as message contents
    # @return [String] the response message id
    # @raise [ArgumentError] if session_id, request_message_id or content are
    #   blank, or content is not valid XML
    def post_response(session_id, request_message_id, content)
      validate_presence_of session_id, 'Session Id'
      validate_presence_of request_message_id, 'Request Message Id'
      validate_presence_of content, 'Content'
      validate_xml content

      # Use Builder to generate XML body as we need to concatenate XML message content
      xml = Builder::XmlMarkup.new
      xml.isbm :SessionID, session_id
      xml.isbm :RequestMessageID, request_message_id
      xml.isbm :MessageContent do
        xml << content
      end

      response = @client.call(:post_response, message: xml.target!)

      response.to_hash[:post_response_response][:message_id].to_s
    end

    # Closes a provider request session.
    #
    # @param session_id [String] the session id
    # @return [void]
    # @raise [ArgumentError] if session_id is blank
    def close_session(session_id)
      validate_presence_of session_id, 'Session Id'

      @client.call(:close_provider_request_session, message: { 'SessionID' => session_id })

      return true
    end
  end
end


require 'isbm_adaptor/client'
require 'isbm_adaptor/channel'

module IsbmAdaptor
  class ChannelManagement < IsbmAdaptor::Client
    # Creates a new ISBM ChannelManagement client.
    #
    # @param endpoint [String] the SOAP endpoint URI
    # @option options [Array<String>] :wsse_auth username and password, i.e. [username, password]
    # @option options [Object] :logger (Rails.logger or $stdout) location where log should be output
    # @option options [Boolean] :log (true) specify whether requests are logged
    # @option options [Boolean] :pretty_print_xml (false) specify whether request and response XML are formatted
    def initialize(endpoint, options = {})
      super('ChannelManagementService.wsdl', endpoint, options)
    end

    # Creates a new channel.
    #
    # @param uri [String] the channel URI
    # @param type [Symbol] the channel type, either publication or request (symbol or titleized string)
    # @param description [String] the channel description, defaults to nil
    # @param tokens [Hash] username password pairs, e.g. {'u1' => 'p1', 'u2' => 'p2'}
    # @return [void]
    # @raise [ArgumentError] if uri or type are blank or type is not a valid Symbol
    def create_channel(uri, type, description = nil, tokens = {})
      validate_presence_of uri, 'Channel URI'
      validate_presence_of type, 'Channel Type'
      channel_type = type.to_s.downcase.capitalize
      validate_inclusion_in channel_type, IsbmAdaptor::Channel::TYPES, 'Channel Type'

      message = { 'ChannelURI' => uri,
                  'ChannelType' => channel_type }
      message['ChannelDescription'] = description unless description.nil?
      message['SecurityToken'] = security_token_hash(tokens) if tokens.any?

      @client.call(:create_channel, message: message)

      return true
    end

    # Adds security tokens to a channel.
    #
    # @param uri [String] the channel URI
    # @param tokens [Hash] username password pairs, e.g. {'u1' => 'p1', 'u2' => 'p2'}
    # @return [void]
    # @raise [ArgumentError] if uri is blank or no tokens are provided
    def add_security_tokens(uri, tokens = {})
      validate_presence_of uri, 'Channel URI'
      validate_presence_of tokens, 'Security Tokens'

      message = { 'ChannelURI' => uri,
                  'SecurityToken' => security_token_hash(tokens) }

      @client.call(:add_security_tokens, message: message)

      return true
    end

    # Removes security tokens from a channel.
    #
    # @param uri [String] the channel URI
    # @param tokens [Hash] username password pairs, e.g. {'u1' => 'p1', 'u2' => 'p2'}
    # @return [void]
    # @raise [ArgumentError] if uri is blank or no tokens are provided
    def remove_security_tokens(uri, tokens = {})
      validate_presence_of uri, 'Channel URI'
      validate_presence_of tokens, 'Security Tokens'

      message = { 'ChannelURI' => uri,
                  'SecurityToken' => security_token_hash(tokens) }

      @client.call(:remove_security_tokens, message: message)

      return true
    end

    # Deletes the specified channel.
    #
    # @param uri [String] the channel URI
    # @return [void]
    # @raise [ArgumentError] if uri is blank
    def delete_channel(uri)
      validate_presence_of uri, 'Channel URI'

      @client.call(:delete_channel, message: { 'ChannelURI' => uri })

      return true
    end

    # Gets information about the specified channel.
    #
    # @param uri [String] the channel URI
    # @yield locals local options, including :wsse_auth
    # @return [Channel] the queried channel
    # @raise [ArgumentError] if uri is blank
    def get_channel(uri, &block)
      validate_presence_of uri, 'Channel URI'

      response = @client.call(:get_channel, message: { 'ChannelURI' => uri }, &block)

      hash = response.to_hash[:get_channel_response][:channel]
      IsbmAdaptor::Channel.from_hash(hash)
    end

    # Gets information about all channels.
    #
    # @yield locals local options, including :wsse_auth
    # @return [Array<Channel>] all authorized channels on the ISBM
    def get_channels(&block)
      response = @client.call(:get_channels, {}, &block)

      channels = response.to_hash[:get_channels_response][:channel]
      channels = [channels].compact unless channels.is_a?(Array)
      channels.map do |hash|
        IsbmAdaptor::Channel.from_hash(hash)
      end
    end

    private
    # Returns tokens mapped to wsse:UsernameToken hash.
    def security_token_hash(tokens)
      wsse = Akami.wsse
      tokens.map do |username, password|
        wsse.credentials(username, password)
        # Extract the UsernameToken element
        username_token = wsse.send(:wsse_username_token)['wsse:Security']
        # Restore the wsse namespace
        ns = {'xmlns:wsse' => Akami::WSSE::WSE_NAMESPACE}
        username_token[:attributes!]['wsse:UsernameToken'].merge!(ns)
        username_token
      end
    end
  end
end


module Idioma
  class Phrase < ActiveRecord::Base

    # == Constants ============================================================

    # == Attributes ===========================================================

    # == Extensions ===========================================================

    # == Relationships ========================================================

    # == Validations ==========================================================

    validates :locale, :i18n_key, presence: true
    validates :i18n_key, uniqueness: { scope: :locale }

    # == Scopes ===============================================================

    scope :translated,   lambda { where("translated_at is not null") }
    scope :untranslated, lambda { where("translated_at is null") }
    scope :missing,      lambda { where("i18n_value is null") }

    # == Callbacks ============================================================

    before_validation :strip_values

    # == Class Methods ========================================================

    # Given a base locale, duplicate all phrases to a set of other locales
    #
    # @param [Symbol] The base locale
    # @param [Array] An array of other locales
    def self.duplicate_for_locales(base_locale, locales)
      locales = [locales] unless locales.class == Array
      where(locale: base_locale).find_each do |base_phrase|
        locales.each do |locale|
          unless where(locale: locale, i18n_key: base_phrase.i18n_key).exists?
            phrase = Phrase.create!({
              locale: locale,
              i18n_key: base_phrase.i18n_key
            })
          end
        end
      end
    end

    # Take what's in the database and prime the i18n backend store (Redis)
    def self.prime_backend
      find_each do |phrase|
        phrase.update_backend
      end
    end

    # == Instance Methods =====================================================

    # Update the record and then push those changes to the i18n backend
    #
    # @param [Hash] The Phrase attributes to update
    # @return [Boolean] Whether the update was successful
    def update_and_update_backend(params = {})
      self.translated_at = Time.zone.now if self.phrase_untranslated?

      result = self.update(params)
      if result
        self.update_backend
      end
      result
    end

    # Save and push the changes to the i18n backend
    #
    # @param [Hash] The Phrase attributes to save
    # @return [Boolean] Whether the save was successful
    def save_and_update_backend(params = {})
      result = self.save(params)
      if result
        self.update_backend
      end
      result
    end

    # Will update the i18n backend if it has been configured
    def update_backend
      if Idioma.configuration.redis_backend
        if i18n_value.present?
          Idioma::RedisBackend.update_phrase(self)
        else
          Idioma::RedisBackend.delete_phrase(self)
        end
      end
    end

    # Is this phrase translated?
    # @return [Boolean]
    def phrase_translated?
      translated_at.present?
    end

    # Is this phrase untranslated?
    # @return [Boolean]
    def phrase_untranslated?
      !phrase_translated?
    end

    private

    # Strip the values before validating the model
    def strip_values
      [:i18n_value].each do |field|
        self.send(:"#{field}=", self.send(field).to_s.strip)
      end
    end

  end
end


require 'ffi'

module Console

    # Implements Windows-specific platform functionality
    module Windows

        extend FFI::Library

        # Constants representing STDIN, STDOUT, and STDERR
        STD_OUTPUT_HANDLE = 0xFFFFFFF5
        STD_INPUT_HANDLE = 0xFFFFFFF6
        STD_ERROR_HANDLE = 0xFFFFFFF7

        ffi_lib 'kernel32.dll'
        ffi_convention :stdcall

        # FFI structure used to get/set information about the current console
        # window buffer
        class BufferInfo < FFI::Struct
            layout  :width, :short,
                    :height, :short,
                    :cursor_x, :short,
                    :cursor_y, :short,
                    :text_attributes, :ushort,
                    :window_left, :short,
                    :window_top, :short,
                    :window_right, :short,
                    :window_bottom, :short,
                    :max_width, :short,
                    :max_height, :short
        end

        # FFI structure used to get/set buffer co-ordinates
        class Coord < FFI::Struct
            layout  :x, :short,
                    :y, :short

            def initialize(x, y)
                self[:x] = x
                self[:y] = y
            end
        end

        # Define Windows console functions we need
        attach_function :get_std_handle, :GetStdHandle, [:uint], :pointer
        attach_function :get_console_screen_buffer_info, :GetConsoleScreenBufferInfo, [:pointer, :pointer], :bool
        attach_function :set_console_cursor_position, :SetConsoleCursorPosition, [:pointer, Coord.by_value], :bool
        attach_function :set_console_text_attribute, :SetConsoleTextAttribute, [:pointer, :ushort], :bool
        attach_function :set_console_title, :SetConsoleTitleA, [:pointer], :bool

        # Retrieve a BufferInfo object
        def buffer_info
            @buffer_info ||= BufferInfo.new
        end
        module_function :buffer_info
        private :buffer_info

        # Sets the cursor position to the specified +x+ and +y+ locations in the
        # console output buffer. If +y+ is nil, the cursor is positioned at +x+ on
        # the current line.
        def set_cursor_position(x, y)
            if stdout && x && y
                coord = Coord.new(x, y)
                self.set_console_cursor_position(stdout, coord)
            end
        end
        module_function :set_cursor_position

    end

end


#require_dependency "idioma/application_controller"

module Idioma
  class PhrasesController < ApplicationController
    before_action :set_phrase, only: [:edit, :update, :show]

    # GET /phrases
    def index
      params[:locale_eq] ||= I18n.default_locale
      scope = Phrase.where(locale: params[:locale_eq])

      if params[:q].present?
        scope = scope.where("i18n_key ilike ? OR i18n_value ilike ?", "%#{params[:q]}%", "%#{params[:q]}%")
      end

      respond_to do |format|
        format.html {
          @phrases = scope.paginate(:page => params[:page])
        }
        format.csv {
          render text: PhraseExporter.to_csv(scope)
        }
        format.yaml {
          render text: PhraseExporter.to_yaml(scope)
        }
        format.json {
          @phrases = scope.paginate(:page => params[:page])
          render json: {
            meta: {
              pagination: {
                current_page: @phrases.current_page,
                per_page: @phrases.per_page,
                total_entries: @phrases.total_entries
              }
            },
            phrases: @phrases
          }
        }
      end
    end

    # GET /phrases/1
    def show
      respond_to do |format|
        format.json {
          render json: @phrase
        }
      end
    end

    # PATCH/PUT /phrases/1
    def update
      result = @phrase.update_and_update_backend(phrase_params)
      respond_to do |format|
        format.html {
          if result
            redirect_to [:edit, @phrase]
          else
            render :edit
          end
        }
        format.json {
          if result
            render json: @phrase
          else
            render json: {
              errors: @phrase.errors.messages
            }.merge(@phrase.attributes),
            status: :bad_request
          end
        }
      end

    end

    private
      # Use callbacks to share common setup or constraints between actions.
      def set_phrase
        @phrase = Phrase.find(params[:id])
      rescue ActiveRecord::RecordNotFound
        respond_to do |format|
          format.json { render json: {}.to_json, status: :not_found }
          format.html {
            flash[:error] = t('idioma.record_not_found')
            redirect_to phrases_path
          }
        end
      end

      # Only allow a trusted parameter "white list" through.
      def phrase_params
        params.require(:phrase).permit(:locale, :i18n_key, :i18n_value, :flagged_at, :notes)
      end
  end
end


module IsbmAdaptor
  class Duration
    # @return [Numeric] the years component of the duration
    attr_accessor :years

    # @return [Numeric] the months component of the duration
    attr_accessor :months

    # @return [Numeric] the days component of the duration
    attr_accessor :days

    # @return [Numeric] the hours component of the duration
    attr_accessor :hours

    # @return [Numeric] the minutes component of the duration
    attr_accessor :minutes

    # @return [Numeric] the seconds component of the duration
    attr_accessor :seconds

    # Creates a new Duration based on specified time components.
    #
    # @option duration [Numeric] :years duration in years
    # @option duration [Numeric] :months duration in months
    # @option duration [Numeric] :days duration in days
    # @option duration [Numeric] :hours duration in hours
    # @option duration [Numeric] :minutes duration in minutes
    # @option duration [Numeric] :seconds duration in seconds
    def initialize(duration)
      duration.keys.each do |key|
        raise ArgumentError.new "Invalid key: #{key}" unless VALID_SYMBOLS.include?(key)
      end

      duration.each do |key, value|
        raise ArgumentError.new "Value for #{key} cannot be less than 0" if value < 0
      end

      @years = duration[:years]
      @months = duration[:months]
      @days = duration[:days]
      @hours = duration[:hours]
      @minutes = duration[:minutes]
      @seconds = duration[:seconds]
    end

    # @return [String] ISO 8601 formatted duration
    def to_s
      date = []
      date << "#{@years}Y" unless @years.nil?
      date << "#{@months}M" unless @months.nil?
      date << "#{@days}D" unless @days.nil?

      time = []
      time << "#{@hours}H" unless @hours.nil?
      time << "#{@minutes}M" unless @minutes.nil?
      time << "#{@seconds}S" unless @seconds.nil?

      result = nil

      if !date.empty? || !time.empty?
        result = 'P'
        result += date.join unless date.empty?
        result += 'T' + time.join unless time.empty?
      end

      result
    end

    # Creates a hash of the time components. Any keys with nil values are
    # excluded from the result.
    #
    # @return [Hash] all specified time components
    def to_hash
      hash = {}
      hash[:years] = @years if @years
      hash[:months] = @months if @months
      hash[:days] = @days if @days
      hash[:hours] = @hours if @hours
      hash[:minutes] = @minutes if @minutes
      hash[:seconds] = @seconds if @seconds
      hash
    end

    private
    # Valid time components in a duration
    VALID_SYMBOLS = [:years, :months, :days, :hours, :minutes, :seconds]
  end
end


require 'geo_units'

module GeoCalc
  module PrettyPrint

    # Returns the latitude of this point; signed numeric degrees if no format, otherwise format & dp
    #
    # @param [String] format: Return value as 'd', 'dm', 'dms'
    # @param [Numeric] dp: No of decimal places to display (0|2|4)
    #
    # @return   [Numeric|String]: Numeric degrees if no format specified, otherwise deg/min/sec
    #
    def to_lat format = :dms, dp = 0
      return lat if !format
      GeoUnits::Converter.to_lat lat, format, dp
    end

    # Returns the longitude of this point; signed numeric degrees if no format, otherwise format & dp
    # as per Geo.toLon()
    #
    # @param   [String} [format]: Return value as 'd', 'dm', 'dms'
    # @param   [Number} [dp=0|2|4]: No of decimal places to display
    # @return [Number|String} Numeric degrees if no format specified, otherwise deg/min/sec
    #
    # @requires Geo

    def to_lon format, dp
      return lon if !format
      GeoUnits::Converter.to_lon lon, format, dp
    end

    # Returns a string representation of this point; format and dp as per lat()/lon()
    #
    # @param   [String] format: Return value as 'd', 'dm', 'dms'
    # @param   [Number] dp: No of decimal places to display (0|2|4)

    # @return [String] Comma-separated latitude/longitude
    #

    def to_s format = :dms, dp = 0
      format ||= :dms

      return '-,-' if !lat || !lon

      _lat = GeoUnits::Converter.to_lat lat, format, dp
      _lon = GeoUnits::Converter.to_lon lon, format, dp

      "#{_lat}, #{_lon}"
    end
  end
end


module Optimizely
  class Engine

    BASE_URL = "https://www.optimizelyapis.com/experiment/v1/"

    attr_accessor :url

    # Initialize Optimizely using an API token.
    #
    # == Options:
    # +:api_token+:: Use an API token you received before.
    # +:timeout+::   Set Net:HTTP timeout in seconds (default is 300).
    #
    def initialize(options={})
      @options = options
      check_init_auth_requirements
    end

    # Initalize the Token so it can be used in every request we'll do later on.
    # Besides that also set the authentication header.
    def token=(token)
      @token = token
      set_headers
    end

    # Returns the list of projects available to the authenticated user.
    #
    # == Usage
    #  optimizely = Optimizely.new({ api_token: 'oauth2_token' })
    #  projects = optimizely.projects # Look up all projects.
    #
    def projects
      if @projects.nil?
        response = self.get("projects")
        @projects = response.collect { |project_json| Project.new(project_json) }
      end
      @projects
    end

    # Returns the details for a specific project.
    #
    # == Usage
    #  optimizely = Optimizely.new({ api_token: 'oauth2_token' })
    #  project = optimizely.project(12345) # Look up the project.
    #
    def project(id)
      @url = "projects/#{id}"
      raise OptimizelyError::NoProjectID, "A Project ID is required to retrieve the project." if id.nil?

      response = self.get(@url)
      Project.new(response)
    end

    # Returns the list of experiments for a specified project.
    #
    # == Usage
    #  optimizely = Optimizely.new({ api_token: 'oauth2_token' })
    #  experiments = optimizely.experiments(12345) # Look up all experiments for a project.
    #
    def experiments(project_id)
      raise OptimizelyError::NoProjectID, "A Project ID is required to retrieve experiments." if project_id.nil?

      response = self.get("projects/#{project_id}/experiments")
      response.collect { |response_json| Experiment.new(response_json) }
    end

    # Returns the details for a specific experiment.
    #
    # == Usage
    #  optimizely = Optimizely.new({ api_token: 'oauth2_token' })
    #  experiment = optimizely.experiment(12345) # Look up the experiment.
    #
    def experiment(id)
      @url = "experiments/#{id}"
      raise OptimizelyError::NoExperimentID, "An Experiment ID is required to retrieve the experiment." if id.nil?

      response = self.get(@url)
      Experiment.new(response)
    end

    # Returns the stats for a specific experiment.
    #
    # == Usage
    #  optimizely = Optimizely.new({ api_token: 'oauth2_token' })
    #  stats = optimizely.stats(12345) # Look up the stats with the specific experiment.
    #
    def stats(experiment_id)
      @url = "experiments/#{experiment_id}/stats"
      raise OptimizelyError::NoExperimentID, "An Experiment ID is required to retrieve the stats." if experiment_id.nil?

      response = self.get(@url)
      response.collect { |response_json| Stat.new(response_json) }
    end

    # Returns the list of variations for a specified experiment.
    #
    # == Usage
    #  optimizely = Optimizely.new({ api_token: 'oauth2_token' })
    #  variations = optimizely.variations(12345) # Look up all variations for an experiment.
    #
    def variations(experiment_id)
      raise OptimizelyError::NoExperimentID, "An Experiment ID is required to retrieve variations." if experiment_id.nil?

      response = self.get("experiments/#{experiment_id}/variations")
      response.collect { |variation_json| Variation.new(variation_json) }
    end

    # Returns the details for a specific variation.
    #
    # == Usage
    #  optimizely = Optimizely.new({ api_token: 'oauth2_token' })
    #  variation = optimizely.variation(12345) # Look up the variation.
    #
    def variation(id)
      @url = "variations/#{id}"
      raise OptimizelyError::NoVariationID, "A Variation ID is required to retrieve the variation." if id.nil?

      response = self.get(@url)
      Variation.new(response)
    end

    # Returns the list of audiences for a specified project.
    #
    # == Usage
    #  optimizely = Optimizely.new({ api_token: 'oauth2_token' })
    #  audiences = optimizely.audiences(12345) # Look up all audiences for a project.
    #
    def audiences(project_id)
      raise OptimizelyError::NoProjectID, "A Project ID is required to retrieve audiences." if project_id.nil?

      response = self.get("projects/#{project_id}/audiences")
      response.collect { |audience_json| Audience.new(audience_json) }
    end

    # Returns the details for a specific audience.
    #
    # == Usage
    #  optimizely = Optimizely.new({ api_token: 'oauth2_token' })
    #  audience = optimizely.audience(12345) # Look up the audience.
    #
    def audience(id)
      @url = "audiences/#{id}"
      raise OptimizelyError::NoAudienceID, "An Audience ID is required to retrieve the audience." if id.nil?

      response = self.get(@url)
      Audience.new(response)
    end

    # Return the parsed JSON data for a request that is done to the Optimizely REST API.
    def get(url)
      uri      = URI.parse("#{BASE_URL}#{url}/")
      https    = Net::HTTP.new(uri.host, uri.port)
      https.read_timeout = @options[:timeout] if @options[:timeout]
      https.verify_mode = OpenSSL::SSL::VERIFY_NONE
      https.use_ssl = true
      request  = Net::HTTP::Get.new(uri.request_uri, @headers)
      response = https.request(request)

      # Response code error checking
      if response.code != '200'
        check_response(response.code, response.body)
      else
        parse_json(response.body)
      end
    end

    def post
      uri      = URI.parse("#{BASE_URL}#{url}/")
      https    = Net::HTTP.new(uri.host, uri.port)
      https.read_timeout = @options[:timeout] if @options[:timeout]
      https.verify_mode = OpenSSL::SSL::VERIFY_NONE
      https.use_ssl = true
      request  = Net::HTTP::Post.new(uri.request_uri, @headers)
      response = https.request(request)

      # Response code error checking
      check_response(response.code, response.body) if response.code != '201'
    end

    def put
      uri      = URI.parse("#{BASE_URL}#{url}/")
      https    = Net::HTTP.new(uri.host, uri.port)
      https.read_timeout = @options[:timeout] if @options[:timeout]
      https.verify_mode = OpenSSL::SSL::VERIFY_NONE
      https.use_ssl = true
      request  = Net::HTTP::Put.new(uri.request_uri, @headers)
      response = https.request(request)

      # Response code error checking
      check_response(response.code, response.body) if response.code != '202'
    end

    def delete
      raise OptimizelyError::NoId, "An ID is required to delete data." if @url.nil?

      uri      = URI.parse("#{BASE_URL}#{@url}")
      https    = Net::HTTP.new(uri.host, uri.port)
      https.read_timeout = @options[:timeout] if @options[:timeout]
      https.verify_mode = OpenSSL::SSL::VERIFY_NONE
      https.use_ssl = true
      request  = Net::HTTP::Delete.new(uri.request_uri, @headers)
      response = https.request(request)

      # Response code error checking
      check_response(response.code, response.body) if response.code != '204'
    end

    def check_response(code, body)
      case code
      when '400'
        raise OptimizelyError::BadRequest, body + "Your request was not sent in valid JSON. (status code: #{code})."
      when '401'
        raise OptimizelyError::Unauthorized, "Your API token was missing or included in the body rather than the header (status code: #{code})."
      when '403'
        raise OptimizelyError::Forbidden, body + "You provided an API token but it was invalid or revoked, or if you don't have read/ write access to the entity you're trying to view/edit (status code: #{code})."
      when '404'
        raise OptimizelyError::NotFound, body + "The id used in the request was inaccurate or you didn't have permission to view/edit it (status code: #{code})."
      else
        raise OptimizelyError::UnknownError, body + " (status code: #{code})."
      end
    end

    private

    # If there is no API token or an empty API token raise an exception.
    def check_init_auth_requirements
      if @options[:api_token].nil? || @options[:api_token].empty?
        raise OptimizelyError::NoAPIToken, "An API token is required to initialize Optimizely."
      else
        self.token = @options[:api_token]
      end
    end

    # As the authentication for the Optimizely Experiments API is handled via the
    # Token header in every request we set the header + also make clear we expect JSON.
    def set_headers
      @headers = {}
      @headers['Token'] = @token
      @headers['Content-Type'] = "application/json"
    end

    # Parse the JSON data that's been requested through the get method.
    def parse_json(data)
      json = JSON.parse(data)
      json
    end
  end
end


# encoding: utf-8

require 'date'

module SayWhen
  # Based on the extended cron capabilties
  # http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-06.html
  class CronExpression
    attr_reader :expression
    attr_accessor :time_zone, :seconds, :minutes, :hours, :days_of_month, :months, :days_of_week, :years

    def initialize(expression = {}, time_zone = nil)
      if expression.is_a?(Hash)
        opts = expression

        @expression = if opts[:expression]
          opts[:expression]
        else
          [:days_of_month, :days_of_week].each do |f|
            opts[f] ||= '?'
          end

          [:seconds, :minutes, :hours, :days_of_month, :months, :days_of_week, :years].each do |f|
            opts[f] ||= '*'
          end

          "#{opts[:seconds]} #{opts[:minutes]} #{opts[:hours]} #{opts[:days_of_month]} #{opts[:months]} #{opts[:days_of_week]} #{opts[:years]}"
        end

        @time_zone = opts[:time_zone]
      else
        @expression = expression
      end

      @time_zone ||= time_zone
      if @time_zone.blank?
        @time_zone = Time.zone.try(:name) || "UTC"
      end

      parse
      validate
    end

    def parse
      return if expression.blank?
      vals = expression.split.map{ |word| word.upcase.gsub(/\s/, '') }
      self.seconds       = SecondsCronValue.new(vals[0])
      self.minutes       = MinutesCronValue.new(vals[1])
      self.hours         = HoursCronValue.new(vals[2])
      self.days_of_month = DaysOfMonthCronValue.new(vals[3])
      self.months        = MonthsCronValue.new(vals[4])
      self.days_of_week  = DaysOfWeekCronValue.new(vals[5])
      self.years         = YearsCronValue.new(vals[6] || "*")
    end

    def validate
      return if expression.blank?
      raise "days_of_week or days_of_month needs to be ?" if (days_of_month.is_specified && days_of_week.is_specified)
    end

    def to_s
      "s:#{seconds}m:#{minutes}h:#{hours}dom:#{days_of_month}m:#{months}dow:#{days_of_week}y:#{years}"
    end

    def will_fire_on?(date)
      [seconds, minutes, hours, days_of_month, months, days_of_week, years].detect { |part| !part.include?(date) }.nil?
    end

    def next_fire_at(time=nil)
      Time.zone = time_zone
      after = time.nil? ? Time.zone.now : time.in_time_zone(@time_zone)

      while (true)
        [years, months, days_of_month, days_of_week, hours, minutes, seconds].each do |cron_value|
          after, changed = move_to_next(cron_value, after)
          return if after.nil?
          break if changed
        end

        break if will_fire_on?(after)
      end
      after
    end

    def last_fire_at(time=nil)
      Time.zone = time_zone
      before = time.nil? ? Time.zone.now : time.in_time_zone(@time_zone)

      while (true)
        [years, months, days_of_month, days_of_week, hours, minutes, seconds].each do |cron_value|
          before, changed = move_to_last(cron_value, before)
          return if before.nil?
          break if changed
        end

        break if will_fire_on?(before)
      end
      before
    end

    protected

    def move_to_next(cron_value, after)
      unless cron_value.include?(after)
        after = cron_value.next(after)
        [after, true]
      end
      [after, false]
    end

    def move_to_last(cron_value, before)
      unless cron_value.include?(before)
        before = cron_value.last(before)
        [before, true]
      end
      [before, false]
    end

  end

  class CronValue
    attr_accessor :part, :min, :max, :expression, :values

    def initialize(p, min, max, exp)
      self.part = p
      self.min = min
      self.max = max
      self.values = []
      self.expression = exp
      parse(exp)
    end

    def parse(exp)
      self.values = CronValue.parse_number(min, max, exp.upcase)
    end

    def to_s
      "[e:#{expression}, v:#{values.inspect}]\n"
    end

    def include?(date)
      values.include?(date.send(part))
    end

    #works for secs, mins, hours
    def self.parse_number(min, max, val)
      values = []
      case val
        #check for a '/' for increments
        when /(.+)\/(\d+)/
          (( $1 == "*") ? min : $1.to_i).step(max, $2.to_i) { |x| values << x }

        #check for ',' for list of values
        when /(\d+)(,\d+)+/
          values = val.split(',').map{ |v| v.to_i }.sort

        #check for '-' for range of values
        when /(\d+)-(\d+)/
          values = (($1.to_i)..($2.to_i)).to_a

        #check for '*' for all values between min and max
        when /^(\*)$/
          values = (min..max).to_a

        #lastly, should just be a number
        when /^(\d+)$/
          values << $1.to_i

        #if nothing else, leave values as []
        else values = []
      end
      values
    end
  end

  class SecondsCronValue < CronValue
    def initialize(exp)
      super(:sec, 0, 59, exp)
    end

    def next(date)
      n = values.detect { |v| v > date.sec }
      if n.blank?
        1.minute.since(date).change(sec: values.first)
      else
        date.change(sec: n)
      end
    end

    def last(date)
      n = values.reverse.detect { |v| v < date.sec }
      if n.blank?
        1.minute.ago(date).change(sec: values.last)
      else
        date.change(sec: n)
      end
    end
  end

  class MinutesCronValue < CronValue
    def initialize(exp)
      super(:min, 0, 59, exp)
    end

    def next(date)
      n = values.detect { |v| v > date.min }
      if n.blank?
        1.hour.since(date).change(min: values.first, sec: 0)
      else
        date.change(min: n, sec: 0)
      end
    end

    def last(date)
      n = values.reverse.detect { |v| v < date.min }
      if n.blank?
        1.hour.ago(date).change(min: values.last, sec: 59)
      else
        date.change(min: n, sec: 59)
      end
    end
  end

  class HoursCronValue < CronValue
    def initialize(exp)
      super(:hour, 0, 24, exp)
    end

    def next(date)
      n = values.detect { |v| v > date.hour }
      if n.blank?
        1.day.since(date).change(hour: values.first, min: 0, sec: 0)
      else
        date.change(hour: n, min: 0, sec: 0)
      end
    end

    def last(date)
      n = values.reverse.detect { |v| v < date.hour }
      if n.blank?
        1.day.ago(date).change(hour: values.last, min: 59, sec: 59)
      else
        date.change(hour: n, min: 59, sec: 59)
      end
    end
  end

  class DaysOfMonthCronValue < CronValue
    attr_accessor :is_specified, :is_last, :is_weekday
    def initialize(exp)
      self.is_last = false
      self.is_weekday = false
      super(:mday, 1, 31, exp)
    end

    def parse(exp)
      if self.is_specified = !(expression =~ /\?/)
        case exp
          when /^(L)$/ then self.is_last = true
          when /^(W)$/ then self.is_weekday = true
          when /^(WL|LW)$/ then self.is_last = (self.is_weekday = true)
          when /^(\d+)W$/ then self.is_weekday = true; self.values << $1.to_i
          else super(exp)
        end
      end
    end

    def last(date)
      result = if !is_specified
        date
      elsif is_last
        eom = date.end_of_month
        eom = nearest_week_day(eom) if is_weekday
        if eom > date
          eom = 1.month.ago(date)
          eom = nearest_week_day(eom.change(day: eom.end_of_month))
        end
        eom
      elsif is_weekday
        if values.empty?
          nearest = nearest_week_day(date)
          if nearest > date
            nearest = 1.month.ago(date)
            nearest = nearest_week_day(date.change(day: date.end_of_month))
          end
        else
          nearest = nearest_week_day(date.change(day: values.first))
          nearest = nearest_week_day(1.month.ago(date).change(day: values.first)) if nearest > date
        end
        nearest
      else
        l = values.reverse.detect { |v| v < date.mday }
        if l.blank?
          1.month.ago(date).change(day: values.last)
        else
          date.change(day: l.to_i)
        end
      end
      result.change(hour: 23, min: 59, sec: 59)
    end

    def next(date)
      result = if !is_specified
        date
      elsif is_last
        last = date.end_of_month
        last = nearest_week_day(last) if is_weekday
        if last < date
          last = 1.month.since(date)
          last = nearest_week_day(last.change(day: last.end_of_month))
        end
        last
      elsif is_weekday
        if values.empty?
          nearest = nearest_week_day(date)
          nearest = nearest_week_day(1.month.since(date).change(day: 1)) if nearest < date
        else
          nearest = nearest_week_day(date.change(day: values.first))
          nearest = nearest_week_day(1.month.since(date).change(day: values.first)) if nearest < date
        end
        nearest
      else
        n = values.detect { |v| v > date.mday }
        if n.blank?
          date.months_since(1).change(day: values.first)
        else
          date.change(day: n.to_i)
        end
      end
      result.change(hour: 0)
    end

    def include?(date)
      return true unless is_specified
      last = date.clone
      if is_last
        last = last.end_of_month.to_date
        last = nearest_week_day(last) if is_weekday
        last == date.to_date
      elsif is_weekday
        if values.empty?
          (1..5).include?(date.wday)
        else
          nearest_week_day(date.change(day: values.first)) == date
        end
      else
        super(date)
      end
    end

    def nearest_week_day(date)
      if (1..5).include?(date.wday)
        date
      elsif date.wday == 6
        (date.beginning_of_month.to_date == date.to_date) ? 2.days.since(date) : 1.day.ago(date)
      elsif date.wday == 0
        (date.end_of_month.to_date == date.to_date) ? date = 2.days.ago(date) : 1.day.since(date)
      end
    end

    def to_s
      "[e:#{expression}, v:#{values.inspect}, is:#{is_specified}, il:#{is_last}, iw:#{is_weekday}]\n"
    end
  end

  class MonthsCronValue < CronValue
    MONTHS = Date::ABBR_MONTHNAMES[1..-1].map { |a| a.upcase }

    def initialize(exp)
      super(:month, 1, 12, exp)
    end

    def parse(exp)
      if exp =~ /[A-Z]+/
        MONTHS.each_with_index { |mon, index| exp = exp.gsub(mon, (index + 1).to_s) }
      end
      super(exp)
    end

    def last(date)
      last_month = values.reverse.detect { |v| v < date.month }
      result = if last_month.nil?
        date.change(year: date.year - 1, month: values.last)
      else
        date.change(month: last_month)
      end
      result.change(day: result.end_of_month, hour: 23, min: 59, sec: 59)
    end

    def next(date)
      next_month = values.detect { |v| v > date.month }
      if next_month.nil?
        date.change(year: date.year + 1, month: values.first, day: 1, hour: 0)
      else
        date.change(month: next_month, day: 1, hour: 0)
      end
    end
  end

  class DaysOfWeekCronValue < CronValue
    DAYS = Date::ABBR_DAYNAMES.map { |a| a.upcase }
    attr_accessor :is_specified, :is_last, :nth_day

    def initialize(exp)
      self.is_last = false
      super(:wday, 1, 7, exp)
    end

    def parse(exp)
      if self.is_specified = !(expression =~ /\?/)
        if exp =~ /[A-Z]+/
          DAYS.each_with_index { |day, index| exp = exp.gsub(day, (index + 1).to_s) }
        end
        case exp
          when /^L$/ then values << max
          when /^(\d+)L$/ then self.is_last = true; values << $1.to_i
          when /^(\d+)#(\d+)/ then self.values << $1.to_i; self.nth_day = $2.to_i
          else super(exp)
        end
      end
    end

    def include?(date)
      return true unless is_specified
      if is_last
        last = last_wday(date, values.first).to_date
        date.to_date == last
      elsif nth_day
        date.to_date == nth_wday(nth_day, values.first, date.month, date.year).to_date
      else
        values.include?(date.wday + 1)
      end
    end

    def last(date)
      last_dow = if !is_specified
        date
      elsif is_last
        last = last_wday(date, values.first)
        if last.to_date > date.to_date
          last = last_wday(1.month.ago(date).change(day: 1), values.first)
        end
        last
      elsif nth_day
        nth = nth_wday(nth_day, values.first, date.month, date.year)
        if nth.to_date > date.to_date
          nth = 1.month.ago(date).change(day: 1)
          nth = nth_wday(nth_day, values.first, nth.month, nth.year)
        end
        nth
      else
        n = values.detect { |v| v > date.wday }
        n = values[0] if n.blank?
        base = (n < (date.wday + 1)) ? 7 : 0
        days_forward = n + (base - (date.wday + 1))
        days_forward.days.since(date)
      end
      last_dow.change(hour: 23, min: 59, sec: 59)
    end

    def next(date)
      next_dow = if !is_specified
        date
      elsif is_last
        last = last_wday(date, values.first)
        if last.to_date <= date.to_date
          last = last_wday(1.month.since(date).change(day: 1), values.first)
        end
        last
      elsif nth_day
        nth = nth_wday(nth_day, values.first, date.month, date.year)
        if nth.to_date <= date.to_date
          date = 1.month.since(date)
          nth = nth_wday(nth_day, values.first, date.month, date.year)
        end
        nth
      else
        n = values.detect { |v| v > date.wday }
        n = values[0] if n.blank?
        base = (n < (date.wday + 1)) ? 7 : 0
        days_forward = n + (base - (date.wday + 1))
        days_forward.days.since(date)
      end
      next_dow.change(hour: 0)
    end

    def last_wday(date, aWday)
      wday = aWday - 1
      eom = date.end_of_month
      if eom.wday == wday
        eom
      elsif eom.wday > wday
        (eom.wday - wday).days.ago(eom)
      else
        ((7 - wday) + eom.wday).days.ago(eom)
      end
    end

    # compliments of the ruby way
    def nth_wday(n, aWday, month, year)
      wday = aWday - 1
      if (!n.between? 1,5) or
         (!wday.between? 0,6) or
         (!month.between? 1,12)
        raise ArgumentError
      end
      t = Time.zone.local year, month, 1
      first = t.wday
      if first == wday
        fwd = 1
      elsif first < wday
        fwd = wday - first + 1
      elsif first > wday
        fwd = (wday+7) - first + 1
      end
      target = fwd + (n-1)*7
      begin
        t2 = Time.zone.local year, month, target
      rescue ArgumentError
        return nil
      end
      if t2.mday == target
        t2
      else
        nil
      end
    end

    def to_s
      "[e:#{expression}, v:#{values.inspect}, is:#{is_specified}, il:#{is_last}, nd:#{nth_day}]\n"
    end
  end

  class YearsCronValue < CronValue
    def initialize(exp)
      super(:year, 1970, (Date.today.year + 100), exp)
    end

    def next(date)
      next_year = values.detect { |v| v > date.year }
      if next_year.nil?
        return nil
      else
        date.change(year: next_year, month: 1, day: 1, hour: 0)
      end
    end

    def last(date)
      last_year = values.reverse.detect { |v| v < date.year }
      if last_year.nil?
        return nil
      else
        date.change(year: last_year, month: 12, day: 31, hour: 23, min: 59, sec: 59)
      end
    end
  end
end


#--
# Copyright (c) 2009-2010, John Mettraux, jmettraux@gmail.com
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Made in Japan.
#++

require 'tokyotyrant'

require 'rufus/edo/error'
require 'rufus/edo/tabcore'
require 'rufus/tokyo/ttcommons'

module Rufus::Edo

  #
  # A Tokyo Cabinet table, but remote...
  #
  #   require 'rufus/edo/ntyrant'
  #   t = Rufus::Edo::NetTyrant.new('127.0.0.1', 44001)
  #   t['toto'] = { 'name' => 'toto the first', 'age' => '34' }
  #   t['toto']
  #     # => { 'name' => 'toto the first', 'age' => '34' }
  #
  # NOTE : The advantage of this class is that it leverages the TokyoTyrant.rb
  # provided by Hirabayashi-san. It's pure Ruby, it's slow but works everywhere
  # without the need for Tokyo Cabinet and Tyrant C libraries.
  #
  class NetTyrantTable

    include Rufus::Edo::TableCore
    include Rufus::Tokyo::TyrantCommons
    include Rufus::Tokyo::NoTransactions

    attr_reader :host, :port

    #
    # Connects to the Tyrant table listening at the given host and port.
    #
    # You start such a Tyrant with :
    #
    #   ttserver -port 44502 data.tct
    #
    # and then :
    #
    #   require 'rufus/edo/ntyrant'
    #   t = Rufus::Edo::NetTyrantTable.new('127.0.0.1', 44502)
    #   t['client0'] = { 'name' => 'Heike no Kyomori', 'country' => 'jp' }
    #   t.close
    #
    #
    # You can start a Tokyo Tyrant and make it listen to a unix socket (not TCP)
    # with :
    #
    #   ttserver -host /tmp/table_socket -port 0 data.tct
    #
    # then :
    #
    #   require 'rufus/edo/ntyrant'
    #   t = Rufus::Edo::NetTyrantTable.new('/tmp/table_socket')
    #   t['client0'] = { 'name' => 'Theodore Roosevelt', 'country' => 'usa' }
    #   t.close
    #
    def initialize (host, port=0)

      @host = host
      @port = port

      @db = TokyoTyrant::RDBTBL.new
      @db.open(host, port) || raise_error

      @default_proc = nil

      if self.stat['type'] != 'table'

        @db.close

        raise ArgumentError.new(
          "tyrant at #{host}:#{port} is not a table, " +
          "use Rufus::Edo::NetTyrant instead to access it.")
      end
    end

    # Gets multiple records in one sweep.
    #
    def lget (*keys)

      h = keys.flatten.inject({}) { |hh, k| hh[k] = nil; hh }
      r = @db.mget(h)

      raise 'lget failure' if r == -1

      h
    end

    alias :mget :lget

    # Calls a lua embedded function
    # (http://tokyocabinet.sourceforge.net/tyrantdoc/#luaext)
    #
    # Options are :global_locking and :record_locking
    #
    # Returns the return value of the called function.
    #
    # Nil is returned in case of failure.
    #
    def ext (func_name, key='', value='', opts={})

      @db.ext(func_name.to_s, key.to_s, value.to_s, compute_ext_opts(opts))
    end

    protected

    def table_query_class #:nodoc#

      TokyoTyrant::RDBQRY
    end

    def do_stat #:nodoc#

      @db.stat
    end
  end
end


# -*- encoding: utf-8 -*-

# Copyright 2011-2015 Glenn Rempe

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module SecretSharing
  # Module for common methods shared across Container, Secret, or Share
  module Shamir
    # Create a random number of a specified Byte length
    # returns Bignum
    def get_random_number(bytes)
      RbNaCl::Util.bin2hex(RbNaCl::Random.random_bytes(bytes).to_s).to_i(16)
    end

    # Creates a random number of a exact bitlength
    # returns Bignum
    def get_random_number_with_bitlength(bits)
      byte_length = (bits / 8.0).ceil + 10
      random_num = get_random_number(byte_length)
      random_num_bin_str = random_num.to_s(2) # Get 1's and 0's

      # Slice off only the bits we require, convert Bits to Numeric (Bignum)
      random_num_bin_str.slice(0, bits).to_i(2)
    end

    # Supports #miller_rabin_prime?
    def mod_exp(n, e, mod)
      fail ArgumentError, 'negative exponent' if e < 0
      prod = 1
      base = n % mod

      until e.zero?
        prod = (prod * base) % mod if e.odd?
        e >>= 1
        base = (base * base) % mod
      end

      prod
    end

    # An implementation of the miller-rabin primality test.
    # See : http://primes.utm.edu/prove/merged.html
    # See : http://rosettacode.org/wiki/Miller-Rabin_primality_test#Ruby
    # See : https://crypto.stackexchange.com/questions/71/how-can-i-generate-large-prime-numbers-for-rsa
    # See : https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test
    #
    # Example : p primes = (3..1000).step(2).find_all {|i| miller_rabin_prime?(i,10)}
    def miller_rabin_prime?(n, g = 1000)
      return false if n == 1
      return true if n == 2

      d = n - 1
      s = 0

      while d.even?
        d /= 2
        s += 1
      end

      g.times do
        a = 2 + rand(n - 4)
        x = mod_exp(a, d, n) # x = (a**d) % n
        next if x == 1 || x == n - 1
        (1..s - 1).each do
          x = mod_exp(x, 2, n) # x = (x**2) % n
          return false if x == 1
          break if x == n - 1
        end
        return false if x != n - 1
      end

      true # probably
    end

    # Finds a random prime number of *at least* bitlength
    # Validate primeness using the miller-rabin primality test.
    # Increment through odd numbers to test candidates until a good prime is found.
    def get_prime_number(bitlength)
      prime_cand = get_random_number_with_bitlength(bitlength + 1)
      prime_cand += 1 if prime_cand.even?

      # loop, adding 2 to keep it odd, until prime_cand is prime.
      (prime_cand += 2) until miller_rabin_prime?(prime_cand)

      prime_cand
    end

    # FIXME : Needs focused tests

    # Evaluate the polynomial at x.
    def evaluate_polynomial_at(x, coefficients, prime)
      result = 0

      coefficients.each_with_index do |c, i|
        result += c * (x**i)
        result %= prime
      end

      result
    end

    def extended_gcd(a, b)
      last_remainder = a.abs
      remainder      = b.abs
      x              = 0
      last_x         = 1
      y              = 1
      last_y         = 0

      until remainder.zero?
        # rubocop:disable Style/ParallelAssignment
        last_remainder, (quotient, remainder) = remainder, last_remainder.divmod(remainder)
        x, last_x = last_x - quotient * x, x
        y, last_y = last_y - quotient * y, y
        # rubocop:enable Style/ParallelAssignment
      end

      [last_remainder, last_x * (a < 0 ? -1 : 1)]
    end

    # Calculate the Modular Inverse.
    # See : http://rosettacode.org/wiki/Modular_inverse#Ruby
    # Based on pseudo code from http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Iterative_method_2
    def invmod(e, et)
      g, x = extended_gcd(e, et)
      fail ArgumentError, 'Teh maths are broken!' if g != 1
      x % et
    end

    # FIXME : Needs focused tests

    # Part of the Lagrange interpolation.
    # This is l_j(0), i.e.
    # \prod_{x_j \neq x_i} \frac{-x_i}{x_j - x_i}
    # for more information compare Wikipedia:
    # http://en.wikipedia.org/wiki/Lagrange_form
    def lagrange(x, shares)
      prime        = shares.first.prime
      other_shares = shares.reject { |s| s.x == x }

      results = other_shares.map do |s|
        minus_xi = -s.x
        # was OpenSSL::BN#mod_inverse
        one_over_xj_minus_xi = invmod(x - s.x, prime)
        # was OpenSSL::BN#mod_mul : (self * other) % m
        (minus_xi * one_over_xj_minus_xi) % prime
      end

      results.reduce { |a, e| (a * e) % prime }
    end
  end # module Shamir
end # module SecretSharing


module Rufus::Tokyo
  #
  # A mixin for classes with new() that need a matching IO-like open().
  #
  module Openable
    #
    # Same args as new(), but can take a block form that will
    # close the db when done. Similar to File.open(). (via Zev and JEG2)
    #
    def open(*args)
      db = new(*args)
      if block_given?
        begin
          yield db
        ensure
          db.close
        end
      else
        db
      end
    end
  end
end