'use strict'


/**
 * Modules (Node.js)
 * @constant
 */
const fs = require('fs-extra')
const { Buffer } = require('buffer')
const { https } = require('follow-redirects')
const os = require('os')
const path = require('path')
const readline = require('readline')
const url = require('url')

/**
 * Modules (Electron)
 * @constant
 */
const electron = require('electron')
const { ipcRenderer, remote } = electron

/**
 * Modules (Third party)
 * @constant
 */
const dataUriToBuffer = require('data-uri-to-buffer')
const FileType = require('file-type/browser')
const fileUrl = require('file-url')
const getYoutubeId = require('get-youtube-id')
const { Howl, Howler } = require('howler')
const ICO = require('icojs')
const isDebug = require('@sidneys/is-env')('debug')
const Jimp = require('jimp')
const logger = require('@sidneys/logger')({ write: true })
const moment = require('moment')
const notificationProvider = remote.require('@sidneys/electron-notification-provider')
const opn = require('opn')
const shortid = require('shortid')
const dynamicThrottledQueue = require('dynamic-throttled-queue')
const _ = require('lodash')


/**
 * Modules (Local)
 * @constant
 */
const appManifest = remote.require('app/scripts/main-process/components/globals').appManifest
const appFilesystem = remote.require('app/scripts/main-process/components/globals').appFilesystem
const configurationManager = remote.require('app/scripts/main-process/managers/configuration-manager')
const pbSms = require('app/scripts/renderer-process/pushbullet/sms')


/**
 * Application
 * @constant
 * @default
 */
const appName = appManifest.name
const appTemporaryDirectory = appFilesystem.tempdir


/**
 * General Defaults
 * @constant
 * @default
 */
const recentPushesAmount = 5

/**
 * URL Defaults
 * @constant
 * @default
 */
const faviconEndpoint = 'https://pb-for-desktop-besticon.herokuapp.com/icon?fallback_icon_color=4AB367&formats=ico,png&size=1..120..200&url='
const pushbulletIconEndpoint = 'https://www.pushbullet.com/img/deviceicons/'
const youtubeThumbnailEndpoint = 'https://img.youtube.com/vi/'


/**
 * Notification Defaults & Globals
 * @constant
 * @default
 * @global
 */
const notificationDisplayInterval = 1000
const notificationIconSize = 176
const notificationFilterCommentTag = '//'
const notificationFilterDebugPrefix = '[FILTERED]'
const notificationQueue = dynamicThrottledQueue({ min_rpi: 1, interval: notificationDisplayInterval, evenly_spaced: true })


/**
 * Retrieve PushbulletLastNotificationTimestamp
 * @return {Number} - timestamp
 */
let retrievePushbulletLastNotificationTimestamp = () => configurationManager('pushbulletLastNotificationTimestamp').get()

/**
 * Store PushbulletLastNotificationTimestamp
 * @param {Number} timestamp - Timestamp
 * @return {undefined}
 */
let storePushbulletLastNotificationTimestamp = (timestamp) => configurationManager('pushbulletLastNotificationTimestamp').set(timestamp)

/**
 * Retrieve ShowAppBadgeCount
 * @return {Boolean} - Show
 */
let retrieveAppShowBadgeCount = () => configurationManager('appShowBadgeCount').get()

/**
 * Retrieve PushbulletHideNotificationBody
 * @return {Boolean} - Hide
 // */
let retrievePushbulletHideNotificationBody = () => configurationManager('pushbulletHideNotificationBody').get()

/**
 * Retrieve PushbulletSoundEnabled
 * @return {Boolean} - Enabled
 */
let retrievePushbulletSoundEnabled = () => configurationManager('pushbulletSoundEnabled').get()

/**
 * Retrieve PushbulletSmsEnabled
 * @return {Boolean} - Enabled
 */
let retrievePushbulletSmsEnabled = () => configurationManager('pushbulletSmsEnabled').get()

/**
 * Retrieve PushbulletSoundFilePath
 * @return {String} - Path
 */
let retrievePushbulletSoundFilePath = () => configurationManager('pushbulletSoundFilePath').get()

/**
 * Retrieve AppSoundVolume
 * @return {Number} - Volume
 */
let retrievePushbulletSoundVolume = () => configurationManager('pushbulletSoundVolume').get()

