text:
require 'rest-client'
require 'zlib'
require 'set'

require 'discordrb/events/message'
require 'discordrb/events/typing'
require 'discordrb/events/lifetime'
require 'discordrb/events/presence'
require 'discordrb/events/voice_state_update'
require 'discordrb/events/channels'
require 'discordrb/events/members'
require 'discordrb/events/roles'
require 'discordrb/events/guilds'
require 'discordrb/events/await'
require 'discordrb/events/bans'

require 'discordrb/api'
require 'discordrb/errors'
require 'discordrb/data'
require 'discordrb/await'
require 'discordrb/container'
require 'discordrb/websocket'
require 'discordrb/cache'
require 'discordrb/gateway'

require 'discordrb/voice/voice_bot'

module Discordrb
  # Represents a Discord bot, including servers, users, etc.
  class Bot
    # The list of currently running threads used to parse and call events.
    # The threads will have a local variable `:discordrb_name` in the format of `et-1234`, where
    # "et" stands for "event thread" and the number is a continually incrementing number representing
    # how many events were executed before.
    # @return [Array<Thread>] The threads.
    attr_reader :event_threads

    # Whether or not the bot should parse its own messages. Off by default.
    attr_accessor :should_parse_self

    # The bot's name which discordrb sends to Discord when making any request, so Discord can identify bots with the
    # same codebase. Not required but I recommend setting it anyway.
    attr_accessor :name

    # @return [Array(Integer, Integer)] the current shard key
    attr_reader :shard_key

    # @return [Hash<Symbol => Await>] the list of registered {Await}s.
    attr_reader :awaits

    # The gateway connection is an internal detail that is useless to most people. It is however essential while
    # debugging or developing discordrb itself, or while writing very custom bots.
    # @return [Gateway] the underlying {Gateway} object.
    attr_reader :gateway

    include EventContainer
    include Cache

    # Makes a new bot with the given authentication data. It will be ready to be added event handlers to and can
    # eventually be run with {#run}.
    #
    # As support for logging in using username and password has been removed in version 3.0.0, only a token login is
    # possible. Be sure to specify the `type` parameter as `:user` if you're logging in as a user.
    #
    # Simply creating a bot won't be enough to start sending messages etc. with, only a limited set of methods can
    # be used after logging in. If you want to do something when the bot has connected successfully, either do it in the
    # {#ready} event, or use the {#run} method with the :async parameter and do the processing after that.
    # @param log_mode [Symbol] The mode this bot should use for logging. See {Logger#mode=} for a list of modes.
    # @param token [String] The token that should be used to log in. If your bot is a bot account, you have to specify
    #   this. If you're logging in as a user, make sure to also set the account type to :user so discordrb doesn't think
    #   you're trying to log in as a bot.
    # @param application_id [Integer] If you're logging in as a bot, the bot's application ID.
    # @param type [Symbol] This parameter lets you manually overwrite the account type. This needs to be set when
    #   logging in as a user, otherwise discordrb will treat you as a bot account. Valid values are `:user` and `:bot`.
    # @param name [String] Your bot's name. This will be sent to Discord with any API requests, who will use this to
    #   trace the source of excessive API requests; it's recommended to set this to something if you make bots that many
    #   people will host on their servers separately.
    # @param fancy_log [true, false] Whether the output log should be made extra fancy using ANSI escape codes. (Your
    #   terminal may not support this.)
    # @param suppress_ready [true, false] Whether the READY packet should be exempt from being printed to console.
    #   Useful for very large bots running in debug or verbose log_mode.
    # @param parse_self [true, false] Whether the bot should react on its own messages. It's best to turn this off
    #   unless you really need this so you don't inadvertently create infinite loops.
    # @param shard_id [Integer] The number of the shard this bot should handle. See
    #   https://github.com/hammerandchisel/discord-api-docs/issues/17 for how to do sharding.
    # @param num_shards [Integer] The total number of shards that should be running. See
    #   https://github.com/hammerandchisel/discord-api-docs/issues/17 for how to do sharding.
    def initialize(
        log_mode: :normal,
        token: nil, application_id: nil,
        type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false,
        shard_id: nil, num_shards: nil
    )

      LOGGER.mode = if log_mode.is_a? TrueClass # Specifically check for `true` because people might not have updated yet
                      :debug
                    else
                      log_mode
                    end

      @should_parse_self = parse_self

      @application_id = application_id

      @type = type || :bot
      @name = name

      @shard_key = num_shards ? [shard_id, num_shards] : nil

      LOGGER.fancy = fancy_log
      @prevent_ready = suppress_ready

      @token = process_token(@type, token)
      @gateway = Gateway.new(self, @token)

      init_cache

      @voices = {}
      @should_connect_to_voice = {}

      @ignored_ids = Set.new

      @event_threads = []
      @current_thread = 0

      @idletime = nil
    end

    # The list of users the bot shares a server with.
    # @return [Hash<Integer => User>] The users by ID.
    def users
      gateway_check
      @users
    end

    # The list of servers the bot is currently in.
    # @return [Hash<Integer => Server>] The servers by ID.
    def servers
      gateway_check
      @servers
    end

    # The bot's user profile. This special user object can be used
    # to edit user data like the current username (see {Profile#username=}).
    # @return [Profile] The bot's profile that can be used to edit data.
    def profile
      gateway_check
      @profile
    end

    alias_method :bot_user, :profile

    # The bot's OAuth application.
    # @return [Application, nil] The bot's application info. Returns `nil` if bot is not a bot account.
    def bot_application
      gateway_check
      return nil unless @type == :bot
      response = API.oauth_application(token)
      Application.new(JSON.parse(response), self)
    end

    alias_method :bot_app, :bot_application

    # The Discord API token received when logging in. Useful to explicitly call
    # {API} methods.
    # @return [String] The API token.
    def token
      API.bot_name = @name
      @token
    end

    # @return the raw token, without any prefix
    # @see #token
    def raw_token
      @token.split(' ').last
    end

    # Runs the bot, which logs into Discord and connects the WebSocket. This prevents all further execution unless it is executed with `async` = `:async`.
    # @param async [Symbol] If it is `:async`, then the bot will allow further execution.
    #   It doesn't necessarily have to be that, anything truthy will work,
    #   however it is recommended to use `:async` for code readability reasons.
    #   If the bot is run in async mode, make sure to eventually run {#sync} so
    #   the script doesn't stop prematurely.
    def run(async = false)
      @gateway.run_async
      return if async

      debug('Oh wait! Not exiting yet as run was run synchronously.')
      @gateway.sync
    end

    # Blocks execution until the websocket stops, which should only happen manually triggered
    # or due to an error. This is necessary to have a continuously running bot.
    def sync
      @gateway.sync
    end

    # @return [true, false] whether or not the bot is currently connected to Discord.
    def connected?
      @gateway.open?
    end

    # Makes the bot join an invite to a server.
    # @param invite [String, Invite] The invite to join. For possible formats see {#resolve_invite_code}.
    def join(invite)
      resolved = invite(invite).code
      API.join_server(token, resolved)
    end

    # Creates an OAuth invite URL that can be used to invite this bot to a particular server.
    # Requires the application ID to have been set during initialization.
    # @param server [Server, nil] The server the bot should be invited to, or nil if a general invite should be created.
    # @return [String] the OAuth invite URL.
    def invite_url(server = nil)
      raise 'No application ID has been set during initialization! Add one as the `application_id` named parameter while creating your bot.' unless @application_id

      guild_id_str = server ? "&guild_id=#{server.id}" : ''
      "https://discordapp.com/oauth2/authorize?&client_id=#{@application_id}#{guild_id_str}&scope=bot"
    end

    # @return [Hash<Integer => VoiceBot>] the voice connections this bot currently has, by the server ID to which they are connected.
    attr_reader :voices

    # Gets the voice bot for a particular server or channel. You can connect to a new channel using the {#voice_connect}
    # method.
    # @param thing [Channel, Server, Integer] the server or channel you want to get the voice bot for, or its ID.
    # @return [VoiceBot, nil] the VoiceBot for the thing you specified, or nil if there is no connection yet
    def voice(thing)
      id = thing.resolve_id
      return @voices[id] if @voices[id]

      channel = channel(id)
      return nil unless channel

      server_id = channel.server.id
      return @voices[server_id] if @voices[server_id]

      nil
    end

    # Connects to a voice channel, initializes network connections and returns the {Voice::VoiceBot} over which audio
    # data can then be sent. After connecting, the bot can also be accessed using {#voice}. If the bot is already
    # connected to voice, the existing connection will be terminated - you don't have to call
    # {Discordrb::Voice::VoiceBot#destroy} before calling this method.
    # @param chan [Channel] The voice channel to connect to.
    # @param encrypted [true, false] Whether voice communication should be encrypted using RbNaCl's SecretBox
    #   (uses an XSalsa20 stream cipher for encryption and Poly1305 for authentication)
    # @return [Voice::VoiceBot] the initialized bot over which audio data can then be sent.
    def voice_connect(chan, encrypted = true)
      chan = channel(chan.resolve_id)
      server_id = chan.server.id
      @should_encrypt_voice = encrypted

      if @voices[chan.id]
        debug('Voice bot exists already! Destroying it')
        @voices[chan.id].destroy
        @voices.delete(chan.id)
      end

      debug("Got voice channel: #{chan}")

      @should_connect_to_voice[server_id] = chan
      @gateway.send_voice_state_update(server_id.to_s, chan.id.to_s, false, false)

      debug('Voice channel init packet sent! Now waiting.')

      sleep(0.05) until @voices[server_id]
      debug('Voice connect succeeded!')
      @voices[server_id]
    end

    # Disconnects the client from a specific voice connection given the server ID. Usually it's more convenient to use
    # {Discordrb::Voice::VoiceBot#destroy} rather than this.
    # @param server_id [Integer] The ID of the server the voice connection is on.
    # @param destroy_vws [true, false] Whether or not the VWS should also be destroyed. If you're calling this method
    #   directly, you should leave it as true.
    def voice_destroy(server_id, destroy_vws = true)
      @gateway.send_voice_state_update(server_id.to_s, nil, false, false)
      @voices[server_id].destroy if @voices[server_id] && destroy_vws
      @voices.delete(server_id)
    end

    # Revokes an invite to a server. Will fail unless you have the *Manage Server* permission.
    # It is recommended that you use {Invite#delete} instead.
    # @param code [String, Invite] The invite to revoke. For possible formats see {#resolve_invite_code}.
    def delete_invite(code)
      invite = resolve_invite_code(code)
      API.delete_invite(token, invite)
    end

    # Sends a text message to a channel given its ID and the message's content.
    # @param channel_id [Integer] The ID that identifies the channel to send something to.
    # @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
    # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
    # @param server_id [Integer] The ID that identifies the server to send something to.
    # @return [Message] The message that was sent.
    def send_message(channel_id, content, tts = false, server_id = nil)
      channel_id = channel_id.resolve_id
      debug("Sending message to #{channel_id} with content '#{content}'")

      response = API.send_message(token, channel_id, content, [], tts, server_id)
      Message.new(JSON.parse(response), self)
    end

    # Sends a text message to a channel given its ID and the message's content,
    # then deletes it after the specified timeout in seconds.
    # @param channel_id [Integer] The ID that identifies the channel to send something to.
    # @param content [String] The text that should be sent as a message. It is limited to 2000 characters (Discord imposed).
    # @param timeout [Float] The amount of time in seconds after which the message sent will be deleted.
    # @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
    # @param server_id [Integer] The ID that identifies the server to send something to.
    def send_temporary_message(channel_id, content, timeout, tts = false, server_id = nil)
      Thread.new do
        message = send_message(channel_id, content, tts, server_id)

        sleep(timeout)

        message.delete
      end

      nil
    end

    # Sends a file to a channel. If it is an image, it will automatically be embedded.
    # @note This executes in a blocking way, so if you're sending long files, be wary of delays.
    # @param channel_id [Integer] The ID that identifies the channel to send something to.
    # @param file [File] The file that should be sent.
    # @param caption [string] The caption for the file.
    # @param tts [true, false] Whether or not this file's caption should be sent using Discord text-to-speech.
    def send_file(channel_id, file, caption: nil, tts: false)
      response = API.send_file(token, channel_id, file, caption: caption, tts: tts)
      Message.new(JSON.parse(response), self)
    end

    # Creates a server on Discord with a specified name and a region.
    # @note Discord's API doesn't directly return the server when creating it, so this method
    #   waits until the data has been received via the websocket. This may make the execution take a while.
    # @param name [String] The name the new server should have. Doesn't have to be alphanumeric.
    # @param region [Symbol] The region where the server should be created. Possible regions are:
    #
    #   * `:london`
    #   * `:amsterdam`
    #   * `:frankfurt`
    #   * `:us-east`
    #   * `:us-west`
    #   * `:us-south`
    #   * `:us-central`
    #   * `:singapore`
    #   * `:sydney`
    # @return [Server] The server that was created.
    def create_server(name, region = :london)
      response = API.create_server(token, name, region)
      id = JSON.parse(response)['id'].to_i
      sleep 0.1 until @servers[id]
      server = @servers[id]
      debug "Successfully created server #{server.id} with name #{server.name}"
      server
    end

    # Creates a new application to do OAuth authorization with. This allows you to use OAuth to authorize users using
    # Discord. For information how to use this, see the docs: https://discordapp.com/developers/docs/topics/oauth2
    # @param name [String] What your application should be called.
    # @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing.
    # @return [Array(String, String)] your applications' client ID and client secret to be used in OAuth authorization.
    def create_oauth_application(name, redirect_uris)
      response = JSON.parse(API.create_oauth_application(@token, name, redirect_uris))
      [response['id'], response['secret']]
    end

    # Changes information about your OAuth application
    # @param name [String] What your application should be called.
    # @param redirect_uris [Array<String>] URIs that Discord should redirect your users to after authorizing.
    # @param description [String] A string that describes what your application does.
    # @param icon [String, nil] A data URI for your icon image (for example a base 64 encoded image), or nil if no icon
    #   should be set or changed.
    def update_oauth_application(name, redirect_uris, description = '', icon = nil)
      API.update_oauth_application(@token, name, redirect_uris, description, icon)
    end

    # Gets the user from a mention of the user.
    # @param mention [String] The mention, which should look like <@12314873129>.
    # @return [User] The user identified by the mention, or `nil` if none exists.
    def parse_mention(mention)
      # Mention format: <@id>
      return nil unless /<@!?(?<id>\d+)>?/ =~ mention
      user(id.to_i)
    end

    # Updates presence status.
    # @param idletime [Integer, nil] The floating point of a Time object that shows the last time the bot was on.
    # @param game [String, nil] The name of the game to be played/stream name on the stream.
    # @param url [String, nil] The Twitch URL to display as a stream. nil for no stream.
    def update_status(idletime, game, url)
      gateway_check

      @game = game
      @idletime = idletime
      @streamurl = url
      type = url ? 1 : nil

      game_obj = game || url ? { name: game, url: url, type: type } : nil
      @gateway.send_status_update(idletime, game_obj)
    end

    # Sets the currently playing game to the specified game.
    # @param name [String] The name of the game to be played.
    # @return [String] The game that is being played now.
    def game=(name)
      gateway_check
      update_status(@idletime, name, nil)
      name
    end

    # Sets the currently online stream to the specified name and Twitch URL.
    # @param name [String] The name of the stream to display.
    # @param url [String] The url of the current Twitch stream.
    # @return [String] The stream name that is being displayed now.
    def stream(name, url)
      gateway_check
      update_status(@idletime, name, url)
      name
    end

    # Sets status to online.
    def online
      gateway_check
      update_status(nil, @game, @streamurl)
    end

    alias_method :on, :online

    # Sets status to idle.
    def idle
      gateway_check
      update_status((Time.now.to_f * 1000), @game, nil)
    end

    alias_method :away, :idle

    # Sets debug mode. If debug mode is on, many things will be outputted to STDOUT.
    def debug=(new_debug)
      LOGGER.debug = new_debug
    end

    # Sets the logging mode
    # @see Logger#mode=
    def mode=(new_mode)
      LOGGER.mode = new_mode
    end

    # Prevents the READY packet from being printed regardless of debug mode.
    def suppress_ready_debug
      @prevent_ready = true
    end

    # Add an await the bot should listen to. For information on awaits, see {Await}.
    # @param key [Symbol] The key that uniquely identifies the await for {AwaitEvent}s to listen to (see {#await}).
    # @param type [Class] The event class that should be listened for.
    # @param attributes [Hash] The attributes the event should check for. The block will only be executed if all attributes match.
    # @yield Is executed when the await is triggered.
    # @yieldparam event [Event] The event object that was triggered.
    # @return [Await] The await that was created.
    def add_await(key, type, attributes = {}, &block)
      raise "You can't await an AwaitEvent!" if type == Discordrb::Events::AwaitEvent
      await = Await.new(self, key, type, attributes, block)
      @awaits ||= {}
      @awaits[key] = await
    end

    # Add a user to the list of ignored users. Those users will be ignored in message events at event processing level.
    # @note Ignoring a user only prevents any message events (including mentions, commands etc.) from them! Typing and
    #   presence and any other events will still be received.
    # @param user [User, Integer, #resolve_id] The user, or its ID, to be ignored.
    def ignore_user(user)
      @ignored_ids << user.resolve_id
    end

    # Remove a user from the ignore list.
    # @param user [User, Integer, #resolve_id] The user, or its ID, to be unignored.
    def unignore_user(user)
      @ignored_ids.delete(user.resolve_id)
    end

    # Checks whether a user is being ignored.
    # @param user [User, Integer, #resolve_id] The user, or its ID, to check.
    # @return [true, false] whether or not the user is ignored.
    def ignored?(user)
      @ignored_ids.include?(user.resolve_id)
    end

    # @see Logger#debug
    def debug(message)
      LOGGER.debug(message)
    end

    # @see Logger#log_exception
    def log_exception(e)
      LOGGER.log_exception(e)
    end

    # Dispatches an event to this bot. Called by the gateway connection handler used internally.
    def dispatch(type, data)
      handle_dispatch(type, data)
    end

    private

    # Throws a useful exception if there's currently no gateway connection
    def gateway_check
      return if connected?

      raise "A gateway connection is necessary to call this method! You'll have to do it inside any event (e.g. `ready`) or after `bot.run :async`."
    end

    ### ##    ## ######## ######## ########  ##    ##    ###    ##        ######
    ##  ###   ##    ##    ##       ##     ## ###   ##   ## ##   ##       ##    ##
    ##  ####  ##    ##    ##       ##     ## ####  ##  ##   ##  ##       ##
    ##  ## ## ##    ##    ######   ########  ## ## ## ##     ## ##        ######
    ##  ##  ####    ##    ##       ##   ##   ##  #### ######### ##             ##
    ##  ##   ###    ##    ##       ##    ##  ##   ### ##     ## ##       ##    ##
    ### ##    ##    ##    ######## ##     ## ##    ## ##     ## ########  ######

    # Internal handler for PRESENCE_UPDATE
    def update_presence(data)
      # Friends list presences have no guild ID so ignore these to not cause an error
      return unless data['guild_id']

      user_id = data['user']['id'].to_i
      server_id = data['guild_id'].to_i
      server = server(server_id)
      return unless server

      member_is_new = false

      if server.member_cached?(user_id)
        member = server.member(user_id)
      else
        # If the member is not cached yet, it means that it just came online from not being cached at all
        # due to large_threshold. Fortunately, Discord sends the entire member object in this case, and
        # not just a part of it - we can just cache this member directly
        member = Member.new(data, server, self)
        debug("Implicitly adding presence-obtained member #{user_id} to #{server_id} cache")

        member_is_new = true
      end

      username = data['user']['username']
      if username && !member_is_new # Don't set the username for newly-cached members
        debug "Implicitly updating presence-obtained information for member #{user_id}"
        member.update_username(username)
      end

      member.status = data['status'].to_sym
      member.game = data['game'] ? data['game']['name'] : nil

      server.cache_member(member)
    end

    # Internal handler for VOICE_STATUS_UPDATE
    def update_voice_state(data)
      server_id = data['guild_id'].to_i
      server = server(server_id)
      return unless server

      server.update_voice_state(data)

      @session_id = data['session_id']
    end

    # Internal handler for VOICE_SERVER_UPDATE
    def update_voice_server(data)
      server_id = data['guild_id'].to_i
      channel = @should_connect_to_voice[server_id]

      debug("Voice server update received! chan: #{channel.inspect}")
      return unless channel
      @should_connect_to_voice.delete(server_id)
      debug('Updating voice server!')

      token = data['token']
      endpoint = data['endpoint']

      unless endpoint
        debug('VOICE_SERVER_UPDATE sent with nil endpoint! Ignoring')
        return
      end

      debug('Got data, now creating the bot.')
      @voices[server_id] = Discordrb::Voice::VoiceBot.new(channel, self, token, @session_id, endpoint, @should_encrypt_voice)
    end

    # Internal handler for CHANNEL_CREATE
    def create_channel(data)
      channel = Channel.new(data, self)
      server = channel.server

      # Handle normal and private channels separately
      if server
        server.channels << channel
        @channels[channel.id] = channel
      else
        @private_channels[channel.id] = channel
      end
    end

    # Internal handler for CHANNEL_UPDATE
    def update_channel(data)
      channel = Channel.new(data, self)
      old_channel = @channels[channel.id]
      return unless old_channel
      old_channel.update_from(channel)
    end

    # Internal handler for CHANNEL_DELETE
    def delete_channel(data)
      channel = Channel.new(data, self)
      server = channel.server

      # Handle normal and private channels separately
      if server
        @channels.delete(channel.id)
        server.channels.reject! { |c| c.id == channel.id }
      else
        @private_channels.delete(channel.id)
      end
    end

    # Internal handler for GUILD_MEMBER_ADD
    def add_guild_member(data)
      server_id = data['guild_id'].to_i
      server = self.server(server_id)

      member = Member.new(data, server, self)
      server.add_member(member)
    end

    # Internal handler for GUILD_MEMBER_UPDATE
    def update_guild_member(data)
      server_id = data['guild_id'].to_i
      server = self.server(server_id)

      member = server.member(data['user']['id'].to_i)
      member.update_roles(data['roles'])
      member.update_nick(data['nick'])
    end

    # Internal handler for GUILD_MEMBER_DELETE
    def delete_guild_member(data)
      server_id = data['guild_id'].to_i
      server = self.server(server_id)

      user_id = data['user']['id'].to_i
      server.delete_member(user_id)
    rescue Discordrb::Errors::NoPermission
      Discordrb::LOGGER.warn("delete_guild_member attempted to access a server for which the bot doesn't have permission! Not sure what happened here, ignoring")
    end

    # Internal handler for GUILD_CREATE
    def create_guild(data)
      ensure_server(data)
    end

    # Internal handler for GUILD_UPDATE
    def update_guild(data)
      @servers[data['id'].to_i].update_data(data)
    end

    # Internal handler for GUILD_DELETE
    def delete_guild(data)
      id = data['id'].to_i
      @servers.delete(id)
    end

    # Internal handler for GUILD_ROLE_UPDATE
    def update_guild_role(data)
      role_data = data['role']
      server_id = data['guild_id'].to_i
      server = @servers[server_id]
      new_role = Role.new(role_data, self, server)
      role_id = role_data['id'].to_i
      old_role = server.roles.find { |r| r.id == role_id }
      old_role.update_from(new_role)
    end

    # Internal handler for GUILD_ROLE_CREATE
    def create_guild_role(data)
      role_data = data['role']
      server_id = data['guild_id'].to_i
      server = @servers[server_id]
      new_role = Role.new(role_data, self, server)
      server.add_role(new_role)
    end

    # Internal handler for GUILD_ROLE_DELETE
    def delete_guild_role(data)
      role_id = data['role_id'].to_i
      server_id = data['guild_id'].to_i
      server = @servers[server_id]
      server.delete_role(role_id)
    end

    # Internal handler for MESSAGE_CREATE
    def create_message(data); end

    # Internal handler for TYPING_START
    def start_typing(data); end

    # Internal handler for MESSAGE_UPDATE
    def update_message(data); end

    # Internal handler for MESSAGE_DELETE
    def delete_message(data); end

    # Internal handler for GUILD_BAN_ADD
    def add_user_ban(data); end

    # Internal handler for GUILD_BAN_REMOVE
    def remove_user_ban(data); end

    ##        #######   ######   #### ##    ##
    ##       ##     ## ##    ##   ##  ###   ##
    ##       ##     ## ##         ##  ####  ##
    ##       ##     ## ##   ####  ##  ## ## ##
    ##       ##     ## ##    ##   ##  ##  ####
    ##       ##     ## ##    ##   ##  ##   ###
    ########  #######   ######   #### ##    ##

    def process_token(type, token)
      # Remove the "Bot " prefix if it exists
      token = token[4..-1] if token.start_with? 'Bot '

      token = 'Bot ' + token unless type == :user
      token
    end

    def handle_dispatch(type, data)
      # Check whether there are still unavailable servers and there have been more than 10 seconds since READY
      if @unavailable_servers && @unavailable_servers > 0 && (Time.now - @unavailable_timeout_time) > 10
        # The server streaming timed out!
        LOGGER.warn("Server streaming timed out with #{@unavailable_servers} servers remaining")
        LOGGER.warn("This means some servers are unavailable due to an outage. Notifying ready now, we'll have to live without these servers")

        # Unset the unavailable server count so this doesn't get triggered again
        @unavailable_servers = nil

        notify_ready
      end

      case type
      when :READY
        # As READY may be called multiple times over a single process lifetime, we here need to reset the cache entirely
        # to prevent possible inconsistencies, like objects referencing old versions of other objects which have been
        # replaced.
        init_cache

        @profile = Profile.new(data['user'], self)

        # Initialize servers
        @servers = {}

        # Count unavailable servers
        @unavailable_servers = 0

        data['guilds'].each do |element|
          # Check for true specifically because unavailable=false indicates that a previously unavailable server has
          # come online
          if element['unavailable'].is_a? TrueClass
            @unavailable_servers += 1

            # Ignore any unavailable servers
            next
          end

          ensure_server(element)
        end

        # Add private channels
        data['private_channels'].each do |element|
          channel = ensure_channel(element)
          @private_channels[channel.recipient.id] = channel
        end

        # Don't notify yet if there are unavailable servers because they need to get available before the bot truly has
        # all the data
        if @unavailable_servers.zero?
          # No unavailable servers - we're ready!
          notify_ready
        end

        @ready_time = Time.now
        @unavailable_timeout_time = Time.now
      when :GUILD_MEMBERS_CHUNK
        id = data['guild_id'].to_i
        server = server(id)
        server.process_chunk(data['members'])
      when :MESSAGE_CREATE
        if ignored?(data['author']['id'].to_i)
          debug("Ignored author with ID #{data['author']['id']}")
          return
        end

        create_message(data)

        message = Message.new(data, self)

        return if message.from_bot? && !should_parse_self

        event = MessageEvent.new(message, self)
        raise_event(event)

        if message.mentions.any? { |user| user.id == @profile.id }
          event = MentionEvent.new(message, self)
          raise_event(event)
        end

        if message.channel.private?
          event = PrivateMessageEvent.new(message, self)
          raise_event(event)
        end
      when :MESSAGE_UPDATE
        update_message(data)

        message = Message.new(data, self)
        return if message.from_bot? && !should_parse_self

        unless message.author
          LOGGER.debug("Edited a message with nil author! Content: #{message.content.inspect}, channel: #{message.channel.inspect}")
          return
        end

        event = MessageEditEvent.new(message, self)
        raise_event(event)
      when :MESSAGE_DELETE
        delete_message(data)

        event = MessageDeleteEvent.new(data, self)
        raise_event(event)
      when :MESSAGE_DELETE_BULK
        debug("MESSAGE_DELETE_BULK will raise #{data['ids'].length} events")

        data['ids'].each do |single_id|
          # Form a data hash for a single ID so the methods get what they want
          single_data = {
            'id' => single_id,
            'channel_id' => data['channel_id']
          }

          # Raise as normal
          delete_message(single_data)

          event = MessageDeleteEvent.new(single_data, self)
          raise_event(event)
        end
      when :TYPING_START
        start_typing(data)

        begin
          event = TypingEvent.new(data, self)
          raise_event(event)
        rescue Discordrb::Errors::NoPermission
          debug 'Typing started in channel the bot has no access to, ignoring'
        end
      when :PRESENCE_UPDATE
        # Ignore friends list presences
        return unless data['guild_id']

        now_playing = data['game']
        presence_user = @users[data['user']['id'].to_i]
        played_before = presence_user.nil? ? nil : presence_user.game
        update_presence(data)

        event = if now_playing != played_before
                  PlayingEvent.new(data, self)
                else
                  PresenceEvent.new(data, self)
                end

        raise_event(event)
      when :VOICE_STATE_UPDATE
        update_voice_state(data)

        event = VoiceStateUpdateEvent.new(data, self)
        raise_event(event)
      when :VOICE_SERVER_UPDATE
        update_voice_server(data)

        # no event as this is irrelevant to users
      when :CHANNEL_CREATE
        create_channel(data)

        event = ChannelCreateEvent.new(data, self)
        raise_event(event)
      when :CHANNEL_UPDATE
        update_channel(data)

        event = ChannelUpdateEvent.new(data, self)
        raise_event(event)
      when :CHANNEL_DELETE
        delete_channel(data)

        event = ChannelDeleteEvent.new(data, self)
        raise_event(event)
      when :GUILD_MEMBER_ADD
        add_guild_member(data)

        event = ServerMemberAddEvent.new(data, self)
        raise_event(event)
      when :GUILD_MEMBER_UPDATE
        update_guild_member(data)

        event = ServerMemberUpdateEvent.new(data, self)
        raise_event(event)
      when :GUILD_MEMBER_REMOVE
        delete_guild_member(data)

        event = ServerMemberDeleteEvent.new(data, self)
        raise_event(event)
      when :GUILD_BAN_ADD
        add_user_ban(data)

        event = UserBanEvent.new(data, self)
        raise_event(event)
      when :GUILD_BAN_REMOVE
        remove_user_ban(data)

        event = UserUnbanEvent.new(data, self)
        raise_event(event)
      when :GUILD_ROLE_UPDATE
        update_guild_role(data)

        event = ServerRoleUpdateEvent.new(data, self)
        raise_event(event)
      when :GUILD_ROLE_CREATE
        create_guild_role(data)

        event = ServerRoleCreateEvent.new(data, self)
        raise_event(event)
      when :GUILD_ROLE_DELETE
        delete_guild_role(data)

        event = ServerRoleDeleteEvent.new(data, self)
        raise_event(event)
      when :GUILD_CREATE
        create_guild(data)

        # Check for false specifically (no data means the server has never been unavailable)
        if data['unavailable'].is_a? FalseClass
          @unavailable_servers -= 1 if @unavailable_servers
          @unavailable_timeout_time = Time.now

          notify_ready if @unavailable_servers.zero?

          # Return here so the event doesn't get triggered
          return
        end

        event = ServerCreateEvent.new(data, self)
        raise_event(event)
      when :GUILD_UPDATE
        update_guild(data)

        event = ServerUpdateEvent.new(data, self)
        raise_event(event)
      when :GUILD_DELETE
        delete_guild(data)

        event = ServerDeleteEvent.new(data, self)
        raise_event(event)
      else
        # another event that we don't support yet
        debug "Event #{type} has been received but is unsupported, ignoring"
      end
    rescue Exception => e
      LOGGER.error('Gateway message error!')
      log_exception(e)
    end

    # Notifies everything there is to be notified that the connection is now ready
    def notify_ready
      # Make sure to raise the event
      raise_event(ReadyEvent.new(self))
      LOGGER.good 'Ready'

      @gateway.notify_ready
    end

    def raise_event(event)
      debug("Raised a #{event.class}")
      handle_awaits(event)

      @event_handlers ||= {}
      handlers = @event_handlers[event.class]
      (handlers || []).each do |handler|
        call_event(handler, event) if handler.matches?(event)
      end
    end

    def call_event(handler, event)
      t = Thread.new do
        @event_threads ||= []
        @current_thread ||= 0

        @event_threads << t
        Thread.current[:discordrb_name] = "et-#{@current_thread += 1}"
        begin
          handler.call(event)
          handler.after_call(event)
        rescue => e
          log_exception(e)
        ensure
          @event_threads.delete(t)
        end
      end
    end

    def handle_awaits(event)
      @awaits ||= {}
      @awaits.each do |_, await|
        key, should_delete = await.match(event)
        next unless key
        debug("should_delete: #{should_delete}")
        @awaits.delete(await.key) if should_delete

        await_event = Discordrb::Events::AwaitEvent.new(await, event, self)
        raise_event(await_event)
      end
    end
  end
