/*
 *             Twidere - Twitter client for Android
 *
 *  Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.mariotaku.twidere.util

import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.app.ActionBar
import android.app.Activity
import android.content.*
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
import android.net.Uri
import android.nfc.NfcAdapter
import android.nfc.NfcAdapter.CreateNdefMessageCallback
import android.os.BatteryManager
import android.os.Build
import android.os.Bundle
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.net.ConnectivityManagerCompat
import androidx.core.view.GravityCompat
import androidx.core.view.accessibility.AccessibilityEventCompat
import androidx.appcompat.app.AppCompatActivity
import android.text.TextUtils
import android.text.format.DateFormat
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.View
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import android.widget.Toast
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.getNullableTypedArray
import org.mariotaku.ktextension.toLocalizedString
import org.mariotaku.pickncrop.library.PNCUtils
import org.mariotaku.sqliteqb.library.AllColumns
import org.mariotaku.sqliteqb.library.Columns
import org.mariotaku.sqliteqb.library.Columns.Column
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.sqliteqb.library.Selectable
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.TwidereConstants.METADATA_KEY_EXTENSION_USE_JSON
import org.mariotaku.twidere.TwidereConstants.SHARED_PREFERENCES_NAME
import org.mariotaku.twidere.TwidereConstants.TAB_CODE_DIRECT_MESSAGES
import org.mariotaku.twidere.TwidereConstants.TAB_CODE_HOME_TIMELINE
import org.mariotaku.twidere.TwidereConstants.TAB_CODE_NOTIFICATIONS_TIMELINE
import org.mariotaku.twidere.annotation.CustomTabType
import org.mariotaku.twidere.annotation.ProfileImageSize
import org.mariotaku.twidere.constant.CompatibilityConstants.EXTRA_ACCOUNT_ID
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEY
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_ACCOUNT_KEYS
import org.mariotaku.twidere.constant.IntentConstants.INTENT_ACTION_PEBBLE_NOTIFICATION
import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
import org.mariotaku.twidere.constant.bandwidthSavingModeKey
import org.mariotaku.twidere.constant.defaultAccountKey
import org.mariotaku.twidere.constant.mediaPreviewKey
import org.mariotaku.twidere.model.ParcelableStatus
import org.mariotaku.twidere.model.ParcelableUserMention
import org.mariotaku.twidere.model.PebbleMessage
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers
import org.mariotaku.twidere.util.TwidereLinkify.PATTERN_TWITTER_PROFILE_IMAGES
import org.mariotaku.twidere.view.TabPagerIndicator
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.util.*
import java.util.regex.Pattern

object Utils {

    private val PATTERN_XML_RESOURCE_IDENTIFIER = Pattern.compile("res/xml/([\\w_]+)\\.xml")
    private val PATTERN_RESOURCE_IDENTIFIER = Pattern.compile("@([\\w_]+)/([\\w_]+)")

    private val HOME_TABS_URI_MATCHER = UriMatcher(UriMatcher.NO_MATCH)

    init {

        HOME_TABS_URI_MATCHER.addURI(CustomTabType.HOME_TIMELINE, null, TAB_CODE_HOME_TIMELINE)
        HOME_TABS_URI_MATCHER.addURI(CustomTabType.NOTIFICATIONS_TIMELINE, null, TAB_CODE_NOTIFICATIONS_TIMELINE)
        HOME_TABS_URI_MATCHER.addURI(CustomTabType.DIRECT_MESSAGES, null, TAB_CODE_DIRECT_MESSAGES)
    }


    fun announceForAccessibilityCompat(context: Context, view: View, text: CharSequence,
            cls: Class<*>) {
        val accessibilityManager = context
                .getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
        if (!accessibilityManager.isEnabled) return
        // Prior to SDK 16, announcements could only be made through FOCUSED
        // events. Jelly Bean (SDK 16) added support for speaking text verbatim
        // using the ANNOUNCEMENT event type.
        val eventType: Int
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            eventType = AccessibilityEvent.TYPE_VIEW_FOCUSED
        } else {
            eventType = AccessibilityEventCompat.TYPE_ANNOUNCEMENT
        }

        // Construct an accessibility event with the minimum recommended
        // attributes. An event without a class name or package may be dropped.
        val event = AccessibilityEvent.obtain(eventType)
        event.text.add(text)
        event.className = cls.name
        event.packageName = context.packageName
        event.setSource(view)

        // Sends the event directly through the accessibility manager. If your
        // application only targets SDK 14+, you should just call
        // getParent().requestSendAccessibilityEvent(this, event);
        accessibilityManager.sendAccessibilityEvent(event)
    }

    fun deleteMedia(context: Context, uri: Uri): Boolean {
        try {
            return PNCUtils.deleteMedia(context, uri)
        } catch (e: SecurityException) {
            return false
        }

    }

    fun sanitizeMimeType(contentType: String?): String? {
        if (contentType == null) return null
        when (contentType) {
            "image/jpg" -> return "image/jpeg"
        }
        return contentType
    }

    fun createStatusShareIntent(context: Context, status: ParcelableStatus): Intent {
        val intent = Intent(Intent.ACTION_SEND)
        intent.type = "text/plain"
        intent.putExtra(Intent.EXTRA_SUBJECT, IntentUtils.getStatusShareSubject(context, status))
        intent.putExtra(Intent.EXTRA_TEXT, IntentUtils.getStatusShareText(context, status))
        intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
        return intent
    }

    fun getAccountKeys(context: Context, args: Bundle?): Array<UserKey>? {
        if (args == null) return null
        if (args.containsKey(EXTRA_ACCOUNT_KEYS)) {
            return args.getNullableTypedArray(EXTRA_ACCOUNT_KEYS)
        } else if (args.containsKey(EXTRA_ACCOUNT_KEY)) {
            val accountKey = args.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY) ?: return emptyArray()
            return arrayOf(accountKey)
        } else if (args.containsKey(EXTRA_ACCOUNT_ID)) {
            val accountId = args.get(EXTRA_ACCOUNT_ID).toString()
            try {
                if (java.lang.Long.parseLong(accountId) <= 0) return null
            } catch (e: NumberFormatException) {
                // Ignore
            }

            val accountKey = DataStoreUtils.findAccountKey(context, accountId)
            args.putParcelable(EXTRA_ACCOUNT_KEY, accountKey)
            if (accountKey == null) return arrayOf(UserKey(accountId, null))
            return arrayOf(accountKey)
        }
        return null
    }

    fun getAccountKey(context: Context, args: Bundle?): UserKey? {
        return getAccountKeys(context, args)?.firstOrNull()
    }

    fun getReadPositionTagWithAccount(tag: String, accountKey: UserKey?): String {
        if (accountKey == null) return tag
        return "$accountKey:$tag"
    }

    fun formatSameDayTime(context: Context, timestamp: Long): String? {
        if (DateUtils.isToday(timestamp))
            return DateUtils.formatDateTime(context, timestamp,
                    if (DateFormat.is24HourFormat(context))
                        DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_24HOUR
                    else
                        DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_12HOUR)
        return DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_DATE)
    }

    fun formatToLongTimeString(context: Context, timestamp: Long): String? {
        val formatFlags = DateUtils.FORMAT_NO_NOON_MIDNIGHT or DateUtils.FORMAT_ABBREV_ALL or
                DateUtils.FORMAT_CAP_AMPM or DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME
        return DateUtils.formatDateTime(context, timestamp, formatFlags)
    }

    fun isComposeNowSupported(context: Context): Boolean {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return false
        return hasNavBar(context)
    }

    fun setLastSeen(context: Context, entities: Array<ParcelableUserMention>?, time: Long): Boolean {
        if (entities == null) return false
        var result = false
        for (entity in entities) {
            result = result or setLastSeen(context, entity.key, time)
        }
        return result
    }

    fun setLastSeen(context: Context, userKey: UserKey, time: Long): Boolean {
        val cr = context.contentResolver
        val values = ContentValues()
        if (time > 0) {
            values.put(CachedUsers.LAST_SEEN, time)
        } else {
            // Zero or negative value means remove last seen
            values.putNull(CachedUsers.LAST_SEEN)
        }
        val where = Expression.equalsArgs(CachedUsers.USER_KEY).sql
        val selectionArgs = arrayOf(userKey.toString())
        return cr.update(CachedUsers.CONTENT_URI, values, where, selectionArgs) > 0
    }


    fun getColumnsFromProjection(projection: Array<String>?): Selectable {
        if (projection == null) return AllColumns()
        val length = projection.size
        val columns = arrayOfNulls<Column>(length)
        for (i in 0 until length) {
            columns[i] = Column(projection[i])
        }
        return Columns(*columns)
    }

    fun getDefaultAccountKey(context: Context): UserKey? {
        val prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
        val accountKey = prefs[defaultAccountKey]
        val accountKeys = DataStoreUtils.getAccountKeys(context)

        return accountKeys.find { it.maybeEquals(accountKey) } ?: run {
            if (!accountKeys.contains(accountKey)) {
                return@run accountKeys.firstOrNull()
            }
            return@run null
        }
    }

    fun getInternalCacheDir(context: Context?, cacheDirName: String): File {
        if (context == null) throw NullPointerException()
        val cacheDir = File(context.cacheDir, cacheDirName)
        if (cacheDir.isDirectory || cacheDir.mkdirs()) return cacheDir
        return File(context.cacheDir, cacheDirName)
    }

    fun getExternalCacheDir(context: Context, cacheDirName: String, sizeInBytes: Long): File? {
        val externalCacheDir = try {
            context.externalCacheDir
        } catch (e: SecurityException) {
            null
        } ?: return null
        val cacheDir = File(externalCacheDir, cacheDirName)
        if (sizeInBytes > 0 && externalCacheDir.freeSpace < sizeInBytes / 10) {
            // Less then 10% space available
            return null
        }
        if (cacheDir.isDirectory || cacheDir.mkdirs()) return cacheDir
        return null
    }

    fun getLocalizedNumber(locale: Locale, number: Number): String {
        return number.toLocalizedString(locale)
    }

    fun getMatchedNicknameKeys(str: String, manager: UserColorNameManager): Array<String> {
        if (str.isEmpty()) return emptyArray()
        return manager.nicknames.filter { (_, value) ->
            val valueStr = value?.toString()?.takeIf(String::isNotEmpty) ?: return@filter false
            return@filter valueStr.startsWith(str, ignoreCase = true)
        }.map { it.key }.toTypedArray()
    }

    fun getOriginalTwitterProfileImage(url: String): String {
        val matcher = PATTERN_TWITTER_PROFILE_IMAGES.matcher(url)
        if (matcher.matches())
            return matcher.replaceFirst("$1$2/profile_images/$3/$4$6")
        return url
    }

    fun getQuoteStatus(context: Context?, status: ParcelableStatus): String? {
        if (context == null) return null
        var quoteFormat: String = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
                .getString(KEY_QUOTE_FORMAT, DEFAULT_QUOTE_FORMAT).orEmpty()
        if (TextUtils.isEmpty(quoteFormat)) {
            quoteFormat = DEFAULT_QUOTE_FORMAT
        }
        var result = quoteFormat.replace(FORMAT_PATTERN_LINK, LinkCreator.getStatusWebLink(status).toString())
        result = result.replace(FORMAT_PATTERN_NAME, status.user_screen_name)
        result = result.replace(FORMAT_PATTERN_TEXT, status.text_plain)
        return result
    }

    fun getResId(context: Context?, string: String?): Int {
        if (context == null || string == null) return 0
        var m = PATTERN_RESOURCE_IDENTIFIER.matcher(string)
        val res = context.resources
        if (m.matches()) return res.getIdentifier(m.group(2), m.group(1), context.packageName)
        m = PATTERN_XML_RESOURCE_IDENTIFIER.matcher(string)
        if (m.matches()) return res.getIdentifier(m.group(1), "xml", context.packageName)
        return 0
    }


    fun getTabDisplayOption(context: Context): String {
        val defaultOption = context.getString(R.string.default_tab_display_option)
        val prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
        return prefs.getString(KEY_TAB_DISPLAY_OPTION, defaultOption) ?: defaultOption
    }

    fun getTabDisplayOptionInt(context: Context): Int {
        return getTabDisplayOptionInt(getTabDisplayOption(context))
    }

    fun getTabDisplayOptionInt(option: String): Int {
        if (VALUE_TAB_DISPLAY_OPTION_ICON == option)
            return TabPagerIndicator.DisplayOption.ICON
        else if (VALUE_TAB_DISPLAY_OPTION_LABEL == option)
            return TabPagerIndicator.DisplayOption.LABEL
        return TabPagerIndicator.DisplayOption.BOTH
    }

    fun hasNavBar(context: Context): Boolean {
        val resources = context.resources ?: return false
        val id = resources.getIdentifier("config_showNavigationBar", "bool", "android")
        if (id > 0) {
            return resources.getBoolean(id)
        } else {
            // Check for keys
            return !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK) && !KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME)
        }
    }


    fun getTwitterProfileImageOfSize(url: String, size: String): String {
        if (ProfileImageSize.ORIGINAL == size) {
            return getOriginalTwitterProfileImage(url)
        }
        val matcher = PATTERN_TWITTER_PROFILE_IMAGES.matcher(url)
        if (matcher.matches()) {
            return matcher.replaceFirst("$1$2/profile_images/$3/$4_$size$6")
        }
        return url
    }

    @DrawableRes
    fun getUserTypeIconRes(isVerified: Boolean, isProtected: Boolean): Int {
        if (isVerified)
            return R.drawable.ic_user_type_verified
        else if (isProtected) return R.drawable.ic_user_type_protected
        return 0
    }

    @StringRes
    fun getUserTypeDescriptionRes(isVerified: Boolean, isProtected: Boolean): Int {
        if (isVerified)
            return R.string.user_type_verified
        else if (isProtected) return R.string.user_type_protected
        return 0
    }

    fun isBatteryOkay(context: Context?): Boolean {
        if (context == null) return false
        val app = context.applicationContext
        val filter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
        val intent: Intent?
        try {
            intent = app.registerReceiver(null, filter)
        } catch (e: Exception) {
            return false
        }

        if (intent == null) return false
        val plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0
        val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0).toFloat()
        val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100).toFloat()
        return plugged || level / scale > 0.15f
    }

    fun isMyAccount(context: Context, accountKey: UserKey): Boolean {
        val am = AccountManager.get(context)
        return AccountUtils.findByAccountKey(am, accountKey) != null
    }

    fun isMyAccount(context: Context, screenName: String): Boolean {
        val am = AccountManager.get(context)
        return AccountUtils.findByScreenName(am, screenName) != null
    }

    fun isMyRetweet(status: ParcelableStatus?): Boolean {
        return status != null && isMyRetweet(status.account_key, status.retweeted_by_user_key,
                status.my_retweet_id)
    }

    fun isMyRetweet(accountKey: UserKey, retweetedByKey: UserKey?, myRetweetId: String?): Boolean {
        return accountKey == retweetedByKey || myRetweetId != null
    }

    fun matchTabCode(uri: Uri): Int {
        return HOME_TABS_URI_MATCHER.match(uri)
    }


    @CustomTabType
    fun matchTabType(uri: Uri): String? {
        return getTabType(matchTabCode(uri))
    }

    @CustomTabType
    fun getTabType(code: Int): String? {
        when (code) {
            TAB_CODE_HOME_TIMELINE -> {
                return CustomTabType.HOME_TIMELINE
            }
            TAB_CODE_NOTIFICATIONS_TIMELINE -> {
                return CustomTabType.NOTIFICATIONS_TIMELINE
            }
            TAB_CODE_DIRECT_MESSAGES -> {
                return CustomTabType.DIRECT_MESSAGES
            }
        }
        return null
    }


    fun setNdefPushMessageCallback(activity: Activity, callback: CreateNdefMessageCallback): Boolean {
        try {
            val adapter = NfcAdapter.getDefaultAdapter(activity) ?: return false
            adapter.setNdefPushMessageCallback(callback, activity)
            return true
        } catch (e: SecurityException) {
            Log.w(LOGTAG, e)
        }

        return false
    }

    fun getInsetsTopWithoutActionBarHeight(context: Context, top: Int): Int {
        val actionBarHeight: Int
        if (context is AppCompatActivity) {
            actionBarHeight = getActionBarHeight(context.supportActionBar)
        } else if (context is Activity) {
            actionBarHeight = getActionBarHeight(context.actionBar)
        } else {
            return top
        }
        if (actionBarHeight > top) {
            return top
        }
        return top - actionBarHeight
    }

    fun restartActivity(activity: Activity?) {
        if (activity == null) return
        val enterAnim = android.R.anim.fade_in
        val exitAnim = android.R.anim.fade_out
        activity.finish()
        activity.overridePendingTransition(enterAnim, exitAnim)
        activity.startActivity(activity.intent)
        activity.overridePendingTransition(enterAnim, exitAnim)
    }

    internal fun isMyStatus(status: ParcelableStatus): Boolean {
        if (isMyRetweet(status)) return true
        return status.account_key.maybeEquals(status.user_key)
    }

    fun showMenuItemToast(v: View, text: CharSequence, isBottomBar: Boolean) {
        val screenPos = IntArray(2)
        val displayFrame = Rect()
        v.getLocationOnScreen(screenPos)
        v.getWindowVisibleDisplayFrame(displayFrame)
        val width = v.width
        val height = v.height
        val screenWidth = v.resources.displayMetrics.widthPixels
        val cheatSheet = Toast.makeText(v.context.applicationContext, text, Toast.LENGTH_SHORT)
        if (isBottomBar) {
            // Show along the bottom center
            cheatSheet.setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL, 0, height)
        } else {
            // Show along the top; follow action buttons
            cheatSheet.setGravity(Gravity.TOP or GravityCompat.END, screenWidth - screenPos[0] - width / 2, height)
        }
        cheatSheet.show()
    }

    internal fun getMetadataDrawable(pm: PackageManager?, info: ActivityInfo?, key: String?): Drawable? {
        if (pm == null || info == null || info.metaData == null || key == null || !info.metaData.containsKey(key))
            return null
        return pm.getDrawable(info.packageName, info.metaData.getInt(key), info.applicationInfo)
    }

    internal fun isExtensionUseJSON(info: ResolveInfo?): Boolean {
        if (info == null || info.activityInfo == null) return false
        val activityInfo = info.activityInfo
        if (activityInfo.metaData != null && activityInfo.metaData.containsKey(METADATA_KEY_EXTENSION_USE_JSON))
            return activityInfo.metaData.getBoolean(METADATA_KEY_EXTENSION_USE_JSON)
        val appInfo = activityInfo.applicationInfo ?: return false
        return appInfo.metaData != null && appInfo.metaData.getBoolean(METADATA_KEY_EXTENSION_USE_JSON, false)
    }

    fun getActionBarHeight(actionBar: ActionBar?): Int {
        if (actionBar == null) return 0
        val context = actionBar.themedContext
        val tv = TypedValue()
        val height = actionBar.height
        if (height > 0) return height
        if (context.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
            return TypedValue.complexToDimensionPixelSize(tv.data, context.resources.displayMetrics)
        }
        return 0
    }

    fun getActionBarHeight(actionBar: androidx.appcompat.app.ActionBar?): Int {
        if (actionBar == null) return 0
        val context = actionBar.themedContext
        val tv = TypedValue()
        val height = actionBar.height
        if (height > 0) return height
        if (context.theme.resolveAttribute(R.attr.actionBarSize, tv, true)) {
            return TypedValue.complexToDimensionPixelSize(tv.data, context.resources.displayMetrics)
        }
        return 0
    }

    fun getNotificationId(baseId: Int, accountKey: UserKey?): Int {
        var result = baseId
        result = 31 * result + (accountKey?.hashCode() ?: 0)
        return result
    }

    @SuppressLint("InlinedApi")
    fun isCharging(context: Context?): Boolean {
        if (context == null) return false
        val intent = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) ?: return false
        val plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
        return plugged == BatteryManager.BATTERY_PLUGGED_AC
                || plugged == BatteryManager.BATTERY_PLUGGED_USB
                || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
    }

    fun isMediaPreviewEnabled(context: Context, preferences: SharedPreferences): Boolean {
        if (!preferences[mediaPreviewKey])
            return false
        val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        return !ConnectivityManagerCompat.isActiveNetworkMetered(cm) || !preferences[bandwidthSavingModeKey]
    }

    /**
     * Send Notifications to Pebble smart watches

     * @param context Context
     * *
     * @param title   String
     * *
     * @param message String
     */
    fun sendPebbleNotification(context: Context, title: String?, message: String) {
        val appName: String

        if (title == null) {
            appName = context.getString(R.string.app_name)
        } else {
            appName = "${context.getString(R.string.app_name)} - $title"
        }

        if (TextUtils.isEmpty(message)) return
        val prefs = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)

        if (prefs.getBoolean(KEY_PEBBLE_NOTIFICATIONS, false)) {

            val messages = ArrayList<PebbleMessage>()
            messages.add(PebbleMessage(appName, message))

            val intent = Intent(INTENT_ACTION_PEBBLE_NOTIFICATION)
            intent.putExtra("messageType", "PEBBLE_ALERT")
            intent.putExtra("sender", appName)
            intent.putExtra("notificationData", JsonSerializer.serializeList(messages, PebbleMessage::class.java))

            context.applicationContext.sendBroadcast(intent)
        }

    }

    fun copyStream(input: InputStream, output: OutputStream, length: Int) {
        val buffer = ByteArray(1024)
        var bytesRead: Int = 0
        do {
            val read = input.read(buffer)
            if (read == -1) {
                break
            }
            output.write(buffer, 0, read)
            bytesRead += read
        } while (bytesRead <= length)
    }
}