/**
 * Retrieve PushbulletNotificationFilterFilePath
 * @return {String} - Path
 */
let retrievePushbulletNotificationFilterFilePath = () => configurationManager('pushbulletNotificationFilterFilePath').get()


/**
 * Set application badge count
 * @param {Number} total - Number to set
 */
let updateBadge = (total) => {
    logger.debug('updateBadge')

    if (!retrieveAppShowBadgeCount()) { return }

    remote.app.badgeCount = total
}

/**
 * Play Sound File
 * @param {Function=} callback - Callback
 */
let playSoundFile = (callback = () => {}) => {
    logger.debug('playSoundFile')

    // Retrieve pushbulletSoundEnabled
    const pushbulletSoundEnabled = retrievePushbulletSoundEnabled()

    // Skip if not enabled
    if (!pushbulletSoundEnabled) { return }

    // Retrieve pushbulletSoundFilePath, pushbulletSoundVolume
    const pushbulletSoundFilePath = retrievePushbulletSoundFilePath()
    const pushbulletSoundVolume = retrievePushbulletSoundVolume()

    // Create file:// URL
    const url = fileUrl(pushbulletSoundFilePath)

    // Create Sound
    const sound = new Howl({
        volume: pushbulletSoundVolume,
        src: [ url ],
        autoplay: true,
        preload: true,
        loop: false
    })

    /** @listens sound:Event#loaderror */
    sound.on('loaderror', (id, error) => {
        logger.error('playSoundFile', 'sound#loaderror', id, error)

        // Callback
        callback(error)
    })

    /** @listens sound:Event#playerror */
    sound.on('playerror', (id, error) => {
        logger.error('playSoundFile', 'sound#playerror', id, error)

        // Callback
        callback(error)
    })

    /** @listens sound:Event#end */
    sound.on('end', (id) => {
        logger.debug('playSoundFile', 'sound#end', id)

        // Callback
        callback()
    })
}


/**
 * Generate Image for Notification
 * @param {Object} push - Push Object
 * @returns {String} - Image URL
 */
let generateNotificationImage = (push) => {
    logger.debug('generateNotificationImage')

    // Account Image
    let iconAccount
    const accountId = push.receiver_iden

    for (let account of window.pb.api.accounts.all) {
        if (account['iden'].startsWith(accountId)) {
            iconAccount = account.image_url
        }
    }

    // Grant Image
    let iconGrant
    const grantId = push.client_iden

    for (let grant of window.pb.api.grants.all) {
        if (grant['client']['iden'] === grantId) {
            iconGrant = grant['client']['image_url']
        }
    }

    // Device Image
    let iconDevice
    const deviceId = push.source_device_iden

    for (let device of window.pb.api.devices.all) {
        if (device.iden === deviceId) {
            iconDevice = `${pushbulletIconEndpoint}${device.icon}.png`
        }
    }

    // SMS Image
    let iconSms

    if (push.type === 'sms_changed') {
        iconSms = `${pushbulletIconEndpoint}phone.png`
    }

    // Chat Image
    let iconChat

    if (!!push.sender_email) {
        const target = window.pb.targets.by_email(push.sender_email)
        if (target && target.hasOwnProperty('image_url')) {
            iconChat = target.image_url
        }
    }

    // Mirroring Image
    let iconMirroring

    if (push.type === 'mirror') {
        iconMirroring = `data:image/jpeg;base64,${push.icon}`
    }

    // Link Image
    let iconLink

    if (push.type === 'link') {
        // Is YouTube URL?
        const youtubeId = getYoutubeId(push.url)
        if (youtubeId) {
            // Fetch YouTube Thumbnail
            iconLink = `${youtubeThumbnailEndpoint}${youtubeId}/hqdefault.jpg`
        } else {
            // Fetch Favicon
            iconLink = `${faviconEndpoint}${push.url}`
        }
    }

    // Image Fallbacks Sequence
    return iconLink || iconMirroring || iconChat || iconGrant || iconDevice || iconSms || iconAccount
}

/**
 * Create Note Push
 * @param {String} message - Message
 * @param {String=} email - Target E-Mail
 * @param {String} deviceId - Target Device Id
 * @param {function=} callback - Callback
 */