end


text: TRUNCATE addons_pledges;

redpajama_set_name: RedPajamaGithub


text:
package com.amazonaws.services.robomaker.model;

import java.io.Serializable;
import javax.annotation.Generated;
import com.amazonaws.protocol.StructuredPojo;
import com.amazonaws.protocol.ProtocolMarshaller;

/**
 *

 * The number of worlds that will be created. You can configure the number of unique floorplans and the number of unique
 * interiors for each floor plan. For example, if you want 1 world with 20 unique interiors, you set
 * floorplanCount = 1 and interiorCountPerFloorplan = 20. This will result in 20 worlds (
 * floorplanCount * interiorCountPerFloorplan).
 *

 *

 * If you set floorplanCount = 4 and interiorCountPerFloorplan = 5, there will be 20 worlds
 * with 5 unique floor plans.
 *

 *
 * @see AWS API
 *      Documentation
 */
@Generated("com.amazonaws:aws-java-sdk-code-generator")
public class WorldCount implements Serializable, Cloneable, StructuredPojo {

    /**
     *

     * The number of unique floorplans.
     *

     */
    private Integer floorplanCount;
    /**
     *

     * The number of unique interiors per floorplan.
     *

     */
    private Integer interiorCountPerFloorplan;

    /**
     *

     * The number of unique floorplans.
     *

     *
     * @param floorplanCount
     *        The number of unique floorplans.
     */

