class WsRosterController < WsController
    require 'xmpp4r/roster'
    require 'xmpp4r/vcard'
    require 'RMagick'

    def initialize
        super

        @storages_arr = [:clients, :rosters]
        @storages_hash = [:link_roster_client, :my_presences, :presences]
    end

    def initialize_storage
        @storages_arr.each do |storage|
            connection_store[storage] = []
        end

        @storages_hash.each do |storage|
            connection_store[storage] = {}
        end
    end

    ##
    # Pripoj sa na jabber ucty.
    def connect
        initialize_storage()

        cookies = env['rack.request.cookie_hash'] # TODO: nahlasit bug na websocket-rails, lebo sa neda pristupit ku `cookies'
        cipher_key = cookies['key']
        cipher_iv = cookies['iv']

        credentials = User.crendentials_for_token(session[:token])

        clients = credentials.map do |tuple|
            decrypted_pass = Security::decrypt(tuple['pass'], cipher_key, cipher_iv)
            {jid: tuple['jid'], pass: decrypted_pass}
        end

        clients.each do |client|
            Thread.new do
                begin
                    client = Signin.try_login(client[:jid], client[:pass])
                    connection_store[:clients] << client
                    send_message 'app.client.connected', client.jid.strip.to_s
                    #Rails.logger.debug '!!!!! finished for ' + client_id.to_s
                rescue Signin::LoginError
                    #Rails.logger.debug '!!!!! finished FAILED for ' + client_id.to_s
                    send_message 'app.client.cannot_connect', client.jid.strip.to_s
                end
            end
        end
    end

    ##
    # Inicializuj roster so zoznamom ludi v nom.
    # Vrat zoznam ludi (ich JID).
    def init_roster
        all_jids = []

        connection_store[:clients].each do |client|
            roster = Jabber::Roster::Helper.new(client)

            connection_store[:rosters] << roster
            connection_store[:link_roster_client][roster] = client

            roster.get_roster()
            roster.wait_for_roster()

            roster.items.each do |jid, contact|
                all_jids << {
                    jid: jid.to_s,
                    belongsTo: client.jid.strip.to_s
                }
            end
        end

        trigger_success contacts: all_jids
    end

    ##
    # Stiahni vcard ludi v rosteri
    def start_fetching_vcards
        connection_store[:rosters].each do |roster|
            client = connection_store[:link_roster_client][roster]

            roster.items.each do |jid, contact|
                Thread.new do
                    vcard = get_vcard_info(client, jid.to_s)
                    send_message 'app.roster.vcard', jid: jid.to_s, vcard: vcard
                end
            end
        end
    end

    ##
    # Zacni pocuvat zmeny stavov v rosteri
    def start_polling_contacts_state
        start_polling_control_answer()

        connection_store[:rosters].each do |roster|
            start_polling_subscription(roster)
            start_polling_presence(roster)
            start_polling_friend_requests(roster)
        end
    end

    ##
    # Nastav ma ako online
    #
    # Musi sa zavolat az po start_polling_contacts_state, inak sa nemusia
    # zachytit stavy ostatnych v rosteri.
    def set_presence
        connection_store[:clients].each do |client|
            presence = Jabber::Presence.new.set_type(:available)
            client.send(presence)
            connection_store[:my_presences][client] = presence
        end
    end

    def remove_contact
        jid = message[:jid]
        client_jid = message[:client]

        found_roster_items = []
        connection_store[:rosters].each do |roster|
            roster_item = roster.find(jid)
            contact_obj = roster_item.first[1]

            if contact_obj
                if client_jid && connection_store[:link_roster_client][roster].jid.strip.to_s == client_jid
                    found_roster_items = [ contact_obj ]
                    break
                else
                    found_roster_items.push(contact_obj)
                end
            end
        end

        found_roster_items.each do |contact|
            contact.remove()
        end
    end

    ##
    # Ziskaj informacie o mne (meno, stav, status...)
    def myself
        # TODO: v pripade viacerych uctov zjednotit meno a stav
        vcard = {}
        jid = presence = ''

        connection_store[:clients].each do |client|
            vcard = get_vcard_info(client)
            jid = client.jid.strip.to_s
            presence = uniform_presence(connection_store[:my_presences][client].show)
        end

        trigger_success jid: jid, vcard: vcard, status: presence
    end

    def me_update_status
        status_message = message[:message]
        state = message[:state]

        xmpp_state = case state
                     when 'away' then :away
                     when 'dnd' then :dnd
                     else nil
        end

        presence = Jabber::Presence.new
        presence.show = xmpp_state
        presence.status = status_message

        connection_store[:clients].each do |client|
            client.send(presence)
        end
    end

    def me_update_vcard
        connection_store[:clients].each do |client|
            Thread.new do
                my_vcard = Jabber::Vcard::Helper.get(client)

                if message[:name]
                    my_vcard['FN'] = my_vcard['NICKNAME'] = message[:name]
                end

                if message[:avatar]
                    begin
                        im = Magick::Image::read_inline(message[:avatar]).first

                        type = im.format.downcase
                        allowed_types = %w(png jpeg pjpeg gif webp)
                        return unless allowed_types.include? type

                        im.resize_to_fit! 128,128
                        base64 = Base64.encode64(im.to_blob)

                        my_vcard['PHOTO/TYPE'] = 'image/' + type
                        my_vcard['PHOTO/BINVAL'] = base64
                    rescue
                        # ignored
                    end
                end

                Jabber::Vcard::Helper.set(client, my_vcard)
            end
        end
    end

    def disconnect
        connection_store[:clients] && connection_store[:clients].each do |client|
            client.close()
        end

        @storages_arr.each do |storage|
            connection_store.delete(storage)
        end

        @storages_hash.each do |storage|
            connection_store.delete(storage)
        end
    end

    def add_contact(jid = nil)
        new_jid = message[:jid] || jid

        if is_valid_jid? new_jid
            connection_store[:rosters].each do |roster|
                Thread.new do
                    roster.add(new_jid, nil, true)
                end
            end
        end
    end

    def answer_friend_request
        jid = message[:jid]
        answer = message[:answer]

        connection_store[:rosters].each do |roster|
            if answer
                roster.accept_subscription(jid)
                add_contact(jid)
            else
                roster.decline_subscription(jid)
            end
        end
    end

    private

    def select_most_online_status(jid_stripped)
        if connection_store[:presences][jid_stripped].nil? || connection_store[:presences][jid_stripped].empty?
            return uniform_presence(:offline)
        end

        statuses = { offline: 0, dnd: 1, away: 2, online: 3 }

        Hash[connection_store[:presences][jid_stripped].map do |jid, data|
            [ statuses[data[:status]], data[:status] ]
        end].max.second
    end

    def get_vcard_info(me, contact_jid = nil)
        vcard = Jabber::Vcard::Helper.get(me, contact_jid)

        { name: pull_name_from_vcard(vcard) || contact_jid,
          avatar: vcard['PHOTO/TYPE'] && ('data:' + vcard['PHOTO/TYPE'] + ';base64,' + vcard['PHOTO/BINVAL']) || ''
        }
    end

    def pull_name_from_vcard(vcard)
        vcard && (vcard['FN'] || vcard['NICKNAME'])
    end

    def uniform_presence(xmpp_presence)
        case xmpp_presence
            when :offline   then :offline
            when :away, :xa then :away
            when :dnd       then :dnd
            else                 :online
        end
    end

    def start_polling_control_answer
        connection_store[:clients].each do |client|
            client.add_message_callback do |message|
                if message.attribute('i_am_using_same_app')
                    connection_store[:presences][message.from.strip.to_s][message.from.to_s.to_sym][:multichat] = true

                    send_message 'app.roster.using_this_app',
                                 jid: message.from.strip.to_s
                elsif message.attribute('are_you_using_my_app')
                    client.send(MessageBuilder::control_answer(client.jid.to_s, message.from.to_s))
                end
            end
        end
    end

    def start_polling_presence(roster)
        roster.add_presence_callback do |roster_item, old_presence, new_presence|
            if new_presence.type == :unavailable
                result = {message: ''}

                unless connection_store[:presences][roster_item.jid.strip.to_s].nil?
                    connection_store[:presences][roster_item.jid.strip.to_s.to_sym].delete(new_presence.from.to_s)
                end

                client = connection_store[:link_roster_client][roster]
                # mozno treba vyhodit cloveka z multichatu, ak som jeho owner
                kick_from_all_multichats(client, new_presence.from)

                # mozno treba sa odpojit z multichatu, ak sa odpojil jeho owner
                kick_myself_from_multichat(client, new_presence.from)
            else
                status = uniform_presence(new_presence.show)
                result = {message: new_presence.status.to_s}

                if connection_store[:presences][roster_item.jid.strip.to_s.to_sym].nil?
                    connection_store[:presences][roster_item.jid.strip.to_s.to_sym] = Hash.new()
                end

                connection_store[:presences][roster_item.jid.strip.to_s][new_presence.from.to_s.to_sym] = {
                    status: status,
                    multichat: false,
                    priority: new_presence.priority
                }

                ask_if_using_this_app(connection_store[:link_roster_client][roster], new_presence.from.to_s)
            end

            result[:status] = select_most_online_status(roster_item.jid.strip.to_s)

            send_message 'app.roster.statusChanged',
                         jid: roster_item.jid.strip.to_s, status: result
        end
    end

    def start_polling_subscription(roster)
        roster.add_subscription_callback do |roster_item, stanza|
            # stan unsubscribe = niekto uz ma nesubscribuje
            # stan unsubscribed = niekto ma uz nesubscribuje a aj si ma vymazal
            # stan subscribed = niekto moze vidiet moj stav

            client = connection_store[:link_roster_client][roster]

            send_message 'app.roster.subscriptionChanged',
                         action: stanza.type,
                         jid: roster_item.jid.strip.to_s,
                         belongsTo: client.jid.strip.to_s
        end
    end

    def start_polling_friend_requests(roster)
        client = connection_store[:link_roster_client][roster]
        roster.add_subscription_request_callback do |roster_item, stanza|
            jid = stanza.from.to_s
            send_message 'app.roster.friendRequest',
                         jid: jid,
                         name: get_vcard_info(client, jid)[:name]
        end
    end

    def ask_if_using_this_app(client, contact)
        client.send(MessageBuilder::control_question(client.jid.to_s, contact))
    end

    def is_valid_jid?(jid)
        !! jid.scan(/^\w+@\w+\.\w+$/).first
    end
end