let createNotePush = (message, email, deviceId, callback = () => {}) => {
    logger.debug('createNotePush')

    window.pb.api.pushes.create({
        type: 'note',
        email: !!deviceId ? void 0 : email,
        device_iden: !!email ? void 0 : deviceId,
        title: message,
        body: message
    })

    // Callback
    callback(email || deviceId)
}

/**
 * Dismiss Push
 * @param {Pushbullet.Push} push - Push Object
 */
let dismissPush = (push) => {
    logger.debug('dismissPush')

    // direction: self
    if (push.direction === 'self') {
        if (!push.dismissed && !push.target_device_iden) {
            logger.debug('dismissPush', 'self', 'push.title:', push.title)
            window.pb.api.pushes.dismiss(push)
        }
    }

    // direction: incoming
    if (push.direction === 'incoming') {
        if (!push.dismissed) {
            logger.debug('dismissPush', 'incoming', 'push.title:', push.title)
            window.pb.api.pushes.dismiss(push)
        }
    }
}

/**
 * Parse strings, look for strings in tags (see https://goo.gl/ijKFPd)
 * @see https://goo.gl/ijKFPd
 * @param {String} message - Message String
 * @returns {Object} - Message Object
 */
let parsePush = (message) => {
    logger.debug('parsePush', message)

    // default
    let body = message
    let subtitle = message
    let title = message

    // Parse Push for Notification Formatting
    // [ Title ] [ Subtitle ] Body Text
    // characters for tag detection
    const tagStart = '['
    const tagEnd = ']'

    let tagList = title.match(new RegExp(`\\${tagStart}(.*?)\\${tagEnd}`, 'gi')) || []
    let titleList = title.match(new RegExp(`${tagStart}^${tagStart}\\${tagEnd}${tagEnd}+(?=${tagEnd})`, 'gi')) || []

    if (titleList.length > 0) {
        /** body */
        // remove all tags
        tagList.forEach((tag) => {
            body = body.replace(tag, '')
        })

        /** title */
        if (titleList.length > 1) {
            subtitle = _.startCase(_.toLower(titleList[0]))

            titleList.shift()
            title = titleList.join(` | `)
        }
    }

    return {
        body: body,
        subtitle: subtitle,
        title: title
    }
}

/**
 * Decorate Push objects
 * @param {Pushbullet.Push|SmsEphemeral|SmsChangeEphemeral|NotificationEphemeral|DismissalEphemeral|ClipboardEphemeral} push - Pushbullet Push
 * @returns {DecoratedPush} - Push Object
 */
let decoratePush = (push) => {
    logger.debug('decoratePush', push.type)

    // Copy Push Object
    const decoratedPush = Object.assign({}, push)

    switch (String(decoratedPush.type)) {
        // Link
        case 'link':
            decoratedPush.icon = generateNotificationImage(decoratedPush)

            if (!decoratedPush.body && !decoratedPush.title) {
                decoratedPush.title = decoratedPush.url
            }

            if (!decoratedPush.body && decoratedPush.title) {
                let parsed = parsePush(decoratedPush.title)

                decoratedPush.body = parsed.body
                decoratedPush.subtitle = parsed.subtitle
                decoratedPush.title = parsed.title
            }

            break
        // Note
        case 'note':
            decoratedPush.title = decoratedPush.title || decoratedPush.body
            decoratedPush.body = decoratedPush.body || decoratedPush.title
            decoratedPush.icon = generateNotificationImage(decoratedPush)
            //push.title = `Note | ${push.title}`

            break
        // File
        case 'file':
            decoratedPush.title = decoratedPush.title || decoratedPush.file_name
            decoratedPush.body = decoratedPush.body || decoratedPush.title
            decoratedPush.url = decoratedPush.file_url
            decoratedPush.icon = decoratedPush.image_url || generateNotificationImage(decoratedPush)
            //push.title = `File | ${push.title}`

            break
        // Mirror
        case 'mirror':
            if (decoratedPush.application_name && decoratedPush.title) {
                decoratedPush.title = `${decoratedPush.application_name} | ${decoratedPush.title}`
            } else if (decoratedPush.application_name && !decoratedPush.title) {
                decoratedPush.title = decoratedPush.application_name
            }

            decoratedPush.body = decoratedPush.body || decoratedPush.title
            decoratedPush.url = decoratedPush.file_url
            decoratedPush.icon = decoratedPush.image_url || generateNotificationImage(decoratedPush)

            break
        // SMS
        case 'sms_changed':
            if (decoratedPush.notifications.length === 0) { return }

            let sms = decoratedPush.notifications[0]
            let phonenumber = sms.title
            let text = sms.body
            let time = (new Date(0)).setUTCSeconds(sms.timestamp)

            decoratedPush.title = `SMS | ${phonenumber}`
            decoratedPush.body = `${text}${os.EOL}${moment(time).fromNow()}`
            decoratedPush.icon = decoratedPush.image_url || generateNotificationImage(decoratedPush)

            break
    }

    // Detect URLs in title
    let detectedUrl = (decoratedPush.title && decoratedPush.title.match(/\bhttps?:\/\/\S+/gi)) || []
    if (!decoratedPush.url && detectedUrl.length > 0) {
        decoratedPush.url = detectedUrl[0]
    }

    // Trim
    decoratedPush.title = decoratedPush.title && decoratedPush.title.trim()
    decoratedPush.body = decoratedPush.body && decoratedPush.body.trim()

    return decoratedPush
}