    public void setFloorplanCount(Integer floorplanCount) {
        this.floorplanCount = floorplanCount;
    }

    /**
     *

     * The number of unique floorplans.
     *

     *
     * @return The number of unique floorplans.
     */

    public Integer getFloorplanCount() {
        return this.floorplanCount;
    }

    /**
     *

     * The number of unique floorplans.
     *

     *
     * @param floorplanCount
     *        The number of unique floorplans.
     * @return Returns a reference to this object so that method calls can be chained together.
     */

    public WorldCount withFloorplanCount(Integer floorplanCount) {
        setFloorplanCount(floorplanCount);
        return this;
    }

    /**
     *

     * The number of unique interiors per floorplan.
     *

     *
     * @param interiorCountPerFloorplan
     *        The number of unique interiors per floorplan.
     */

    public void setInteriorCountPerFloorplan(Integer interiorCountPerFloorplan) {
        this.interiorCountPerFloorplan = interiorCountPerFloorplan;
    }

    /**
     *

     * The number of unique interiors per floorplan.
     *

     *
     * @return The number of unique interiors per floorplan.
     */

    public Integer getInteriorCountPerFloorplan() {
        return this.interiorCountPerFloorplan;
    }

    /**
     *

     * The number of unique interiors per floorplan.
     *

     *
     * @param interiorCountPerFloorplan
     *        The number of unique interiors per floorplan.
     * @return Returns a reference to this object so that method calls can be chained together.
     */