/**
 * Check ANY of multiple regular expression patterns matches a given string
 * @param {String} text - String to test
 * @param {Array} patternList - List of regular expression patterns
 * @returns {Boolean} - Yes/No
 */
let matchTextAgainstRegexList = (text, patternList) => {
    logger.debug('matchTextAgainstRegexList')

    // Test if any of the regular expression patterns match
    const isMatch = patternList.some((pattern) => {
        // Convert pattern to regex
        const patternRegex = new RegExp(pattern, 'i')

        // DEBUG
        // logger.debug('matchTextAgainstRegexList', 'patternRegex', patternRegex)

        return patternRegex.test(text)
    })

    // Returns true if any regex pattern was matched
    return Boolean(isMatch)
}

/**
 * Parse a file, interpret each line as regex pattern, and check against a list of texts to determine if any matches
 * @param {String} filterFilePath - Absolute path to filter file
 * @param {Array} textList - List of strings to check against filter
 * @param {Function=} callback - Callback
 */
let compareTextListAgainstFilterFile = (filterFilePath, textList, callback = () => {}) => {
    logger.debug('compareTextListAgainstFilterFile')

    // Initialize filter entry list
    let filterEntryList = []

    // Initialize filter file reader
    const reader = readline.createInterface({
        input: fs.createReadStream(filterFilePath)
    })

    // Filter file reader: read next line
    reader.on('line', (line) => {
        logger.debug('compareTextListAgainstFilterFile', 'readline#line')

        // Only add filter entry if it's not comment (starting with "//")
        if (!line.startsWith(notificationFilterCommentTag)) {
            filterEntryList.push(line)
        }
    })

    // Filter file reader: error
    reader.on('error', (error) => {
        logger.error('compareTextListAgainstFilterFile', 'reader', error)

        // Callback
        callback(error)
    })

    // Filter file reader: complete
    reader.on('close', () => {
        logger.debug('compareTextListAgainstFilterFile', 'readline#close')

        // Cleanup filter entries, removing empty
        filterEntryList = filterEntryList.filter(Boolean)

        // DEBUG
        // logger.debug('filter entries:')
        // filterEntryList.forEach(entry => logger.debug(entry))

        // Check if any filter entry matches any text
        const isFilterMatch = textList.some(text => matchTextAgainstRegexList(text, filterEntryList))

        // Callback
        callback(null, isFilterMatch)
    })
}

/**
 * Show Notification
 * @param {Object} notificationOptions - Notification Configuration
 * @param {Pushbullet.Push|Object=} push - Pushbullet Push
 */
let showNotification = (notificationOptions, push) => {
    logger.debug('showNotification')

    // Retrieve pushbulletNotificationFilterFilePath
    const pushbulletNotificationFilterFilePath = retrievePushbulletNotificationFilterFilePath()

    // Create Notification
    const notification = notificationProvider.create(notificationOptions)

    /** @listens notification#click */
    notification.on('click', () => {
        logger.debug('notification#click')

        // Open Notification URL
        if (notificationOptions.url) {
            opn(notificationOptions.url, { wait: false }).then(() => {
                logger.info('Opened Notification URL:', notificationOptions.url)
            })
        }

        // Dismiss within Pushbullet
        if (push) {
            dismissPush(push)
        }
    })

    /** @listens notification#close */
    notification.on('close', () => {
        logger.debug('notification#close')

        // Dismiss within Pushbullet
        if (push) {
            dismissPush(push)
        }
    })

    /** @listens notification#reply */
    notification.on('reply', (event, message) => {
        logger.debug('notification#reply')

        if (!!!message) {
            logger.warn('reply message was empty')

            return
        }

        // SMS Reply
        if (push.type === 'sms_changed') {
            pbSms.reply(message, push.source_device_iden, pbSms.getMessageThreadId(push), (target) => {
                logger.debug('reply message sent', 'to:', target)
            })
        }

        // Chat Reply
        if (push.type === 'note' || push.type === 'link' || push.type === 'file') {
            createNotePush(message, push.sender_email, null, (target) => {
                logger.debug('reply message sent', 'to:', target)
            })
        }

    })

    /** @listens notification#error */
    notification.on('error', (error) => {
        logger.error('notification#error', error)
    })

    /** @listens notification#show */
    notification.on('show', () => {
        logger.debug('notification#show')

        // Status
        logger.info('Showing Notification', notificationOptions.title)
    })

    // Notification Filter
    // Checks if notification title or body contain filtered terms
    compareTextListAgainstFilterFile(pushbulletNotificationFilterFilePath, [ notification.title, notification.body ], (error, isFiltered) => {
        // Filtered
        if (isFiltered) {
            logger.warn('Filtered:', notification.title)

            // DEBUG
            if (isDebug) {
                // Prefix Notification
                notification.title = `${notificationFilterDebugPrefix} ${notification.title}`
            } else {
                // Skip Notification
                return
            }
        }

        // Not filtered
        notificationQueue(() => {
            // Play Sound
            playSoundFile()
            // Show Notification
            notification.show()
        })
    })
}

/**
 * Aspect-Resize Image and Write to File
 * @param {ArrayBuffer|Uint8Array|Array|*} imageData - Image Data
 * @param {String} outputPath - Output Image file path
 * @param {Number} width - Output Image width
 * @param {Function=} callback - Callback
 */
let resizeWriteImage = (imageData, outputPath, width, callback = () => {}) => {
    logger.debug('resizeWriteImage')

    Jimp.read(imageData, (error, result) => {
        if (error) {
            logger.error('resizeWriteImage', 'jimp.read', error)

            // Callback
            callback(error)
            return
        }

        result.resize(width, Jimp.AUTO).write(outputPath, (error) => {
            if (error) {
                logger.error('resizeWriteImage', 'jimp.resize', error)

                callback(error)
                return
            }

            // Callback
            callback(null)
        })
    }).then((result) => {
        logger.debug('resizeWriteImage', 'result', result)
    })
}

/**
 * Create Notification from Push Objects
 * @param {Pushbullet.Push|SmsEphemeral|SmsChangeEphemeral|NotificationEphemeral|DismissalEphemeral|ClipboardEphemeral} push - Pushbullet Push
 */