    public WorldCount withInteriorCountPerFloorplan(Integer interiorCountPerFloorplan) {
        setInteriorCountPerFloorplan(interiorCountPerFloorplan);
        return this;
    }

    /**
     * Returns a string representation of this object. This is useful for testing and debugging. Sensitive data will be
     * redacted from this string using a placeholder value.
     *
     * @return A string representation of this object.
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        if (getFloorplanCount() != null)
            sb.append("FloorplanCount: ").append(getFloorplanCount()).append(",");
        if (getInteriorCountPerFloorplan() != null)
            sb.append("InteriorCountPerFloorplan: ").append(getInteriorCountPerFloorplan());
        sb.append("}");
        return sb.toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;

        if (obj instanceof WorldCount == false)
            return false;
        WorldCount other = (WorldCount) obj;
        if (other.getFloorplanCount() == null ^ this.getFloorplanCount() == null)
            return false;
        if (other.getFloorplanCount() != null && other.getFloorplanCount().equals(this.getFloorplanCount()) == false)
            return false;
        if (other.getInteriorCountPerFloorplan() == null ^ this.getInteriorCountPerFloorplan() == null)
            return false;
        if (other.getInteriorCountPerFloorplan() != null && other.getInteriorCountPerFloorplan().equals(this.getInteriorCountPerFloorplan()) == false)
            return false;
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int hashCode = 1;

        hashCode = prime * hashCode + ((getFloorplanCount() == null) ? 0 : getFloorplanCount().hashCode());
        hashCode = prime * hashCode + ((getInteriorCountPerFloorplan() == null) ? 0 : getInteriorCountPerFloorplan().hashCode());
        return hashCode;
    }

    @Override
    public WorldCount clone() {
        try {
            return (WorldCount) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new IllegalStateException("Got a CloneNotSupportedException from Object.clone() " + "even though we're Cloneable!", e);
        }
    }

    @com.amazonaws.annotation.SdkInternalApi
    @Override
    public void marshall(ProtocolMarshaller protocolMarshaller) {
        com.amazonaws.services.robomaker.model.transform.WorldCountMarshaller.getInstance().marshall(this, protocolMarshaller);
    }
}


text:
Vagner Souza

Angélica Canuto

Felipe Mattos


text:
const platformToOs = new Map([
  ['darwin', 'macOS'],
  ['linux', 'Linux'],
  ['win32', 'Windows']
])

// electron memory APIs are documented as KB but are actually KiB
const kibibytesToBytes = kibibytes => kibibytes * 1024

const createDeviceUpdater = (client, NativeClient, device) => newProperties => {
  Object.assign(device, newProperties)

  try {
    NativeClient.setDevice(device)
  } catch (err) {
    client._logger.error(err)
  }
}

module.exports = (app, screen, process, filestore, NativeClient, powerMonitor) => ({
  load (client) {
    const device = {}
    let cachedDevice = {}

    try {
      // fetch the device ID from the filestore - if one does not exist it will be created for us
      cachedDevice = filestore.getDeviceInfo()
    } catch (e) {
      // swallow errors
      client._logger.error(e)
    }

    const updateDevice = createDeviceUpdater(client, NativeClient, device)

    updateDevice({
      ...cachedDevice,
      totalMemory: kibibytesToBytes(process.getSystemMemoryInfo().total),
      locale: app.getLocale(),
      osName: platformToOs.get(process.platform) || process.platform,
      osVersion: process.getSystemVersion(),
      runtimeVersions: {
        node: process.versions.node,
        chrome: process.versions.chrome,
        electron: process.versions.electron
      }
    })

    client.addMetadata('device', {
      // the value for 'idleThreshold' doesn't matter here because it is ignored
      // if the system is locked and that's the only state we care about
      isLocked: powerMonitor.getSystemIdleState(1) === 'locked',
      // note: this is only available from Electron 12, earlier versions cannot
      // read the battery state without 'on-ac'/'on-battery' events
      usingBattery: powerMonitor.onBatteryPower
    })

    app.whenReady().then(() => {
      // on windows, app.getLocale won't return the locale until the app is ready
      const locale = app.getLocale()

      if (device.locale !== locale) {
        updateDevice({ locale })
      }

      // the screen module can't be used until the app is ready
      const primaryDisplay = screen.getPrimaryDisplay() || {}

      client.addMetadata('device', {
        screenResolution: primaryDisplay.size,
        screenDensity: primaryDisplay.scaleFactor
      })

      screen.on('display-metrics-changed', (event, display, changedMetrics) => {
        // ignore updates to non-primary screens
        if (display.id !== primaryDisplay.id) {
          return
        }

        // ignore updates that don't affect the size or scale factor
        if (!changedMetrics.includes('bounds') && !changedMetrics.includes('scaleFactor')) {
          return
        }

        client.addMetadata('device', {
          screenResolution: display.size,
          screenDensity: display.scaleFactor
        })
      })
    })

    powerMonitor.on('on-ac', () => {
      client.addMetadata('device', { usingBattery: false })
    })

    powerMonitor.on('on-battery', () => {
      client.addMetadata('device', { usingBattery: true })
    })

    powerMonitor.on('unlock-screen', () => {
      client.addMetadata('device', { isLocked: false })
    })

    powerMonitor.on('lock-screen', () => {
      client.addMetadata('device', { isLocked: true })
    })

    client.addOnError(event => {
      event.device = Object.assign(
        {},
        event.device,
        device,
        {
          freeMemory: kibibytesToBytes(process.getSystemMemoryInfo().free),
          time: new Date()
        }
      )

      event.addMetadata('device', { idleTime: powerMonitor.getSystemIdleTime() })

      setDefaultUserId(event)
    }, true)

    client.addOnSession(session => {
      session.device = Object.assign(
        {},
        session.device,
        device
      )
      setDefaultUserId(session)
    })

    const setDefaultUserId = (eventOrSession) => {
      // device id is also used to populate the user id field, if it's not already set
      const user = eventOrSession.getUser()
      if (!user || !user.id) {
        eventOrSession.setUser(eventOrSession.device.id)
      }
    }

    client._device = device
  }
})


text:
<?php
session_start();
    include "../../connexionDB.php";

//Fonction pour valider les valeurs saisies dans les champs en fonction de la taille
function is_valid($value, $taille)
{
    return  (is_string($value) and strlen($value)<$taille);
}

// les variables

    $barre=$_POST['barre'];
    $cip=$_POST['cip'];
    $interne=$_POST['interne'];
    $dci=$_POST['dci'];
    $designation=$_POST['designation'];
    $peremption=$_POST['peremption'];
    $tva=$_POST['tva'];
    $achat=$_POST['achat'];
    $coef=$_POST['coef'];
    $reduction=$_POST['reduction'];
    $vente=$_POST['vente'];
    $labo=$_POST['laboratoire'];
    $localisation=$_POST['localisation'];
    $exploitant=$_POST['exploitant'];
    $classe=$_POST['classe'];
    $specialite=$_POST['specialite'];
    $forme=$_POST['forme'];

//Si cliquer sur envoyer
if (isset($_POST['submit'])) {
    //Traitement des champs
    $message = "";
    if (empty($_POST['cip'])) {
        $message = "Veuillez renseigner le numero CIP du produit !";
    }
    elseif (empty($_POST['barre'])) {
        $message = "Veuillez renseigner le code barre du produit !";
    }
    elseif (empty($_POST['interne'])) {
        $message = "Veuillez renseigner le code interne du produit !";
    }
    elseif (empty($_POST['dci'])) {
        $message = "Veuillez renseigner la molecule de base du produit !";
    }
    elseif (empty($_POST['designation'])) {
        $message = "Veuillez renseigner le nom du produit !";
    }
    elseif (empty($_POST['peremption'])) {
        $message = "Veuillez renseigner la date de peremption du produit !";
    }
    elseif (empty($_POST['achat'])) {
        $message = "Veuillez renseigner le prix d'achat du produit !";
    }
    elseif (empty($_POST['coef'])) {
        $message = "Veuillez renseigner le coeficient multiplicatif du produit !";
    }

    elseif (empty($_POST['vente'])) {
        $message = "Veuillez renseigner le prix de vente du produit !";
    }

    //si aucune erreur, on verifie les valeurs et on enregistre
    if (empty($message)) {
        $requete = $bdd->prepare('INSERT INTO produit(code_barre, code_cip, code_interne,dci,designation,date_peremption,Soumis_tva,prix_achat,multiplicateur,reduction,prix_vente,code_lab,code_localisation,code_exploitant, code_clas,code_specialite,code_forme)
                                VALUES(:barre,:cip,:interne,:dci,:designation,:permption,:tva,:achat,:coef,:reduction,:vente,:labo,:localisation,:exploitant,:classe,:specialite,:forme)');
// On lie les variables définie au-dessus au paramètres de la requête préparée
        $requete->bindValue(':barre', $barre, PDO::PARAM_STR);
        $requete->bindValue(':cip', $cip, PDO::PARAM_STR);
        $requete->bindValue(':interne', $interne, PDO::PARAM_STR);
        $requete->bindValue(':dci', $dci, PDO::PARAM_STR);
        $requete->bindValue(':designation', $designation, PDO::PARAM_STR);
        $requete->bindValue(':peremption', $peremption, PDO::PARAM_STR);
        $requete->bindValue(':tva', $tva, PDO::PARAM_STR);
        $requete->bindValue(':achat', $achat, PDO::PARAM_STR);
        $requete->bindValue(':coef', $coef, PDO::PARAM_STR);
        $requete->bindValue(':reduction', $reduction, PDO::PARAM_STR);
        $requete->bindValue(':vente', $vente, PDO::PARAM_STR);
        $requete->bindValue(':labo', $labo, PDO::PARAM_STR);
        $requete->bindValue(':localisation', $localisation, PDO::PARAM_STR);
        $requete->bindValue(':exploitant', $exploitant, PDO::PARAM_STR);
        $requete->bindValue(':classe', $classe, PDO::PARAM_STR);
        $requete->bindValue(':specialite', $specialite, PDO::PARAM_STR);
        $requete->bindValue(':forme', $forme, PDO::PARAM_STR);
//On exécute la requête
        $requete->execute();

        // Un petit message pour confirmer l'insertion

        echo "Insertion reussie!";
        http_redirect();
    }
        else
            $message = "Champ CIP invalide !";

}

?>


text:
try:
    from .utils import testForMain
except:
    from utils import testForMain
testForMain(__name__)

# ------------------------------------------------------------------------
# SPEEDTEST.PY
#
# AUTHOR(S):    Peter Walker    pwalker@csumb.edu
#               Brandon Layton  blayton@csumb.edu
#
# PURPOSE-  This object will hold a raw data file's header information (see list of variables)
#           and then parse individual test from the remaining text, storing them as a series of
#           related objects
#
# VARIABLES:
#   FileName            String, holding file name that is being parsed
#   Date                String, holding the date the test was taken
#   Time                String, holding the time test was taken
#   OSName              String, holding OS name that this test was conducted with
#   OSArchitecture      String, holding OS architecture that this test was conducted with
#   OSVersion           String, holding OS version that this test was conducted with
#   JavaVersion         String, holding Java version that this test was conducted with
#   JavaVendor          String, holding Java vendor that this test was conducted with
#   NetworkType         String, holding Network Type that this test was conducted with
#                           note: this is either mobile or netbook
#   Server              String, holding Server that this test was conducted with
#   Host                String, holding Host that this test was conducted with
#   NetworkProvider     String, holding Network Provider that this test was conducted with (i.e. the Carrier)
#   NetworkOperator     String, holding Network Operator that this test was conducted with (i.e. the Carrier)
#                           note: Sometimes, the carrier name is in Network Provider, other times it is in Network Operator
#   NetworkCarrier      String, will always hold one of the given network carriers defined in the SpeedTestDataStructure.
#                           note: determined when NetworkProvider and Operator are parsed. A copy of either variable
#   DeviceID            String, the Device ID number
#   ConnectionType      String, holding Connection type that this test was conducted with
#   LocationID          Integer, the ID number of the location that this test was conducted at
#   Latitude            Integer, the latitude given by the GPS that this test was conducted at.
#                           Is 0 if no GPS data was available
#   Longitude           Integer, the latitude given by the GPS that this test was conducted at.
#                           Is 0 if no GPS data was available
#   mySpeedTests        List, holding all of the Individual Speed tests that are contained in the given file being parsed
#   FileStreamLoc       Integer, representing byte location that the script is at in file stream
#   ignored_text        String, text from original file that was ignored when splitting up text by Test
#   short_str_method    Boolean, used in SpeedTestDataStructure if the printout requested in short of long.
#                           Default is False
#
# FUNCTIONS:
#   __init__ - initializes the object by parsing the data in the given file path. calls load()
#       INPUTS-     self:       reference to the object calling this method (i.e. Java's THIS)
#                   filePath:   String, containing absolute path to raw data file
#                   listOfCarriers: List, passed from STDS. Used when defining NetworkCarrier
#                   short:      Boolean, determines how this object will print out it's data when __str__ is called
#       OUTPUTS-    none
#
#   loadHeaderInfo - initializes the object by parsing the data in the given file path.
#       INPUTS-     self:       reference to the object calling this method (i.e. Java's THIS)
#                   filePath:   String, containing absolute path to raw data file
#                   listOfCarriers: List, passed from STDS. Used when defining NetworkCarrier
#       OUTPUTS-    None
#
#   createIndivTests - This takes the string of the entire file contents, and splits it up by test.
#                      These chunks are then passed to their appropiate constructors
#       INPUTS-     self:           reference to the object calling this method (i.e. Java's THIS)
#                   fileContents:   String, containing the entire file contents
#       OUTPUTS-    None            As tests are created, they are added to the struct
#
#   convert_Obj_To_2D - Converts this SpeedTestFile object into a 2D array, and returns the result
#       INPUTS-     self:           reference to the object calling this method (i.e. Java's THIS)
#       OUTPUTS-    objectAs2D:   the 2D array that will be returned
#
#   calc_TCP_StDev_for_Distribution - For each test in this object, if the test is a TCP test, calculate the
#                       standard deviation of the sum of thread speeds at each 1 second interval.
#                       The IndivSpeedTest object will handle putting the value into the structure
#       INPUTS-     self:           reference to the object calling this method (i.e. Java's THIS)
#                   structRef:      reference to the structure created in STDs.convertTo_ObjectToTCP()
#                   list_carriers:  reference to array of carriers
#       OUTPUTS-    None
#
#   calc_TCP_StDev_and_Median_then_Append - This calculates the standard deviation and
#                       median of the TCP tests in this object, and then appends the values to
#                       the CPUC_Results CSV the is chosen by the user
#       INPUTS-     self:           reference to the object calling this method (i.e. Java's THIS)
#                   origCSVRef:     reference to the 2D array of the CSV file
#       OUTPUTS-    None            returns 16 values
#
#   calc_TCP_Total_StDev_and_Median_then_Append - This calculates the standard deviation and
#                       median of all the TCP tests in this object, and then appends the values to
#                       the CPUC_Results CSV the is chosen by the user
#       INPUTS-     self:           reference to the object calling this method (i.e. Java's THIS)
#                   origCSVRef:     reference to the 2D array of the CSV file
#       OUTPUTS-    None            returns 4 values
#
#   calc_TCP_Throughput_then_Append - ..
#       INPUTS-     ..
#       OUTPUTS-    ..
#
#   calc_rVal_and_MOS_then_Append - ..
#       INPUTS-     ..
#       OUTPUTS-    ..
#
#   this_File_Index_in_GivenCSV - This looks for the row in the CPUC_Results CSV that corresponds
#                       to this object's information
#       INPUTS-     self:           reference to the object calling this method (i.e. Java's THIS)
#                   origCSVRef:     reference to the 2D array of the CSV file
#       OUTPUTS-    index:          Integer, the row index at which this file is located in the CPUC_Results CSV
#                                   If the file was not found in the CSV, returns None
#
#   get_Test_with_Attribute - This takes a few given strings. Returns the corresponding Test
#                       whose value in the specified attribute corresponds to the passed value
#       INPUTS-     self:       reference to the object calling this method (i.e. Java's THIS)
#                   attribute:  String, the attribute name we are comparing to
#                   value:      String/Integer, value we wish to find
#                   testType:   String, is either TCP, UDP, or PING.
#       OUTPUTS-    test:       Test object whose attribute corresponds to the given value in value.
#                               If no test was found, returns None
#
#   printSpeedTests - Return a string that has the information of each speed test in the object
#       INPUTS-     self:   reference to the object calling this method (i.e. Java's THIS)
#       OUTPUTS-    text:   String, a representation of the IndividualSpeedTests held in
#                           this objects mySpeedTests
#
#   __str__ - Returns a string represenation of the object
#       INPUTS-     self:   reference to the object calling this method (i.e. Java's THIS)
#       OUTPUTS-    String, representing the attributes of the object (THIS)
#
# ------------------------------------------------------------------------

from .utils import readToAndGetLine, monthAbbrToNum
from .utils import calcStDevP, calcMean, getMedian, calcTCPThroughput, calc_rVal_MOS
from .utils import global_str_padding as pad; pad = pad*1
from .TCP_Test import TCPTest
from .UDP_Test import UDPTest
from .Ping_Test import PingTest

class SpeedTestFile(object):
    # -------------------
    # Initializing some class attributes
    #File information
    FileName = "UNKNOWN"

    Date = "UNKNOWN"
    Time = "UNKNOWN"

    OSName = "UNKNOWN"
    OSArchitecture = "UNKNOWN"
    OSVersion = "UNKNOWN"
    JavaVersion = "UNKNOWN"
    JavaVendor = "UNKNOWN"

    NetworkType = "UNKNOWN"
    Server = "UNKNOWN"
    Host = "UNKNOWN"

    NetworkProvider = "UNKNOWN"
    NetworkOperator = "UNKNOWN"
    NetworkCarrier = "UNKNOWN"
    DeviceID = "UNKNOWN"
    ConnectionType = "UNKNOWN"

    LocationID = "UNKNOWN"
    Latitude = 0
    Longitude = 0

    mySpeedTests = {}

    #Variables for tracking errors, filestream location, or str method
    FileStreamLoc = 0
    ignored_text = []
    short_str_method = False
    # -------------------

    # DESC: init functions calls load using the given file path
    def __init__(self, filePath, listOfCarriers, short=False):
        self.short_str_method = short
        self.ignored_text = []
        self.mySpeedTests = {  "TCP" : [],
                               "UDP" : [],
                               "PING": []   }
        self.FileName = filePath.split("/")[-1]
        self.loadHeaderInfo(filePath, listOfCarriers)
    #END INIT

    # DESC: parses data and info in given file (location is filePath)
    #       and stores it in the object's attributes
    def loadHeaderInfo(self, filePath, listOfCarriers):
        #Open the file and read through the first line (which is "CPUC Beta .....")
        # save byte location to self.FileStreamLoc
        fs = open(filePath,'r')
        fs.readline()
        self.FileStreamLoc = fs.tell()

        #Reading in the Date and Time of the test
        #Also setting the Network Type, as this is where the file types start to differ
        fs.seek(self.FileStreamLoc)
        datetime = fs.readline()
        self.FileStreamLoc = fs.tell()
        if ("Testing started" not in datetime):
            self.NetworkType = "netbook"
            self.Date = datetime.split(" ")[0]
            self.Time = datetime.split(" ")[1][:-1]
        else:
            self.NetworkType = "mobile"
            #Formatting the date and time as "mm/dd/yyyy" and "hh:mm:ss" when not in this format
            datetime = datetime.split("started at ")[1][4:-1]
            month = str(monthAbbrToNum(datetime[:3]))
            day = str(datetime[4:6])
            year = str(datetime[-4:])
            time = str(datetime[7:9]) + ":" + str(datetime[10:12]) + ":" + str(datetime[13:15])
            self.Date = month + "/" + day + "/" + year
            self.Time = time
        fs.seek(self.FileStreamLoc)
        #END IF/ELSE

        #Read in Operating System Header Information
        temp = readToAndGetLine(fs,"OS: ")
        if temp:
            self.OSName = temp.split("Name = ")[1].split(",")[0]
            self.OSArchitecture = temp.split("Architecture = ")[1].split(",")[0]
            self.OSVersion = temp.split("Version = ")[1][:-1]
            # After parsing text, we need to check that there are no empty values
            if self.OSName == "": self.OSName = "N/A"
            if self.OSArchitecture == "": self.OSArchitecture = "N/A"
            if self.OSVersion == "": self.OSVersion = "N/A"
        else:
            self.OSName = "N/A"
            self.OSArchitecture = "N/A"
            self.OSVersion = "N/A"
        fs.seek(self.FileStreamLoc)
        #END IF/ELSE

        #Read in Java Header Information
        temp = readToAndGetLine(fs,"Java: ")
        if temp:
            self.JavaVersion = temp.split("Version = ")[1].split(",")[0];
            self.JavaVendor  = temp.split("Vendor = ")[1][:-1]
            if self.JavaVersion == "": self.JavaVersion = "N/A"
            if self.JavaVendor == "": self.JavaVendor = "N/A"
        else:
            self.JavaVersion = "N/A"
            self.JavaVendor = "N/A"
        fs.seek(self.FileStreamLoc)
        #END IF/ELSE

        #Read in Server Header Information
        try:
            self.Server = readToAndGetLine(fs,"Server: ").split("Server: ")[1][:-1]
            if self.Server == "": self.Server = "N/A"
        except:
            self.Server = "N/A"
        fs.seek(self.FileStreamLoc)
        #Read in Host Header Information
        try:
            self.Host = readToAndGetLine(fs,"Host: ").split("Host: ")[1][:-1]
            if self.Host == "": self.Host = "N/A"
        except:
            self.Host = "N/A"
        fs.seek(self.FileStreamLoc)
        #END TRY/EXCEPT

        #Get Network Provider
        #First TRY branch is for the mobile data files (no spaces in Network Provider)
        # Second TRY branch is for the netbook data files (space between Network and Provider)
        try:
            self.NetworkProvider = readToAndGetLine(fs,"NetworkProvider: ").split("NetworkProvider: ")[1][:-1]
            if self.NetworkProvider == "": self.NetworkProvider = "N/A"
        except:
            fs.seek(self.FileStreamLoc)
            try:
                self.NetworkProvider = readToAndGetLine(fs,"Network Provider: ").split("Network Provider: ")[1][:-1]
                if self.NetworkProvider == "": self.NetworkProvider = "N/A"
            except:
                self.NetworkProvider = "N/A"
        fs.seek(self.FileStreamLoc)
        #END TRY/EXCEPT
        #Get Network Operator
        try:
            self.NetworkOperator = readToAndGetLine(fs,"NetworkOperator: ").split("NetworkOperator: ")[1][:-1]
            if self.NetworkOperator == "": self.NetworkOperator = "N/A"
        except:
            self.NetworkOperator = "N/A"
        fs.seek(self.FileStreamLoc)
        #Defining self.NetworkCarrier, based on the data in NetworkProvider and NetworkOperator
        if self.NetworkProvider in listOfCarriers:
            self.NetworkCarrier = self.NetworkProvider
        elif self.NetworkOperator in listOfCarriers:
            self.NetworkCarrier = self.NetworkOperator
        else:
            self.NetworkCarrier = "N/A"

        #Get Device ID
        #First TRY branch is for the mobile data files (no spaces in Network Provider)
        # Second TRY branch is for the netbook data files (space between Network and Provider)
        try:
            self.DeviceID = readToAndGetLine(fs,"Device ID: ").split("Device ID: ")[1][:-1]
            if self.DeviceID == "": self.DeviceID = "N/A"
        except:
            fs.seek(self.FileStreamLoc)
            try:
                self.DeviceID = readToAndGetLine(fs,"Host name: ").split("Host name: ")[1][:-1]
                if self.DeviceID == "": self.DeviceID = "N/A"
            except:
                self.DeviceID = "N/A"
        fs.seek(self.FileStreamLoc)
        #Get Device ConnectionType
        try:
            self.ConnectionType = readToAndGetLine(fs,"ConnectionType: ").split("ConnectionType: ")[1][:-1]
            if self.ConnectionType == "": self.ConnectionType = "N/A"
        except:
            self.ConnectionType = "N/A"
        fs.seek(self.FileStreamLoc)
        #END TRY/EXCEPTs

        #Get the Location ID
        try:
            self.LocationID = readToAndGetLine(fs,"Location ID: ").split("Location ID: ")[1][:-1]
            if self.LocationID == "": self.LocationID = "N/A"
        except:
            fs.seek(self.FileStreamLoc)
            try:
                self.LocationID = readToAndGetLine(fs,"Location: ").split("Location: ")[1][:-1]
                if self.LocationID == "": self.LocationID = "N/A"
            except:
                self.LocationID = "N/A"
        fs.seek(self.FileStreamLoc)
        #END TRY/EXCEPT

        #Get the Latitude and Longitude, if it is available
        try:
            temp = readToAndGetLine(fs, "Latitude:")
            while temp:
                temp = temp.split("Latitude:")[1][:-1]
                if temp != "0.0":
                    self.Latitude = temp
                temp = readToAndGetLine(fs, "Longitude:")
                temp = temp.split("Longitude:")[1][:-1]
                if temp != "0.0":
                    self.Longitude = temp
                temp = readToAndGetLine(fs, "Latitude:")
        except:
            catching_exceptions_and_doing_nothing = True
        fs.seek(self.FileStreamLoc)

        #Set the file stream to the beginning
        fs.seek(0)

        #Reading in the remaining contents of the file (the speed tests) and passing
        # it to the function that will split it into test chunks and create the objects
        remainingFileContents = fs.read()
        # !!!!
        # SUPER IMPORTANT!! Otherwise, problems galore
        # Don't want open streams o_O
        fs.close()
        #Passing the string of file contents to the function that will split each test into it's own unit
        self.createIndivTests(remainingFileContents)
    #END DEF

    # DESC: This takes the remaining contents of the file being parsed, splits the
    #       content by test (to included any error messages) and the creates the Test objects
    def createIndivTests(self, fileContents):
        #First splitting the contents into sections. These section are all of the chunks
        # that are separated by two newline characters ('\n\n')
        fileContents = fileContents.split('\n\n')
        sortedFileContents = []
        appending = False
        aNewChunk = ""
        #These loops will create a very large string that contains all of the text
        # pertaining to one test. This will include error messages, if they are present.
        # A new test is know to start if "Starting Test" is in the string chunk. Once we
        # know that we have all of the string pertaining to one test, it is appended to the array
        for chunk in fileContents:
            if not appending:
                if "Starting Test " in chunk:
                    aNewChunk =  chunk + "\n"
                    appending = True
                else:
                    continue
                #END IF/ELSE
            else:
                if "Starting Test " not in chunk:
                    aNewChunk += chunk + "\n"
                else:
                    sortedFileContents.append(aNewChunk)
                    aNewChunk =  chunk + "\n"
                #END IF/ELSE
            #END IF/ELSE
            #This is so that the last test is appended, as it will not be when the loop finishes
            if chunk == fileContents[-1]:
                sortedFileContents.append(aNewChunk)
            #END IF
        #END FOR
        #Now we go through each of our sorted string chunks, and determine whether they are a TCP test,
        # a UDP test, or a Ping Test. The appropiate constructor is then called
        for testText in sortedFileContents:
            testAsArray = testText.split('\n')
            command = ""
            #Getting the command line string used to run the test
            for line in testAsArray:
                if "Iperf command line" in line:
                    command = line; break
            #END FOR
            #Determining what kind of test the current chunk of test is
            # and calling the corresponding constructor
            if " -e " in command:
                newTCP = TCPTest(testText, short=self.short_str_method)
                self.mySpeedTests["TCP"].append(newTCP)
            elif " -u " in command:
                #The 1 second test chunk is organized a little differently than the 5 second test,
                # and needs to use a different variation of constructor
                if ("-t 1" in command):
                    #Adding an extra new line to separate each 1 second UDP test,
                    # and keeping track of the test number, as it is the same for each one
                    newTestText = ""
                    thisTestNumber = 0
                    #This loop adds an extra empty line in between each "sub" test. This is
                    # because in the string, there are 3 one second UDP test, and this is the
                    # easiest way to split them up later
                    for line in testText.split('\n'):
                        if "Starting Test" in line:
                            thisTestNumber = line.split(" ")[2].split(":")[0].split("..")[0]
                        if "Starting UDP 1" in line:
                            newTestText += '\n'
                        newTestText += line+'\n'
                    #END FOR
                    #This is when the 3 one second tests are separated. One by one, the UDP constructor
                    # is called, and the text passed is and individual one second UDP test.
                    testText = newTestText.split('\n\n')
                    subTestNum = 0
                    for chunk in testText:
                        if "Starting UDP 1" in chunk:
                            subTestNum += 1
                            testNum = str(thisTestNumber)+"."+str(subTestNum)
                            newUDP = UDPTest(chunk, testNum, self.short_str_method)
                            self.mySpeedTests["UDP"].append(newUDP)
                    #END FOR
                else:
                    newUDP = UDPTest(testText, short=self.short_str_method)
                    self.mySpeedTests["UDP"].append(newUDP)
                #END IF/ELSE
            elif (": Ping" in testText or "Pinging" in testText):
                newPing = None
                if self.NetworkType == "mobile":
                    newPing = PingTest(testText, isMobile=True, short=self.short_str_method)
                else:
                    newPing = PingTest(testText, isMobile=False, short=self.short_str_method)
                self.mySpeedTests["PING"].append(newPing)
            else:
                self.ignored_text.append(testText)
            #END IF/ELIF/ELSE
        #END FOR
    #END DEf

    # DESC: Converts all of the individual test and ping threads and such
    #       in this object and returns a 2D array of it all
    def convert_Obj_To_2D(self):
        objectAs2D = []
        #Setting up the basic information at the top of the 2D array/.csv file
        objectAs2D.append(["Filename", self.FileName])
        objectAs2D.append(["DateTime", self.Date + " " + self.Time])
        objectAs2D.append(["Location ID", self.LocationID])
        objectAs2D.append(["Network Type", self.NetworkType])
        objectAs2D.append(["Provider", self.NetworkCarrier])
        #Counter refers to the current array in objectAs2D. This array is
        # where the tests will start to be array-itized and appened
        for TCPTest in self.mySpeedTests["TCP"]:
            objectAs2D.extend(TCPTest.convert_Obj_To_2D())
        #END FOR
        #Now we create the arrays for the UDP tests, as their structure differs from
        # that of the TCP tests.
        for UDPTest in self.mySpeedTests["UDP"]:
            objectAs2D.extend(UDPTest.convert_Obj_To_2D())
        #END FOR
        #This is where we convert the Ping tests into 2D versions of themselves. Their data
        # is the appended to the end of the 2D array for this file.
        for PingTest in self.mySpeedTests["PING"]:
            objectAs2D.extend(PingTest.convert_Obj_To_2D())
        #END FOR
        return objectAs2D
    #END DEF

    # DESC: Looping through each Test, if the test if of type TCP, then
    #       call it's thread sum function, and append the standard deviation to
    #       the passed structure reference.
    def calc_TCP_StDev_for_Distribution(self, structRef, list_carriers):
        #We only use the TCP tests in the function
        for TCPTest in self.mySpeedTests["TCP"]:
            #We only use the test if there were no errors in it
            if not TCPTest.ERROR:
                #This if/else block makes sure that the test is in our list of carriers.
                # Otherwise, continue will skip to the next test in the object
                if (self.NetworkCarrier in list_carriers):  mycarrier = self.NetworkCarrier
                else:  continue
                #END IF/ELIF
                up_stdev = calcStDevP(TCPTest.sum_Threads_Speed("Up"))
                if up_stdev is not None:
                    structRef[self.NetworkType]\
                             [mycarrier]\
                             [TCPTest.ConnectionLoc]\
                             ["Up"].append(up_stdev)
                #END IF
                down_stdev = calcStDevP(TCPTest.sum_Threads_Speed("Down"))
                if down_stdev is not None:
                    structRef[self.NetworkType]\
                             [mycarrier]\
                             [TCPTest.ConnectionLoc]\
                             ["Down"].append(down_stdev)
                #END IF
            #END IF
        #END FOR
    #END DEF

    # DESC: This uses the information in this object to find the row in the CPUC_Results CSV
    #       that it corresponds to. It first checks with DeviceID, Date, and Time. If that
    #       doesn't work, it tries with LocationID, Date, Time, and Network Carrier.
    def this_File_Index_in_GivenCSV(self, origCSVRef):
        index = None
        for row in origCSVRef:
            if ((self.DeviceID in str(row[13])) and
                (self.Date in str(row[5])) and
                (self.Time in str(row[6])) ):
                #Before we return the index, we first test that we have nothing in the columns we
                # are creating. The index of the last column of the values we are inserting is
                # the length of the header row minus 1.
                # If the TRY statement doesn't catch an error, then there are already values there, and we
                # continue searching.
                try:
                    isIndexDefined = row[len(origCSVRef[0])-1]
                except:
                    index = origCSVRef.index(row)
                    break
                #END TRY/EXCEPT
            elif ((self.LocationID in str(row[3])) and
                  (self.Date in str(row[5])) and
                  (self.Time in str(row[6])) and
                  (self.NetworkCarrier in str(row[7])) ):
                try:
                    isIndexDefined = row[len(origCSVRef[0])-1]
                except:
                    index = origCSVRef.index(row)
                    break
                #END TRY/EXCEPT
            #END IF/ELIF
        #END FOR
        if (index is None):
            for row in origCSVRef:
                #If our other attempts at finding the row failed, we'll search using
                # just the date, time, and location id.
                if ((self.LocationID in str(row[3])) and
                    (self.Date in str(row[5])) and
                    (self.Time in str(row[6])) ):
                    try:
                        isIndexDefined = row[len(origCSVRef[0])-1]
                    except:
                        index = origCSVRef.index(row)
                        break
                    #END TRY/EXCEPT
                #END IF
            #END FOR
        #END IF
        return index
    #END DEF

    # DESC: Using the value passed in num, this looks for the test whose TestNumber
    #       corresponds to that value. If there was no such test, returns None
    def get_Test_with_Attribute(self, attribute, value, testType="TCP"):
        if (not isinstance(attribute, str)) or (value is None):
            raise TypeError
        for aTest in self.mySpeedTests[testType]:
            try:
                if (aTest.__dict__[attribute] == value):
                    return aTest
                #END IF
            except:
                the_test_didnt_have_that_attribute = True
            #END TRY/EXCEPT
        #END FOR
        return None
    #END DEF

    # DESC: This function first tries to find the index of this file in the CPUC_Results CSV.
    #       If the file was found, it calculates the StDev and Median for all of the TCP tests,
    #       and appends the values to the CSV.
    def calc_TCP_StDev_and_Median_then_Append(self, origCSVRef):
        thisFile = self.this_File_Index_in_GivenCSV(origCSVRef)
        if thisFile is not None:
            toAppend = []
            #The test number order is specific, as test 1 is the first TCP West, 2 is TCP East, etc.
            # Also, the array values are all strings, as the initialization removed the values
            # from a string, and so the Test Number variable remained a string
            for testNum in ["1","2","4","5"]:
                indivTest = self.get_Test_with_Attribute("TestNumber", testNum, "TCP")
                if indivTest is not None:
                    toAppend.extend( indivTest.create_Array_of_StDev_Median_for_CSV() )
                else:
                    toAppend.extend( ["error"]*4 )
            #END FOR
            origCSVRef[thisFile].extend(toAppend)
        #END IF
    #END DEF

    # DESC: ..
    #       ..
    def calc_TCP_Total_StDev_and_Median_then_Append(self, origCSVRef):
        thisFile = self.this_File_Index_in_GivenCSV(origCSVRef)
        if thisFile is not None:
            #We producing four numbers, two from each direction. For each TCP test direction (Up and Down),
            # we will create an array that will hold values that are the sum of the speeds of all 4 TCP threads
            # in all 4 tests. For each TCP test, in each direction, the code adds together the values from
            # each thread for a given time interval, and saves the number in an array. That array is passed
            # to the StDev and Mean functions.
            UpVals = []
            DownVals = []
            #For each TCP test, we call the sum_Threads_Speed() function. Given a direction (Up/Down),
            # this function will return an array where each number represents a sum of the speeds
            # experienced by each thread. If all 4 threads wer uploading/downloading for 10 seconds,
            # the function will return an array of 10 values, where each value is the sum of the speed of
            # all 4 threads. These values are added to our large array, which will
            # be passed to calcStDev() and getMedian()
            for TCPTest in self.mySpeedTests["TCP"]:
                UpVals.extend(TCPTest.sum_Threads_Speed("Up"))
                DownVals.extend(TCPTest.sum_Threads_Speed("Down"))
            #END FOR
            #The headers appended are in this order
            #["cTCP_UP_STDEV","cTCP_UP_MEAN","cTCP_DOWN_STDEV","cTCP_DOWN_MEAN"]
            #First, we append the stdev and mean of the Up direction. If there were no tests, append "error"
            if UpVals:
                origCSVRef[thisFile].extend( [calcStDevP(UpVals), getMedian(UpVals)] )
            else:
                origCSVRef[thisFile].extend( ["error"]*2 )
            #Now we append the stdev and mean of the Down direction
            if DownVals:
                origCSVRef[thisFile].extend( [calcStDevP(DownVals), getMedian(DownVals)] )
            else:
                origCSVRef[thisFile].extend( ["error"]*2 )
        #END IF
    #END DEF

    # DESC: ..
    #       ..
    def calc_TCP_Throughput_then_Append(self, origCSVRef):
        thisFile = self.this_File_Index_in_GivenCSV(origCSVRef)
        if thisFile is not None:
            #Declaring the array of values that will be appended
            toAppend = []
            #Calculating the theoretical TCP Throughput (TP_trtcl) for a given connection based
            # on the average RTT in the Ping test. Loss is the theoretical percent of packets lost
            westPing = self.get_Test_with_Attribute("ConnectionLoc", "West", "PING")
            eastPing = self.get_Test_with_Attribute("ConnectionLoc", "East", "PING")
            #In both blocks, we have a tertiary if statement, where ___TP_trtcl is only calculated if the ping
            # returned by the statement above had no errors. Otherwise, it equals None, and is appended
            westTP_trtcl = (calcTCPThroughput(float(westPing.RTTAverage), Loss=0.0001)
                            if (westPing is not None) else "DataError")
            toAppend.append(westTP_trtcl)
            eastTP_trtcl = (calcTCPThroughput(float(eastPing.RTTAverage), Loss=0.0001)
                            if (eastPing is not None) else "DataError")
            toAppend.append(eastTP_trtcl)
            #For each direction, we add the transfer speeds from the final measurement, and determine
            # the percent of the theoretical throughput that is achieved.
            for testNum in ["1","2","4","5"]:
                TCPTest = self.get_Test_with_Attribute("TestNumber", testNum, "TCP")
                if (TCPTest is not None) and (not TCPTest.ERROR):
                    for direction in ["Up","Down"]:
                        #We first add up the final measurement's speeds, keeping track of how many
                        # values we've added. We then divide the number, and divide again by the calculated
                        # theoretical TCP Throughput for the TCP Test's direction,
                        # and then multiply by 100 (to get a percent). This is then appended to the array
                        sumSpeed = 0; threadCnt = 0
                        for thread in TCPTest.myPingThreads[direction]:
                            sumSpeed += thread.myfinalPing.speed; threadCnt += 1.0
                        #END FOR
                        if ((TCPTest.ConnectionLoc == "West") and ((westTP_trtcl == "DataError") or (westTP_trtcl==0))):
                            TP_pct = "DataError"
                        elif ((TCPTest.ConnectionLoc == "East") and ((eastTP_trtcl == "DataError") or (eastTP_trtcl==0))):
                            TP_pct = "DataError"
                        else:
                            TP_pct = str( ((sumSpeed/threadCnt)/westTP_trtcl)*100
                                        if TCPTest.ConnectionLoc == "West"
                                        else ((sumSpeed/threadCnt)/eastTP_trtcl)*100 ) + "%"
                        #END IF/ELIF/ELSE
                        toAppend.append(TP_pct)
                    #END FOR
                else:
                    toAppend.extend( ["error"]*2 )
            #END FOR
            origCSVRef[thisFile].extend(toAppend)
        #END IF
    #END DEF

    #DESC: Calculates rVal and MOS of ping tests and appends to CSV reference
    #      delayThresh = if under then they get bucketed
    def calc_rVal_and_MOS_then_Append(self, origCSVRef, delayThreshold):
        #Converted to Python from original script
        # by Ian Bowers, CSUMB, Feb 2013
        thisFile = self.this_File_Index_in_GivenCSV(origCSVRef)
        #Setting a lot of variables that will hold the numbers we need. We have 3 categories;
        # East, West, and Total. Each category has a counter, __TPng (the theoretical number of pings we expect)
        # total (sum), a max (RTT), Lost (packets lost), and F(d)
        westCnt = 0.0;      eastCnt = 0.0;      totalCnt = 0.0
        westTPng = 10.0;    eastTPng = 10.0;    totalTPng = 20.0
        westSum = 0.0;      eastSum = 0.0;      totalSum = 0.0
        westMax = 0.0;      eastMax = 0.0;      totalMax = 0.0
        westLost = 0.0;     eastLost = 0.0;     totalLost = 0.0
        # F(d) -the rate of packets below delay threshold, done by incrementing this value for every packet
        # that is below delayThreshold and then dividing by the total number of measurements. This value
        # is recorded in the variables below
        westFd = 0.0;       eastFd = 0.0;       totalFd = 0.0
        #Declaring a few arrays, which will all be combined at the end before appending to the CSV
        eastResults = []
        westResults = []
        totalResults = []
        #If the variable thisFile is not None, it means that the CSV has a row with this file's information.
        # We can then proceed to calculate the rValue and MOS
        if thisFile is not None:
            #This is the array that will hold the values we wil append to the CSV
            toAppend = []
            #This for loop totals up the RTT times. Each value is put into the total and
            # the test's respective locations (East or West).
            for PingTest in self.mySpeedTests["PING"]:
                #If there was an error in the test, we ignore the test entirely, and continue
                # to the next element
                if PingTest.ERROR:
                    continue
                #The first two blocks determine the direction of the Ping test we are
                # currently accessing. We then update the Max and Lost variables for each location,
                # and then we update the total Max and Lost
                if PingTest.ConnectionLoc == "West":
                    westMax = PingTest.RTTMax if (westMax < PingTest.RTTMax) else westMax
                    westLost += PingTest.PacketsLost
                elif PingTest.ConnectionLoc == "East":
                    eastMax = PingTest.RTTMax if (eastMax < PingTest.RTTMax) else eastMax
                    eastLost += PingTest.PacketsLost
                #END IF/ELIF
                #Updating the total Max and Lost variables
                totalMax = PingTest.RTTMax if (totalMax < PingTest.RTTMax) else totalMax
                totalLost += PingTest.PacketsLost
                #Now we loop through all of the ping times so that we can add them to the Sum variable. If the
                # time retrieved is not 0, the value is added to the ___Sum for it's location (East/West)
                # and the totalSum. We also increment "Fd" if the time is above the given threshold "delayThreshold"
                for index in PingTest.Times:
                    time = PingTest.Times[index]
                    if time > 0:
                        if PingTest.ConnectionLoc == "East":
                            eastCnt += 1
                            eastSum += time
                            if (time < delayThreshold):
                                eastFd += 1
                        elif PingTest.ConnectionLoc == "West":
                            westCnt += 1
                            westSum += time
                            if (time < delayThreshold):
                                westFd += 1
                        #END IF/ELSE
                        totalCnt += 1
                        totalSum += time
                        if (time < delayThreshold):
                            totalFd+=1
                        #END IF
                    #END IF
                #END FOR times
            #END FOR tests

            #This section sees if the east, west, and total counts were 0. If any of them were,
            # that category's result is "NA"
            #West Cateogry
            if (westCnt == 0):
                westResults = ["NA","NA"]
            else:
                (rVal, MOS) = calc_rVal_MOS(westSum, westCnt, westTPng, westLost, westFd)
                westResults = [rVal, MOS]
            #END IF/ELSE
            #East Category
            if (eastCnt == 0):
                eastResults = ["NA","NA"]
            else:
                (rVal, MOS) = calc_rVal_MOS(eastSum, eastCnt, eastTPng, eastLost, eastFd)
                eastResults = [rVal, MOS]
            #END IF/ELSE
            #Total Category
            if (totalCnt == 0):
                totalResults = ["NA","NA"]
            else:
                (rVal, MOS) = calc_rVal_MOS(totalSum, totalCnt, totalTPng, totalLost, totalFd)
                totalResults = [rVal, MOS]
            #END IF/ELSE

            #After either assigning NA to rVal/MOS, or passing the numbers to the rVal/MOS calculating function,
            # we append first the west results, then east, then total
            origCSVRef[thisFile].extend(westResults)
            origCSVRef[thisFile].extend(eastResults)
            origCSVRef[thisFile].extend(totalResults)
        #END IF
    #END DEF

    # DESC: Returns all of the sub tests for this file as a string. If there are no
    #       tests, then it returns a string saying there were no tests
    def printSpeedTests(self):
        text = ""
        for speedTest in self.mySpeedTests["TCP"]:
            text +=  str(speedTest)
        for speedTest in self.mySpeedTests["UDP"]:
            text +=  str(speedTest)
        for speedTest in self.mySpeedTests["PING"]:
            text +=  str(speedTest)
        #END FOR
        if text == "":
            text = pad + "No viable network speed tests"
        return text
    #END DEF

    # DESC: Returns a string representation of the object
    def __str__(self):
        if self.short_str_method:
            return (pad + "Filename: " + str(self.FileName) + "\n" +
                    pad + "DateTime of Speed Test: " + str(self.Date) + " " + str(self.Time) + "\n" +
                    pad + "Network Type: " + str(self.NetworkType) + "\n" +
                    pad + "Network: Provider = " + str(self.NetworkProvider) +
                          ", Operator = " + str(self.NetworkOperator) + "\n" +
                    pad + "Device ID: " + str(self.DeviceID) + "\n" +
                    pad + "Location ID: " + str(self.LocationID) + "\n" +
                    self.printSpeedTests() +
                    "\n"
                    )
        else:
            return (pad + "Filename: " + str(self.FileName) + "\n" +
                    pad + "DateTime of Speed Test: " + str(self.Date) + " " + str(self.Time) + "\n" +
                    pad + "OS: " + str(self.OSName) + ", " + str(self.OSArchitecture) + ", " + str(self.OSVersion) + "\n" +
                    pad + "Java: " + str(self.JavaVersion) + ", " + str(self.JavaVendor) + "\n" +
                    pad + "Network Type: " + str(self.NetworkType) + "\n" +
                    pad + "Connection: Server = " + str(self.Server) + ", Host = " + str(self.Host) + "\n" +
                    pad + "Network: Provider = " + str(self.NetworkProvider) +
                          ", Operator = " + str(self.NetworkOperator) + "\n" +
                    pad + "Device: ID = " + str(self.DeviceID) +
                          ", Connection Type = " + str(self.ConnectionType) + "\n" +
                    pad + "Location ID: " + str(self.LocationID) + "\n" +
                    pad + "Latitude:" + str(self.Latitude) +
                          " Longitude:" + str(self.Longitude) + "\n" +
                    self.printSpeedTests() +
                    "\n"
                    )
    #END DEF
#END CLASS


text:
<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/*
|--------------------------------------------------------------------------
| Enable/Disable Migrations
|--------------------------------------------------------------------------
|
| Migrations are disabled by default for security reasons.
| You should enable migrations whenever you intend to do a schema migration
| and disable it back when you're done.
|
*/
$config['migration_enabled'] = FALSE;

/*
|--------------------------------------------------------------------------
| Migrations table
|--------------------------------------------------------------------------
|
| This is the name of the table that will store the current migrations state.
| When migrations runs it will store in a database table which migration
| level the system is at. It then compares the migration level in the this
| table to the $config['migration_version'] if they are not the same it
| will migrate up. This must be set.
|
*/
$config['migration_table'] = 'migrations';

/*
|--------------------------------------------------------------------------
| Auto Migrate To Latest
|--------------------------------------------------------------------------
|
| If this is set to TRUE when you load the migrations class and have
| $config['migration_enabled'] set to TRUE the system will auto migrate
| to your latest migration (whatever $config['migration_version'] is
| set to). This way you do not have to call migrations anywhere else
| in your code to have the latest migration.
|
*/
$config['migration_auto_latest'] = FALSE;

/*
|--------------------------------------------------------------------------
| Migrations version
|--------------------------------------------------------------------------
|
| This is used to set migration version that the file system should be on.
| If you run $this->migration->current() this is the version that schema will
| be upgraded / downgraded to.
|
*/
$config['migration_version'] = 0;

/*
|--------------------------------------------------------------------------
| Migrations Path
|--------------------------------------------------------------------------
|
| Path to your migrations folder.
| Typically, it will be within your application path.
| Also, writing permission is required within the migrations path.
|
*/
$config['migration_path'] = APPPATH . 'migrations/';

/* End of file migration.php */


text:
/*
Theme Name:         aek
Theme URI:          https://roots.io/sage/
Description:        Based on Sage
Version:            8.2.1
Author:             Roots
Author URI:         https://roots.io/

License:            MIT License
License URI:        http://opensource.org/licenses/MIT
*/