let convertPushToNotification = (push) => {
    logger.debug('convertPushToNotification')

    // Copy Push Object
    const decoratedPush = decoratePush(push)

    // Create Options
    const notificationOptions = {
        body: decoratedPush.body,
        icon: decoratedPush.icon,
        subtitle: decoratedPush.subtitle,
        tag: decoratedPush.iden,
        title: decoratedPush.title,
        url: decoratedPush.url
    }

    // SMS Feature Enabled?
    if (decoratedPush.type === 'sms_changed') {
        const pushbulletSmsEnabled = retrievePushbulletSmsEnabled()
        if (!pushbulletSmsEnabled) { return }
    }

    // Hide Notification Body?
    const pushbulletHideNotificationBody = retrievePushbulletHideNotificationBody()
    if (pushbulletHideNotificationBody) {
        notificationOptions.body = ''
    }

    // Enable SMS Reply?
    if (decoratedPush.type === 'sms_changed') {
        notificationOptions.hasReply = true
        notificationOptions.replyPlaceholder = 'Your SMS Reply'
    }

    // Enable Chat Reply?
    if ((decoratedPush.type === 'note' || decoratedPush.type === 'link' || decoratedPush.type === 'file') && decoratedPush.direction === 'incoming' && !!decoratedPush.sender_email) {
        notificationOptions.hasReply = true
        notificationOptions.replyPlaceholder = 'Your Chat Reply'
    }

    // Icon: Prepare temporary Image path
    const temporaryImagePath = path.join(appTemporaryDirectory, `${appName}.push.${shortid.generate()}.png`)

    // Icon: Ensure temporary Image folder exists
    if (!fs.existsSync(appTemporaryDirectory)) {
        fs.mkdirpSync(appTemporaryDirectory)

        // Status
        logger.info('mkdirpSync', 'appTemporaryDirectory:', appTemporaryDirectory)
    }

    // Icon: Analyze Image URL
    const imageUrl = notificationOptions.icon || ''
    const imageProtocol = url.parse(imageUrl).protocol

    // Icon: Skip Image if protocol could not be detected
    if (!imageProtocol) {
        showNotification(notificationOptions, decoratedPush)

        // Return
        return
    }

    // Icon: Create Image by generating from Data URL
    if (imageProtocol === 'data:') {
        resizeWriteImage(dataUriToBuffer(imageUrl), temporaryImagePath, notificationIconSize, (error) => {
            if (error) { return }

            notificationOptions.icon = temporaryImagePath
            showNotification(notificationOptions, decoratedPush)
        })

        // Return
        return
    }

    // Icon: Create Image by downloading from normal URL into Buffer
    https
        .request(imageUrl, { timeout: 3000 }, (response) => {
            let fileBuffer = Buffer.alloc(0, 'Uint8Array')

            // Error
            response.on('error', (error) => {
                logger.error('icon file download', error)
            })

            // Download
            response.on('data', (chunk) => {
                // logger.debug('image download', 'data')
                fileBuffer = Buffer.concat([ fileBuffer, Buffer.from(chunk) ])
            })

            // Complete
            response.on('end', () => {
                logger.debug('icon file download', 'complete')

                // DEBUG
                // logger.debug('fileBuffer:')
                // console.dir(fileBuffer)

                // Icon: Detect File Type
                FileType.fromBuffer(fileBuffer)
                    .then((fileType) => {
                        logger.debug('file type', fileType)

                        // Icon: Detect Mimetype
                        const isICO = ICO.isICO(fileBuffer)
                        const isPNG = fileType.mime === 'image/png'
                        const isJPG = fileType.mime === 'image/jpg' || fileType.mime === 'image/jpeg'

                        // Icon: PNG/JPG Handling
                        if (isPNG || isJPG) {
                            resizeWriteImage(fileBuffer, temporaryImagePath, notificationIconSize, (error) => {
                                if (error) {
                                    logger.error('resizeWriteImage', error)

                                    // Return
                                    return
                                }

                                notificationOptions.icon = temporaryImagePath
                                showNotification(notificationOptions, decoratedPush)
                            })

                            return
                        }

                        // Icon: ICO Handling
                        if (isICO) {
                            // Lookup biggest image version inside .ICO
                            ICO.parse(fileBuffer, 'image/png').then(iconSet => {
                                const imageMaximum = iconSet[iconSet.length - 1]

                                // DEBUG
                                logger.debug('imageMaximum', imageMaximum)
                                logger.debug('iconSet:')
                                console.dir(iconSet)

                                resizeWriteImage(Buffer.from(imageMaximum.buffer), temporaryImagePath, notificationIconSize, (error) => {
                                    if (error) {
                                        logger.error('ICO.parse', 'resizeWriteImage', error)

                                        // Return
                                        return
                                    }

                                    notificationOptions.icon = temporaryImagePath
                                    showNotification(notificationOptions, decoratedPush)
                                })
                            })
                        }

                    })
                    .catch((error) => {
                        logger.warn('FileType.fromBuffer', error)

                        showNotification(notificationOptions, decoratedPush)
                    })
            })
        })
        .on('error', (error) => {
            logger.error('http image download', error)
        })
        .end()
}

/**
 * Test if Push is dismissed via API
 * @param {Object} push - Push Object
 * @returns {Boolean} - Yes / No
 */
let testIfPushIsIgnored = (push) => {
    //logger.debug('testIfPushIsIgnored')

    // Push inactive?
    if (push.hasOwnProperty('active')) {
        if (push.active === false) {
            return true
        }
    }

    // // Push directed at PB for Desktop and Push dismissed?
    // if (push.direction === 'self' && !!push.dismissed) {
    //     return true
    // }

    // Push is an SMS without enclosed notification?
    if (push.type === 'sms_changed' && !!!push.notifications.length) {
        return true
    }
}

/**
 * Get all Pushbullet Pushes sorted by recency (ascending)
 * @param {Number=} queueLimit - Limit result to fixed number
 * @returns {Array|undefined} List of Pushes
 */
let getRecentPushes = (queueLimit = 0) => {
    logger.debug('getRecentPushes')

    // List recent Pushes
    const recentPushesList = window.pb.api.pushes.all.filter((push) => !testIfPushIsIgnored(push))

    // Sort recent Pushes (by date)
    recentPushesList.sort((pushA, pushB) => {
        const dateA = pushA.created
        const dateB = pushB.created

        if (dateA < dateB) {
            return -1
        } else if (dateA > dateB) {
            return 1
        }
        return 0
    })

    // Return sliced list
    return recentPushesList.slice(recentPushesList.length - queueLimit, recentPushesList.length)
}

/**
 * Enqueue 1 + N Pushes
 * @param {Array|Object} pushes - Pushbullet push objects
 * @param {Boolean} ignoreDate - Ignore time of push, always show
 * @param {Boolean} updateBadgeCount - Update badge counter
 * @param {Function=} callback - Callback
 */
let enqueuePushes = (pushes, ignoreDate = false, updateBadgeCount = true, callback = () => {}) => {
    logger.debug('enqueuePushes')

    pushes = _.isArray(pushes) ? pushes : [ pushes ]

    if (pushes.length === 0) {
        logger.warn('enqueuePushes', 'pushes list was empty')

        // Callback
        callback(null, 0)
        return
    }

    // Retrieve pushbulletLastNotificationTimestamp
    const pushbulletLastNotificationTimestamp = retrievePushbulletLastNotificationTimestamp()

    // Init pushes variables
    let nextPushesList = pushes
    let notifyAfterTimestamp = pushbulletLastNotificationTimestamp || 0

    // Filter Pushes before notifyAfterTimestamp
    if (!!!ignoreDate) {
        nextPushesList = pushes.filter(push => push.created > notifyAfterTimestamp)
    }

    nextPushesList.forEach((/** Pushbullet.Push */ push, pushIndex, pushList) => {
        // Client Snoozing?
        const isSnoozing = (Date.now() < remote.getGlobal('snoozeUntil'))

        // Push ignored?
        const isIgnoredPush = testIfPushIsIgnored(push)

        if (!isSnoozing && !isIgnoredPush) {
            convertPushToNotification(push)
        }

        // Last Iteration?
        if (pushIndex !== pushList.length - 1) { return }

        // Store pushbulletLastNotificationTimestamp
        if (push.created > notifyAfterTimestamp) {
            storePushbulletLastNotificationTimestamp(push.created)
        }

        // Update AppIcon Badge
        if (updateBadgeCount) {
            updateBadge(remote.app.getBadgeCount() + nextPushesList.length)
        }

        // Callback
        callback(null, pushList.length)
    })
}

/**
 * Get all new pushes and show them (if any)
 * @param {Function=} callback - Callback
 * @public
 */
let enqueueRecentPushes = (callback = () => {}) => {
    logger.debug('enqueueRecentPushes')

    const pushesList = getRecentPushes(recentPushesAmount)

    enqueuePushes(pushesList, true, false, (error, count) => {
        if (error) {
            logger.error('enqueueRecentPushes', error)

            // Callback
            callback(error)
            return
        }

        // Callback
        callback(null, count)
    })
}

/**
 * Init
 */
let init = () => {
    logger.debug('init')

    // Configure Web Audio
    // https://github.com/goldfire/howler.js/issues/593
    Howler.autoSuspend = false
}


/**
 * @listens ipcRenderer:tray-close
 */
ipcRenderer.on('tray-close', () => {
    logger.debug('ipcRenderer#tray-close')
})

/**
 * @listens window:UIEvent#load
 */
window.addEventListener('load', () => {
    logger.debug('window#load')

    init()
})


/**
 * @exports
 */
module.exports = {
    enqueuePushes: enqueuePushes,
    enqueueRecentPushes: enqueueRecentPushes,
    updateBadge: updateBadge
}

/**
 * @typedef DecoratedPush
 * @mixes {Pushbullet.Push}
 */
