/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.fbui.textlayoutbuilder

import android.text.Layout
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.AbsoluteSizeSpan
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.facebook.testing.screenshot.Screenshot
import com.facebook.testing.screenshot.ScreenshotRunner
import com.facebook.testing.screenshot.ViewHelpers
import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ScreenshotTest {
  companion object {
    const val DENSITY = 2f // 320dpi

    val paragraph =
        """
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut rhoncus, odio a hendrerit
    porttitor, tellus purus rhoncus massa, sit amet varius urna arcu non elit. Quisque rutrum
    lacinia tempus. Mauris quis sapien nulla. Nam iaculis nec nibh eget posuere. Quisque sodales
    interdum risus, eget commodo augue interdum ac. Integer libero eros, pellentesque eu maximus
    at, tempus eu arcu. Duis sed elit auctor, imperdiet neque quis, viverra augue. In iaculis
    eleifend arcu quis vulputate. Aliquam lorem ligula, condimentum sed odio non, aliquam blandit
    turpis. Nullam vitae egestas leo, nec blandit dui. Morbi at ultrices justo, eu tempus lorem.
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tempus ante et sapien vehicula,
    id ornare enim pharetra. Vestibulum interdum erat lorem, a consectetur ex rhoncus eget.
    """
            .trimIndent()

    @BeforeClass
    @JvmStatic
    fun before() {
      ScreenshotRunner.onCreate(
          InstrumentationRegistry.getInstrumentation(), InstrumentationRegistry.getArguments())
    }

    @AfterClass
    @JvmStatic
    fun after() {
      ScreenshotRunner.onDestroy()
    }
  }

  @Test
  fun testDefault() {
    val context = InstrumentationRegistry.getTargetContext()
    val layout = TextLayoutBuilder().setText(paragraph).setDensity(DENSITY).build()!!
    val view = TestView(context, layout)

    ViewHelpers.setupView(view)
        .setExactWidthPx(layout.width)
        .setExactHeightPx(layout.height)
        .layout()
    Screenshot.snap(view).record()
  }

  @Test
  fun testAbsoluteSizeSpan() {
    val context = InstrumentationRegistry.getTargetContext()
    val stringBuilder = SpannableStringBuilder("BIG TEXT half size text")
    stringBuilder.setSpan(AbsoluteSizeSpan(24, true), 0, 8, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

    val layout =
        TextLayoutBuilder()
            .setText(stringBuilder)
            .setTextSize(dp(12f))
            .setDensity(DENSITY)
            .build()!!
    val view = TestView(context, layout)

    ViewHelpers.setupView(view)
        .setExactWidthPx(layout.width)
        .setExactHeightPx(layout.height)
        .layout()
    Screenshot.snap(view).record()
  }

  @Test
  fun testCenterAlignment() {
    val context = InstrumentationRegistry.getTargetContext()
    val layout =
        TextLayoutBuilder()
            .setText(paragraph)
            .setTextSize(dp(12f))
            .setAlignment(Layout.Alignment.ALIGN_CENTER)
            .build()!!
    val view = TestView(context, layout)

    ViewHelpers.setupView(view)
        .setExactWidthPx(layout.width)
        .setExactHeightPx(layout.height)
        .layout()
    Screenshot.snap(view).record()
  }

  @Test
  fun testLineHeight() {
    val context = InstrumentationRegistry.getTargetContext()
    val layout =
        TextLayoutBuilder()
            .setText(paragraph)
            .setTextSize(dp(12f))
            .setLineHeight(dp(20f).toFloat())
            .build()!!
    val view = TestView(context, layout)

    ViewHelpers.setupView(view)
        .setExactWidthPx(layout.width)
        .setExactHeightPx(layout.height)
        .layout()
    Screenshot.snap(view).record()
  }

  private fun dp(value: Float): Int {
    return (value * DENSITY + 0.5f).toInt()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.fbui.textlayoutbuilder

import android.content.Context
import android.graphics.Canvas
import android.text.Layout
import android.view.View

class TestView(context: Context, private val layout: Layout) : View(context) {
  override fun onDraw(canvas: Canvas) {
    layout.draw(canvas)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.fbui.textlayoutbuilder.sample

import android.content.Context
import android.os.Bundle
import android.text.Layout
import android.util.TypedValue
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder

class MainActivity : AppCompatActivity() {
  private lateinit var parent: LinearLayout

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    parent = findViewById(R.id.parent)

    addSample { TextLayoutBuilder().setText("Hello, world!").setTextSize(20f.dp(this)).build() }
  }

  private fun addSample(block: () -> Layout?) {
    val layout = block() ?: return
    parent.addView(
        SampleView(this, layout).apply {
          layoutParams = LinearLayout.LayoutParams(layout.width, layout.height)
        })
  }

  private fun Float.dp(context: Context): Int {
    return Math.round(
        TypedValue.applyDimension(
            TypedValue.COMPLEX_UNIT_DIP, this, context.resources.displayMetrics))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.fbui.textlayoutbuilder.sample

import android.content.Context
import android.graphics.Canvas
import android.text.Layout
import android.view.View

class SampleView(context: Context, private val layout: Layout) : View(context) {
  override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    layout.draw(canvas)
  }
}


package com.example.freecodecamp

import io.flutter.embedding.android.FlutterActivity

class MainActivity: FlutterActivity()


package org.freecodecamp.phone_ide

import io.flutter.embedding.android.FlutterActivity

class MainActivity: FlutterActivity() {
}


package com.example.example

import io.flutter.embedding.android.FlutterActivity

class MainActivity: FlutterActivity() {
}


package dynamicProgramming

import kotlin.math.min

/*
 * This is a dynamic programming implementation of assembly line scheduling.
 * @Params n- number of stations, a- service time at each station, t- line switch time from each station, e- entry time, x- exit time
 * @Return minimum time to cross n stations
 * */
fun AssemblyLineScheduling(n:Int,a:Array<IntArray>,t:Array<IntArray>,e:IntArray,x:IntArray):Int {

    var L1:IntArray =  IntArray(n);
    var L2:IntArray = IntArray(n) ;
    var i=0;

    L1[0] = e[0] + a[0][0];

    L2[0] = e[1] + a[1][0];

    for(i in 1..n-1)
    {
        L1[i] = min(L1[i - 1] + a[0][i],
                L2[i - 1] + t[1][i] + a[0][i]);
        L2[i] = min(L2[i - 1] + a[1][i],
                L1[i - 1] + t[0][i] + a[1][i]);
    }

    return min(L1[n-1] + x[0],
            L2[n-1] + x[1]);

}


package dynamicProgramming

fun min(x: Int, y: Int, z: Int): Int {
    if (x <= y && x <= z) return x
    return if (y <= x && y <= z) y else z
}

/*
 * This is a dynamic programming implementation of edit distance.
 * @Params str1,str2 - strings to be compared
 * @Return minimum number of operations to convert one string to another
 * */
fun editDistance(str1: String, str2: String): Int {
    val dp = Array(str1.length + 1) { IntArray(str2.length + 1) }

    for (i in 0..str1.length) {
        for (j in 0..str2.length) {
            if (i == 0) dp[i][j] = j
            else if (j == 0) dp[i][j] = i
            else if (str1[i - 1]
                    == str2[j - 1]) dp[i][j] = dp[i - 1][j - 1] else dp[i][j] = (1
                    + min(dp[i][j - 1],
                    dp[i - 1][j],
                    dp[i - 1][j - 1]))
        }
    }
    return dp[str1.length][str2.length]
}


package dynamicProgramming

tailrec fun factorial(n: Int, accumulator: Int = 1): Int {
    if (n == 0) {
        return accumulator
    }

    return factorial(n - 1, accumulator * n)
}


package dynamicProgramming

fun Int.isPrime() = this > 1 && (2..(this / 2)).all { this % it != 0 }


package dynamicProgramming

import kotlin.math.max

/* This algorithm is Longest Common Subsequence

 * @param  s1,s2 - strings to be compared
 * @return Length of longest common subsequence between two strings.
   Eg., for stage and sale it is 3.(sae is the longest common subsequence)
 */

fun lcs(s1: String, s2: String): Int {
    val L = Array(s1.length + 1) { IntArray(s2.length + 1) }

    for (i in 0..s1.length) {
        for (j in 0..s2.length) {
            if (i == 0 || j == 0) L[i][j] = 0
            else if (s1[i - 1] == s2[j - 1]) L[i][j] = L[i - 1][j - 1] + 1
            else L[i][j] = max(L[i - 1][j], L[i][j - 1])
        }
    }
    return L[s1.length][s2.length]
}


package dynamicProgramming

/*
 * This is a dynamic programming implementation of matrix chain multiplication.
 * @Params p- array which represents the chain of matrices such that the ith matrix Ai is of dimension p[i-1] x p[i]
 * @Return minimum number of multiplications needed to multiply the chain
 * */
fun MatrixChainOrder(p: IntArray): Int {

    val m = Array(p.size) { IntArray(p.size) }
    var i: Int
    var j: Int
    var k: Int
    var L: Int
    var q: Int

    i = 1
    while (i < p.size) {
        m[i][i] = 0
        i++
    }

    L = 2
    while (L < p.size) {
        i = 1
        while (i < p.size - L + 1) {
            j = i + L - 1
            if (j == p.size) {
                i++
                continue
            }
            m[i][j] = Int.MAX_VALUE
            k = i
            while (k <= j - 1) {

                q = (m[i][k] + m[k + 1][j]
                        + p[i - 1] * p[k] * p[j])
                if (q < m[i][j]) m[i][j] = q
                k++
            }
            i++
        }
        L++
    }
    return m[1][p.size - 1]
}


package dynamicProgramming

import kotlin.math.max

/*
 * This is a dynamic programming implementation of rod cutting problem.
 * @Params price- array of prices of all possible cut sizes of rod of array length
 * @Return maximum value obtained by cutting rod
 * */
fun rodCutting(price: IntArray): Int {
    val value = IntArray(price.size + 1)
    value[0] = 0

    for (i in 1..price.size) {
        var maxVal = Int.MIN_VALUE
        for (j in 0 until i) maxVal = max(maxVal,
                price[j] + value[i - j - 1])
        value[i] = maxVal
    }
    return value[price.size]
}


package dynamicProgramming

import kotlin.math.max

/* This algorithm is Unbounded Knapsack Problem

 * @param W- capacity, weight- array of weights, value- array of value, n- size of weight and value array
 * @return Maximum value with repetition of items that can be obtained
 */

fun unboundedKnapsack(W: Int, wt: IntArray, v: IntArray, n: Int): Int {

    if (W < 0) return 0

    val dp = IntArray(W + 1)

    for (i in 0..W) {
        for (j in 0 until n) {
            if (wt[j] <= i) {
                dp[i] = max(dp[i], dp[i - wt[j]] + v[j])
            }
        }
    }

    for (i in 0..W) {
        print(dp[i])
        print(" ")
    }
    println(dp[W])
    return dp[W]
}


package dynamicProgramming

import java.util.Comparator
import java.util.Collections
import kotlin.math.max

/* This algorithm is Weighted Job Scheduling

 * @param jobs - list of jobs with start time, end time and profit
 * @return Maximum profit that can be obtained
 */

class Job(var start: Int, var finish: Int, var profit: Int)

fun findLastNonConflictingJob(jobs: List<Job>, n: Int): Int {
    for (i in n - 1 downTo 0) {
        if (jobs[i].finish <= jobs[n].start) {
            return i
        }
    }
    return -1
}

fun findMaxProfit(jobs: List<Job>): Int {
    Collections.sort(jobs, Comparator.comparingInt { x: Job -> x.finish })

    val n = jobs.size

    val maxProfit = IntArray(n)

    maxProfit[0] = jobs[0].profit

    for (i in 1 until n) {
        val index = findLastNonConflictingJob(jobs, i)

        var incl = jobs[i].profit
        if (index != -1) {
            incl += maxProfit[index]
        }

        maxProfit[i] = max(incl, maxProfit[i - 1])
    }
    return maxProfit[n - 1]
}


package dynamicProgramming

import kotlin.math.max

/* This algorithm is Zero One Knapsack Problem

 * @param W- capacity, weight- array of weights, value- array of value, n- size of weight and value array
 * @return Maximum value that can be obtained
 */

fun zerooneknapsack(W:Int, weight: IntArray, values:IntArray, n:Int):Int{
    if (W<0) return 0
    val k = Array(n+1) {IntArray(W+1) {0} }
    for (i in 0..n)
    {
        for (j in 0..W)
        {
            if (i == 0 || j == 0)
                k[i][j] = 0
            else if (weight[i - 1] <= j)
                k[i][j] = max(values[i - 1]
            + k[i - 1][j - weight[i - 1]],
            k[i - 1][j])
            else
            k[i][j] = k[i - 1][j]
        }
    }
    return k[n][W]
}


package dynamic_programming

/**
 * Palindrome Partitioning Algorithm
 *
 * You are given a string as input, and task is to find the minimum number of partitions to be made,
 * in the string sot that the resulting strings are all palindrome
 * eg. s = "nitik"
 * string s can be partitioned as n | iti | k into 3 palindromes, thus the number of partions are 2
 * Time Complexity = O(n^2)
 *
 * */

/**
 * @param String is the string to be checked
 * @param Int is the starting index of the string in consideration
 * @param Int is the ending index of the string in consideration
 * @return whether string is a palindrome or not
 **/
fun isPalindrome(string: String, i: Int, j: Int): Boolean {
    for (l in 0..(j - i) / 2) {
        if (string[l + i] != string[j - l]) {
            return false
        }
    }
    return true
}

/**
 * @param String is the string to be checked
 * @param Int is the starting index of the string in consideration
 * @param Int is the ending index of the string in consideration
 * @return minimum number of partitions required
 **/
fun palindromePartition(string: String, i: Int, j: Int): Int {
    if (i >= j) {
        return 0
    }
    if (isPalindrome(string, i, j)) {
        return 0
    }
    if (dp[i][j] != -1) {
        return dp[i][j]
    }
    var mn = Int.MAX_VALUE
    for (k in i until j) {
        val temp: Int = palindromePartition(string, i, k) + palindromePartition(string, k + 1, j) + 1
        if (temp < mn) {
            mn = temp
        }
    }
    dp[i][j] = mn
    return dp[i][j]
}

/**
 * memoization table
 **/
lateinit var dp: Array<Array<Int>>

/**
 * @param String the string on which algorithm is to be operated
 */
fun initialize(string: String): Int {
    dp = Array(string.length) { Array(string.length) { -1 } }
    return palindromePartition(string, 0, string.length - 1)
}


package math

import java.lang.IllegalArgumentException
import kotlin.math.pow

/**
 * Calculate the area of a rectangle
 *
 * @param length length of rectangle
 * @param width width of rectangle
 * @return area of given rectangle
 */
fun areaOfARectangle(length: Double, width: Double) =  when {
    length > 0 && width > 0 -> length * width
    else -> throw IllegalArgumentException("Length and Width must be positive")
}

/**
 * Calculate the area of a square
 *
 * @param sideLength side length of square
 * @return area of given square
 */
fun areaOfASquare(sideLength: Double) =
    when {
        sideLength > 0 -> sideLength * sideLength
        else -> throw IllegalArgumentException("Side Length must be positive")
    }

/**
 * Calculate the area of a triangle
 *
 * @param base base of triangle
 * @param height height of triangle
 * @return area of given triangle
 */
fun areaOfATriangle(base: Double, height: Double) =
    when {
        base > 0 && height > 0 -> base * height / 2
        else -> throw IllegalArgumentException("Base and Height must be positive")
    }

/**
 * Calculate the area of a circle
 *
 * @param radius radius of circle
 * @return area of given circle
 */
fun areaOfACircle(radius: Double) =
    when {
        radius > 0 -> Math.PI * radius.pow(2.0)
        else -> throw IllegalArgumentException("Radius must be positive")
    }


package math

/**
 * Calculate the average of a list of Double
 *
 * @param numbers array to store numbers
 * @return average of given numbers
 */
fun average(numbers: Array<Double>): Double {
    var sum = 0.0
    for (number in numbers) {
        sum += number
    }
    return sum / numbers.size
}

/**
 * Calculate the average of a list of Int
 *
 * @param numbers array to store numbers
 * @return average of given numbers
 */
fun average(numbers: Array<Int>) : Int {
    var sum = 0
    for (number in numbers) {
        sum += number
    }
    return sum / numbers.size
}


package mathematics

import java.security.InvalidParameterException

/**
 * Calculates the factorial of a natural number greater than or equal to 0 recursively.
 * @param number The number of which to calculate the factorial.
 * @return The factorial of the number passed as parameter.
 */
fun getFactorial(number: Long): Long {
    if (number < 0L) {
        throw InvalidParameterException("The number of which to calculate the factorial must be greater or equal to zero.")
    } else return when (number) {
        0L -> 1
        1L -> number
        else -> number * getFactorial(number - 1)
    }
}


package math

import java.util.Arrays.sort

/**
 * Calculates the median of an array of Int
 *
 * @param values is an array of Int
 * @return the middle number of the array
 */
fun median(values: IntArray): Double {
    sort(values)
    return when {
        values.size % 2 == 0 -> getHalfwayBetweenMiddleValues(values)
        else -> getMiddleValue(values)
    }
}

/**
 * Calculates the middle number of an array when the size is an even number
 *
 * @param values is an array of Int
 * @return the middle number of the array
 */
private fun getHalfwayBetweenMiddleValues(values: IntArray): Double {
    val arraySize = values.size
    val sumOfMiddleValues = (values[arraySize / 2] + values[(arraySize / 2) - 1 ])
    return sumOfMiddleValues / 2.0
}

/**
 * Calculates the middle number of an array when the size is an odd number
 *
 * @param values is an array of Int
 * @return the middle number of the array
 */
private fun getMiddleValue(values: IntArray): Double {
    return values[values.size / 2].toDouble()
}


package math
/**
 * Approach 1: Brute Force
 *
 * Complexity Analysis:
 *
 * Time complexity: O(n^2)
 * Space complexity: O(1)
 *
 * Try all the pairs in the array and see if any of them add up to the target number.
 * @param nums Array of integers.
 * @param target Integer target.
 * @return Indices of the two numbers such that they add up to target.
 */
fun twoSum(nums: IntArray, target: Int): IntArray{
    for (index1 in nums.indices) {
        val startIndex = index1 + 1
        for (index2 in startIndex..nums.lastIndex) {
            if (nums[index1] + nums[index2] == target) {
                return intArrayOf(index1, index2)
            }
        }
    }
    return intArrayOf(0,1)

}


package other

import java.text.Normalizer
import java.util.regex.Pattern

/**
 * A palindrome is a word, number, phrase, or other sequence
 * of characters which reads the same backward as forward,
 * such as madam, racecar. There are also numeric palindromes,
 * particularly date/time stamps using short digits 11/11/11 11:11
 * and long digits 02/02/2020
 *
 * This function
 * @param text The text to be checked if it is a palindrome
 * @return return true if the text is a Palindrome
 */
fun isPalindrome(text: String): Boolean {

    val normalizedText = text.normalize()
    for(i in normalizedText.indices)
        if(normalizedText[i] != normalizedText[normalizedText.length - (i + 1)])
            return false
    return true;
}

fun String.normalize(): String {
    val nfdNormalizedString = Normalizer.normalize(this, Normalizer.Form.NFD)
    return Pattern
        .compile("\\p{InCombiningDiacriticalMarks}+")
        .matcher(nfdNormalizedString)
        .replaceAll("")
        .toLowerCase()
        .replace(" ", "")

}


package search

/**
 * Binary search is an algorithm which finds the position of a target value within an array (Sorted)
 *
 * Worst-case performance	O(log(n))
 * Best-case performance	O(1)
 * Average performance	O(log(n))
 * Worst-case space complexity	O(1)
 */

/**
 * @param array is an array where the element should be found
 * @param key is an element which should be found
 * @return index of the element
 */
fun <T : Comparable<T>> binarySearch(array: Array<T>, key: T): Int {
    return binarySearchHelper(array, key, 0, array.size - 1)
}

/**
 * @param array The array to search
 * @param key The element you are looking for
 * @return the location of the key or -1 if the element is not found
 **/
fun <T : Comparable<T>> binarySearchHelper(array: Array<T>, key: T, start: Int, end: Int): Int {
    if (start > end) {
        return -1
    }

    val mid = start + (end - start) / 2

    return when {
        array[mid].compareTo(key) == 0 -> mid
        array[mid].compareTo(key) > 0 -> binarySearchHelper(array, key, start, mid - 1)
        else -> binarySearchHelper(array, key, mid + 1, end)
    }
}


package search

/**
 * Interpolation search is an algorithm which finds the position of a target value within an array (Sorted)
 *
 * Worst-case performance	O(n)
 * Best-case performance	O(1)
 * Average performance	O(log log n)
 * Worst-case space complexity	O(1)
 */

/**
 * @param arr is an array where the element should be found
 * @param lo array starting index
 * @param hi array ending index
 * @param x is an element which should be found
 * @return index of the element
 */

fun interpolationSearch(arr: IntArray, lo: Int,
                        hi: Int, x: Int): Int {
    val pos: Int

    if (lo <= hi && x >= arr[lo] && x <= arr[hi]) {

        pos = (lo
                + ((hi - lo) / (arr[hi] - arr[lo])
                * (x - arr[lo])))

        if (arr[pos] == x) return pos

        if (arr[pos] < x) return interpolationSearch(arr, pos + 1, hi,
                x)

        if (arr[pos] > x) return interpolationSearch(arr, lo, pos - 1,
                x)
    }
    return -1
}


package search

/**
 * Linear search is an algorithm which finds the position of a target value within an array (Usually unsorted)
 *
 * Worst-case performance	O(n)
 * Best-case performance	O(1)
 * Average performance	O(n)
 * Worst-case space complexity	O(1)
 */

/**
 * @param array is an array where the element should be found
 * @param key is an element which should be found
 * @return index of the element
 */
fun <T : Comparable<T>> linearSearch(array: Array<T>, key: T): Int {
    return linearSearchImpl(array, key)
}

/**
 * @param array The array to search
 * @param key The element you are looking for
 * @return the location of the key or -1 if the element is not found
 **/
fun <T : Comparable<T>> linearSearchImpl(array: Array<T>, key: T): Int {
    for (i in array.indices) {
        if (array[i].compareTo(key) == 0) {
            return i
        }
    }

    return -1
}


package search

/**
 * Complexity Analysis:
 *
 * Time Complexity: O(log3 n)
 * Space Complexity: O(1)
 *
 */

fun ternarySearch(l: Double, r: Double, func: (Double) -> Double, eps: Double = 1e-3): Double {
    var left = l
    var right = r
    while (right - left > eps) {
        val midFirst = left + (right - left) / 3
        val midSecond = right - (right - left) / 3
        if (func(midFirst) < func(midSecond)) {
            left = midFirst
        } else {
            right = midSecond
        }
    }
    return left
}


package sort

/**
 * This method implements the Generic Brick Sort
 *
 * @param array The array to be sorted
 * Sorts the array in increasing order
 *
 * Worst-case performance	O(n^2)
 * Best-case performance	O(n)
 * Average performance	O(n^2)
 * Worst-case space complexity	O(1)
 **/

fun <T : Comparable<T>>  oddEvenSort(array: Array<T>) {
    var isSorted = false
    while (!isSorted) {
        isSorted = true
        var temp : Comparable<T>

        var i = 1
        while (i <= array.size - 2) {
            if (array[i] > array[i + 1]) {
                temp = array[i]
                array[i] = array[i + 1]
                array[i + 1] = temp
                isSorted = false
            }
            i += 2
        }
        var j = 0
        while (j <= array.size - 2) {
            if (array[j] > array[j + 1]) {
                temp = array[j]
                array[j] = array[j + 1]
                array[j + 1] = temp
                isSorted = false
            }
            j += 2
        }
    }
    return
}


package sort

/**
 * This method implements the Generic Bubble Sort
 *
 * @param array The array to be sorted
 * Sorts the array in increasing order
 *
 * Worst-case performance	O(n^2)
 * Best-case performance	O(n)
 * Average performance	O(n^2)
 * Worst-case space complexity	O(1)
 **/
fun <T : Comparable<T>> bubbleSort(array: Array<T>) {
    val length = array.size - 1

    for (i in 0..length) {
        var isSwapped = false
        for (j in 1..length) {
            if (array[j] < array[j - 1]) {
                isSwapped = true
                swapElements(array, j, j - 1)
            }
        }

        if (!isSwapped) break
    }
}

/**
 * This method swaps the element at two indexes
 *
 * @param array The array containing the elements
 * @param idx1 Index of first element
 * @param idx2 Index of second element
 * Swaps the element at two indexes
 **/
fun <T : Comparable<T>> swapElements(array: Array<T>, idx1: Int, idx2: Int) {
    array[idx1] = array[idx2].also {
        array[idx2] = array[idx1]
    }
}


package sort

/**
 * This function implements the Heap Sort.
 *
 * @param array The array to be sorted
 * Sorts the array in increasing order
 *
 * Worst-case performance       O(n*log(n))
 * Best-case performance        O(n*log(n))
 * Average-case performance     O(n*log(n))
 * Worst-case space complexity  O(1) (auxiliary)
 */
fun <T : Comparable<T>> heapSort(array: Array<T>) {
    buildMaxHeap(array)
    transformMaxHeapToSortedArray(array)
}

/**
 * This function changes the element order of the array to represent a max
 * binary tree.
 *
 * @param array The array containing the elements
 * @param index Index of the currently largest element
 */
fun <T : Comparable<T>> maxheapify(array: Array<T>, heapSize: Int, index: Int) {
    val left = 2 * index + 1
    val right = 2 * index + 2
    var largest = index

    if (left < heapSize && array[left] > array[largest])
        largest = left
    if (right < heapSize && array[right] > array[largest])
        largest = right
    if (largest != index) {
        swapElements(array, index, largest)
        maxheapify(array, heapSize, largest)
    }
}

/**
 * Arrange the elements of the array to represent a max heap.
 *
 * @param array The array containing the elements
 */
private fun <T : Comparable<T>> buildMaxHeap(array: Array<T>) {
    val n = array.size
    for (i in (n / 2 - 1) downTo 0)
        maxheapify(array, n, i)
}

/**
 * Arrange the elements of the array, which should be in order to represent a
 * max heap, into ascending order.
 *
 * @param array The array containing the elements (max heap representation)
 */
private fun <T : Comparable<T>> transformMaxHeapToSortedArray(array: Array<T>) {
    for (i in (array.size - 1) downTo 0) {
        swapElements(array, i, 0)
        maxheapify(array, i, 0)
    }
}


package sort

/**
 * This method implements the Generic Insertion Sort
 *
 * @param array The array to be sorted
 * Sorts the array in increasing order
 *
 * Worst-case performance	O(n^2)
 * Best-case performance	O(n)
 * Average performance	O(n^2)
 * Worst-case space complexity	O(1)
 **/
fun <T : Comparable<T>> insertionSort(array: Array<T>) {
    val size = array.size - 1

    for (i in 1..size) {
        val key = array[i]
        var idx = i

        for (j in i - 1 downTo 0) {
            if (array[j].compareTo(key) > 0) {
                array[j + 1] = array[j]
                idx = j
            } else {
                break
            }
        }

        array[idx] = key
    }
}


package sort

/**
 * This function implements Merge Sort
 *
 * @param array The array to be sorted
 * It is a Divide and Conquer algorithm. It sorts the array by dividing it into two halves and comparing the elements.
 *
 * Worst-case performance	    O(n log n)
 * Best-case performance	    O(n log n)
 * Average performance      	O(n log n)
 * Worst-case space complexity	O(n)
 */
fun <T : Comparable<T>> mergeSort(array: Array<T>, start: Int, end: Int) {

    val temp = arrayOfNulls<Comparable<*>>(array.size) as Array<T>

    if (start < end) {
        val mid = start + (end - start) / 2
        mergeSort(array, start, mid)
        mergeSort(array, mid + 1, end)
        merge(array, temp, start, mid, end)
    }
}

/**
 * This function merges the two halves after comparing them
 * @param array The array to be sorted
 * @param temp The temp array containing the values
 * @param start Starting index of the array
 * @param mid Middle index of the array
 * @param end Ending index of the array
 */
fun <T : Comparable<T>> merge(array: Array<T>, temp: Array<T>, start: Int, mid: Int, end: Int) {

    System.arraycopy(array, start, temp, start, end - start + 1)

    var i = start
    var j = mid + 1
    var k = start

    while (i <= mid && j <= end) {
        if (temp[i] < temp[j]) {
            array[k++] = temp[i++]
        } else {
            array[k++] = temp[j++]
        }
    }

    while (i <= mid) {
        array[k++] = temp[i++]
    }

    while (j <= end) {
        array[k++] = temp[j++]
    }
}


package sort

/**
 * This method implements the Quick Sort
 *
 * @param array The array to be sorted
 * It is a Divide and Conquer algorithm. It picks an element as pivot and partitions the given array around the picked pivot.
 *
 * Worst-case performance	    O(n^2)
 * Best-case performance	    O(nLogn)
 * Average performance      	O(nLogn)
 * Worst-case space complexity	O(1)
 **/
fun <T : Comparable<T>> quickSort(array: Array<T>, low: Int, high: Int) {
    if (low < high) {
        val pivot = partition(array, low, high)
        quickSort(array, low, pivot - 1)
        quickSort(array, pivot, high)
    }
}

/**
 * This method finds the pivot index for an array
 *
 * @param array The array to be sorted
 * @param low The first index of the array
 * @param high The last index of the array
 *
 * */
fun <T : Comparable<T>> partition(array: Array<T>, low: Int, high: Int): Int {

    var left = low
    var right = high
    val mid = (left + right) / 2
    val pivot = array[mid]

    while (left <= right) {
        while (array[left] < pivot) {
            left++
        }

        while (array[right] > pivot) {
            right--
        }

        if (left <= right) {
            swapElements(array, left, right)
            left++
            right--
        }
    }
    return left
}


package sort

/**
 * This method implements the Generic Selection Sort
 *
 * @param array The array to be sorted
 * Sorts the array by repeatedly finding the minimum element from unsorted part and putting in the beginning
 *
 * Worst-case performance	O(n^2)
 * Best-case performance	O(n^2)
 * Average performance	O(n^2)
 * Worst-case space complexity	O(1)
 **/
fun <T : Comparable<T>> selectionSort(array: Array<T>) {
    val length = array.size - 1

    for (i in 0..length) {
        var idx = i
        for (j in i + 1..length) {
            if (array[j] < array[idx]) {
                idx = j
            }
        }

        swapElements(array, i, idx)
    }
}


package dynamicProgramming

import org.junit.Test

class AssemblyLineSchedulingTest{

    @Test
    fun testWith6Stations() {
        assert(AssemblyLineScheduling(6,arrayOf(intArrayOf(3, 7, 2, 5,5,3), intArrayOf(4, 1, 6, 7,5,3)),arrayOf(intArrayOf(0,2,3,4,5,6), intArrayOf(0,5,4,3,2,1)), intArrayOf(15,11), intArrayOf(16,5))== 42)
    }
    @Test
    fun testWith2Stations() {
        assert(AssemblyLineScheduling(2,arrayOf(intArrayOf(3, 7), intArrayOf(4, 1)),arrayOf(intArrayOf(0,2), intArrayOf(0,5)), intArrayOf(5,1), intArrayOf(6,5))== 11)
    }
    @Test
    fun testWith4Stations() {
        assert(AssemblyLineScheduling(4,arrayOf(intArrayOf(3, 7,6,2), intArrayOf(4, 1,6,2)),arrayOf(intArrayOf(0,2,6,1), intArrayOf(0,5,3,4)), intArrayOf(7,4), intArrayOf(5,8))== 25)
    }

}


package dynamicProgramming

import org.junit.Test

class EditDistanceTest {
    @Test
    fun testForInsertion() {
        assert(editDistance("time", "timetable") == 5)
    }

    @Test
    fun testForReplacement() {
        assert(editDistance("rice", "race") == 1)
    }

    @Test
    fun testForInsertionReplacementAndDeletion() {
        assert(editDistance("saturday", "friday") == 5)
    }
}


package dynamicProgramming

import org.junit.Test

internal class FactorialTest {

    @Test
    fun testFactorial1() {
        assert(factorial(0) == 1)
    }

    @Test
    fun testFactorial2() {
        assert(factorial(1) == 1)
    }

    @Test
    fun testFactorial3() {
        assert(factorial(5) == 120)
    }
}


package dynamicProgramming

import org.junit.Test

internal class isPrimeTest {

    @Test
    fun testPrime1(){
        assert(2.isPrime()==true)
    }

    @Test
    fun testPrime2(){
        assert(53.isPrime()==true)
    }

    @Test
    fun testPrime3(){
        assert(4.isPrime()==false)
    }
}


package dynamicProgramming

import org.junit.Test

class LCSTest {

    @Test
    fun testForBasicCases() {
        assert(lcs("apple", "orange") == 2)
    }

    @Test
    fun testForNoCommonSubsequence() {
        assert(lcs("bird", "fly") == 0)
    }

    @Test
    fun testForSameString() {
        assert(lcs("success", "success") == 7)
    }

    @Test
    fun testForSubstringOfAString() {
        assert(lcs("application", "app") == 3)
    }

    @Test
    fun testForTwoLcs() {
        assert(lcs("abcxyz", "xyzabc") == 3)
    }
}


package dynamicProgramming

import org.junit.Test

class MatrixChainMultiplicationTest {
    @Test
    fun testWith5Matrices() {
        assert(MatrixChainOrder(intArrayOf(30, 20, 40, 10, 30)) == 23000)
    }

    @Test
    fun testWith4Matrices() {
        assert(MatrixChainOrder(intArrayOf(50, 20, 10, 30)) == 25000)
    }
}


package dynamicProgramming

import org.junit.Test

class RodCuttingProblemTest {

    @Test
    fun testWithRodSize3() {
        assert(rodCutting(intArrayOf(3, 8, 10)) == 11)
    }

    @Test
    fun testWithZeroPrice() {
        assert(rodCutting(intArrayOf(0, 0, 0)) == 0)
    }

    @Test
    fun testWithSameValue() {
        assert(rodCutting(intArrayOf(2, 4, 6, 8, 10)) == 10)
    }

}


package dynamicProgramming

import org.junit.Test

class UnboundedKnapsackTest {
    @Test
    fun testBothWeightAndValueArrayHasDifferentValuesGivesExpectedOutput() {
        assert(unboundedKnapsack(5, intArrayOf(1, 2, 3), intArrayOf(20, 50, 30), 3) == 120)
    }

    @Test
    fun testBothWeightAndValueArrayHasSameValuesGivesExpectedOutput() {
        assert(unboundedKnapsack(3, intArrayOf(2, 2), intArrayOf(3, 3), 2) == 3)
    }

    @Test
    fun testNegativeCapacityGivesZero() {
        assert(unboundedKnapsack(-3, intArrayOf(2, 2), intArrayOf(3, 3), 2) == 0)
    }

    @Test
    fun testZeroCapacityGivesZero() {
        assert(unboundedKnapsack(0, intArrayOf(2, 2), intArrayOf(3, 3), 2) == 0)
    }
}


package dynamicProgramming

import org.junit.Test

class WeightedJobSchedulingTest{
    @Test
    fun testForBasicCases(){
        assert(findMaxProfit(listOf(Job(0,1,30),Job(0,2,10),Job(0,3,50),Job(1,4,60),Job(2,3,120),Job(2,5,90),Job(3,6,40),Job(4,7,70)))==220)
    }

    @Test
    fun testForSameOptimalOutput(){
        assert(findMaxProfit(listOf(Job(0,2,10),Job(1,3,10)))==10)
    }
}


package dynamicProgramming

import org.junit.Test

class ZeroOneKnapsackProblemTest {
    @Test
    fun testBothWeightAndValueArrayHasDifferentValuesGivesExpectedOutput() {
        assert(zerooneknapsack(5, intArrayOf(3,2,1), intArrayOf(20,50,30),3) == 80)
    }

    @Test
    fun testBothWeightAndValueArrayHasSameValuesGivesExpectedOutput(){
        assert(zerooneknapsack(3, intArrayOf(2,2), intArrayOf(3,3),2)==3)
    }

    @Test
    fun testNegativeCapacityGivesZero(){
        assert(zerooneknapsack(-3, intArrayOf(2,2), intArrayOf(3,3),2)==0)
    }

    @Test
    fun testZeroCapacityGivesZero(){
        assert(zerooneknapsack(0, intArrayOf(2,2), intArrayOf(3,3),2)==0)
    }
}


package dynamic_programming

import org.junit.Test

class PalindromePartitioningTest {

    @Test
    fun palindromePartitioningTest1() {
        val string = "nitik"
        val res = 2
        assert(initialize(string) == res)
    }

    @Test
    fun palindromePartitioningTest2() {
        val string = "ababbbabbaababa"
        val res = 3
        assert(initialize(string) == res)
    }

    @Test
    fun palindromePartitioningTest3() {
        val string = "abcddefgk"
        val res = 7
        assert(initialize(string) == res)
    }
}


package math

import org.junit.Test
import java.lang.IllegalArgumentException

class AreaTest {
    @Test
    fun testAreaOfARectangle() = assert(areaOfARectangle(10.0, 5.0) == 50.0)

    @Test
    fun testAreaOfASquare() = assert(areaOfASquare(5.0) == 25.0)

    @Test
    fun testAreaOfACircle() = assert(areaOfACircle(1.0) == Math.PI)

    @Test
    fun testAreaOfATriangle() = assert(areaOfATriangle(5.0, 10.0) == 25.0)

    @Test(expected = IllegalArgumentException::class)
    fun testAreaWithNegatives() {
        areaOfARectangle(-1.0, 0.0)
        areaOfASquare(-1.0)
        areaOfACircle(-1.0)
        areaOfATriangle(-1.0, 1.0)
    }

    @Test(expected = IllegalArgumentException::class)
    fun testAreaWithZeros() {
        areaOfARectangle(0.0, 0.0)
        areaOfASquare(0.0)
        areaOfACircle(0.0)
        areaOfATriangle(0.0, 1.0)
    }
}


package math

import org.junit.Test

class AverageTest {
    @Test
    fun testAverageArrayOfDouble() {
        val doubleArray = arrayOf(1.0, 1.25, 1.50, 1.75, 2.0)
        assert(average(doubleArray) == 1.50)
    }

    @Test
    fun testAverageArrayOfDoubleWithOneElement() {
        val doubleArray = arrayOf(1.0)
        assert(average(doubleArray) == 1.00)
    }

    @Test
    fun testAverageArrayOfInt() {
        val intArray = arrayOf(1, 2, 3)
        assert(average(intArray) == 2)
    }

    @Test
    fun testAverageArrayOfIntWithOneElement() {
        val intArray = arrayOf(1)
        assert(average(intArray) == 1)
    }
}


package mathematics

import org.junit.Test
import java.security.InvalidParameterException

class FactorialTest {

    @Test
    fun testFactorialNormal() {
        val input = 6L
        val expectedFactorial = 720L
        assert(getFactorial(input) == expectedFactorial)
    }

    @Test(expected = InvalidParameterException::class)
    fun testFactorialOfNegativeNumber() {
        val input = -1L
        getFactorial(input)
    }

    @Test
    fun testFactorialOfZero() {
        val input = 0L
        val expectedFactorial = 1L
        assert(getFactorial(input) == expectedFactorial)
    }

}


package math

import math.median
import org.junit.Test

class Median {
    @Test
    fun testMedianWithEvenSize() {
        val array = intArrayOf(1, 2, 3, 4)
        assert(median(array) == 2.5)
    }

    @Test
    fun testMedianWithOddSize() {
        val array = intArrayOf(1, 2, 3)
        assert(median(array) == 2.0)
    }

    @Test
    fun testMedianWithNegatives() {
        val array = intArrayOf( 2, 3, 4, -2, -8, -3)
        assert(median(array) == 0.0)
    }
}


package math

import org.junit.Test

class TwoSumTest {
    @Test
    fun testTwoSum(){
        val array = IntArray(2)
        array[0] = 3
        array[1] = 3
        val target = 6
        val result = intArrayOf(0,1)
        assert(twoSum(array,target).contentEquals(result))
    }
}


package other

import org.junit.Assert.assertTrue
import org.junit.Test

class PalindromeTest {

    @Test
    fun testPalindromePortuguesePhrase() {
        val text = "A mÃ£e te ama"
        assertTrue(isPalindrome(text))
    }

    @Test
    fun testPalindromeEnglishPhrase() {
        val text = "Mr Owl ate my metal worm"
        assertTrue(isPalindrome(text))
    }

    @Test
    fun testPalindromeName() {
        val text = "Hannah"
        assertTrue(isPalindrome(text))
    }

    @Test
    fun testPalindromeNumber() {
        val text = "2002"
        assertTrue(isPalindrome(text))
    }
}


package search

import org.junit.Test

class BinarySearchTest {

    @Test
    fun testBinarySearch1() {
        val array = arrayOf(1, 2, 4, 8, 16)
        val key = 2
        assert(binarySearch(array, key) == 1)
    }

    @Test
    fun testBinarySearch2() {
        val array = arrayOf(1, 2, 4, 8, 16)
        val key = 1
        assert(binarySearch(array, key) == 0)
    }

    @Test
    fun testBinarySearch3() {
        val array = arrayOf(1, 2, 4, 8, 16)
        val key = 16
        assert(binarySearch(array, key) == 4)
    }

    @Test
    fun testBinarySearch4() {
        val array = arrayOf(1, 2, 4, 8, 16)
        val key = 20
        assert(binarySearch(array, key) == -1)
    }

    @Test
    fun testBinarySearch5() {
        val array = arrayOf("A", "B", "D", "E")
        val key = "D"
        assert(binarySearch(array, key) == 2)
    }
}


package search

import org.junit.Test

class InterpolationSearchTest
{
    @Test
    fun testWhenElementPresent(){
        val array = intArrayOf(1,2,3,4,5)

        assert(interpolationSearch(array,0,array.size-1,5)==4)
    }

    @Test
    fun testWhenElementNotPresent() {
        val array = intArrayOf(4,7,9,23,67,78,99)

        assert(interpolationSearch(array,0,array.size-1,100)==-1)
    }
}


package search

import org.junit.Test

class LinearSearchTest {

    @Test
    fun testLinearSearch1() {
        val array = arrayOf(1, 2, 4, 5, 3, 9, 53)
        val key = 5

        assert(linearSearch(array, key) == 3)
    }

    @Test
    fun testLinearSearch2() {
        val array = arrayOf(1, 4, 3)
        val key = 9

        assert(linearSearch(array, key) == -1)
    }

    @Test
    fun testLinearSearch3() {
        val array = arrayOf('a', 'c', 'e', 'd')
        val key = 'c'

        assert(linearSearch(array, key) == 1)
    }

    @Test
    fun testLinearSearch4() {
        val array = arrayOf("Hey", "There", "How", "Are", "You")
        val key = "You"

        assert(linearSearch(array, key) == 4)
    }
}


package search

import org.junit.Test

class TernarySearchTest {
    companion object {
        const val EPS = 1e-2
    }

    @Test
    fun testTernarySearch1() {
        val func = { x: Double ->
            if (x < 10) {
                x
            } else {
                -10 * (x - 10) + 10
            }
        }
        assert(kotlin.math.abs(ternarySearch(-1e9, 1e9, func) - 10) <= EPS)
    }
}


package sort

import org.junit.Assert
import org.junit.Test

class BrickSortTest{
    @Test
    fun testForIntegers(){
        val array = arrayOf(4, 3, 2, 8, 1)
        oddEvenSort(array)

        Assert.assertArrayEquals(array, arrayOf(1, 2, 3, 4, 8))
    }

    @Test
    fun testForCharacters() {
        val array = arrayOf("A", "D", "E", "C", "B")
        bubbleSort(array)

        Assert.assertArrayEquals(array, arrayOf("A", "B", "C", "D", "E"))
    }
}


package sort

import org.junit.Assert.assertArrayEquals
import org.junit.Test

class BubbleSortTest {

    @Test
    fun testBubbleSort1() {
        val array = arrayOf(4, 3, 2, 8, 1)
        bubbleSort(array)

        assertArrayEquals(array, arrayOf(1, 2, 3, 4, 8))
    }

    @Test
    fun testBubbleSort2() {
        val array = arrayOf("A", "D", "E", "C", "B")
        bubbleSort(array)

        assertArrayEquals(array, arrayOf("A", "B", "C", "D", "E"))
    }
}


package sort

import org.junit.Assert.assertArrayEquals
import org.junit.Test

class HeapSortTest {

    @Test
    fun `sort array of integers`() {
        val array = arrayOf(4, 3, 2, 8, 1)
        heapSort(array)
        assertArrayEquals(array, arrayOf(1, 2, 3, 4, 8))
    }

    @Test
    fun `sort sorted array of integers`() {
        val array = arrayOf(1, 2, 3, 4, 8)
        heapSort(array)
        assertArrayEquals(array, arrayOf(1, 2, 3, 4, 8))
    }

    @Test
    fun `sort array of characters`() {
        val array = arrayOf('A', 'D', 'E', 'C', 'B')
        heapSort(array)
        assertArrayEquals(array, arrayOf('A', 'B', 'C', 'D', 'E'))
    }

}


package sort

import org.junit.Assert.assertArrayEquals
import org.junit.Test

class InsertionSortTest {

    @Test
    fun testInsertionSort1() {
        val array = arrayOf(4, 3, 2, 8, 1)
        insertionSort(array)

        assertArrayEquals(array, arrayOf(1, 2, 3, 4, 8))
    }

    @Test
    fun testInsertionSort2() {
        val array = arrayOf(-1, 2, 43, 3, 97, 1, 0)
        insertionSort(array)

        assertArrayEquals(array, arrayOf(-1, 0, 1, 2, 3, 43, 97))
    }

    @Test
    fun testInsertionSort3() {
        val array = arrayOf("A", "D", "E", "C", "B")
        insertionSort(array)

        assertArrayEquals(array, arrayOf("A", "B", "C", "D", "E"))
    }
}


package sort

import org.junit.Assert
import org.junit.Test

class MergeSortTest {

    @Test
    fun testMergeSort1() {
        val array = arrayOf(4, 3, 2, 8, 1)
        mergeSort(array, 0, array.size - 1)

        Assert.assertArrayEquals(array, arrayOf(1, 2, 3, 4, 8))
    }

    @Test
    fun testMergeSort2() {
        val array = arrayOf("A", "D", "E", "C", "B")
        mergeSort(array, 0, array.size - 1)

        Assert.assertArrayEquals(array, arrayOf("A", "B", "C", "D", "E"))
    }

    @Test
    fun testMergeSort3() {
        val array = arrayOf(20, 5, 16, -1)
        mergeSort(array, 0, array.size - 1)

        Assert.assertArrayEquals(array, arrayOf(-1, 5, 16, 20))
    }
}


package sort

import org.junit.Assert.assertArrayEquals
import org.junit.Test

class QuickSortTest {

    @Test
    fun testQuickSort1() {
        val array = arrayOf(4, 3, 2, 8, 1)
        quickSort(array, 0, array.size - 1)

        assertArrayEquals(array, arrayOf(1, 2, 3, 4, 8))
    }

    @Test
    fun testQuickSort2() {
        val array = arrayOf("A", "D", "E", "C", "B")
        quickSort(array, 0, array.size - 1)

        assertArrayEquals(array, arrayOf("A", "B", "C", "D", "E"))
    }

    @Test
    fun testQuickSort3() {
        val array = arrayOf(20, 5, 16, -1, 6)
        quickSort(array, 0, array.size - 1)

        assertArrayEquals(array, arrayOf(-1, 5, 6, 16, 20))
    }
}


package sort

import org.junit.Assert.assertArrayEquals
import org.junit.Test

class SelectionSortTest {

    @Test
    fun testSelectionSort1() {
        val array = arrayOf(4, 3, 2, 8, 1)
        selectionSort(array)

        assertArrayEquals(array, arrayOf(1, 2, 3, 4, 8))
    }

    @Test
    fun testSelectionSort2() {
        val array = arrayOf(20, 5, 16, -1, 6)
        selectionSort(array)

        assertArrayEquals(array, arrayOf(-1, 5, 6, 16, 20))
    }

    @Test
    fun testSelectionSort3() {
        val array = arrayOf("A", "D", "E", "C", "B")
        selectionSort(array)

        assertArrayEquals(array, arrayOf("A", "B", "C", "D", "E"))
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.audiovideo.video.backgroundfilter

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoFrame
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.VideoRotation
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.backgroundfilter.backgroundblur.BackgroundBlurConfiguration
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.backgroundfilter.backgroundblur.BackgroundBlurVideoFrameProcessor
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.backgroundfilter.backgroundreplacement.BackgroundReplacementConfiguration
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.backgroundfilter.backgroundreplacement.BackgroundReplacementVideoFrameProcessor
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.buffer.VideoFrameRGBABuffer
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.DefaultEglCoreFactory
import com.amazonaws.services.chime.sdk.meetings.audiovideo.video.gl.EglCoreFactory
import com.amazonaws.services.chime.sdk.meetings.utils.logger.ConsoleLogger
import com.amazonaws.services.chime.sdk.meetings.utils.logger.LogLevel
import com.amazonaws.services.chime.sdk.test.R
import com.xodee.client.video.JniUtil
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockkStatic
import io.mockk.verify
import java.nio.ByteBuffer
import kotlin.math.abs
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class BackgroundFilterVideoFrameProcessorTest {

    private lateinit var testBackgroundFilterVideoFrameProcessor: BackgroundFilterVideoFrameProcessor
    private lateinit var testBackgroundBlurVideoFrameProcessor: BackgroundBlurVideoFrameProcessor
    private lateinit var testBackgroundReplacementVideoFrameProcessor: BackgroundReplacementVideoFrameProcessor
    private lateinit var frame: VideoFrame
    private lateinit var rgbaData: ByteBuffer
    private lateinit var filteredByteBuffer: ByteBuffer
    private lateinit var scaledBitmap: Bitmap
    private lateinit var scaledBlurredImageBitmap: Bitmap
    private lateinit var scaledReplacedImageBitmap: Bitmap

    private val logger = ConsoleLogger(LogLevel.INFO)
    private val context = InstrumentationRegistry.getInstrumentation().targetContext
    private val eglCoreFactory: EglCoreFactory = DefaultEglCoreFactory()
    private val bitmap =
        BitmapFactory.decodeResource(context.resources, R.raw.background_ml_test_image)
    private val blurredImageBitmap =
        BitmapFactory.decodeResource(context.resources, R.raw.background_blurred_image)
    private val replacedImageBitmap =
        BitmapFactory.decodeResource(context.resources, R.raw.background_replaced_image)
    private val staticImageWidth = 720
    private val staticImageHeight = 1280

    private fun checkImageSimilarity(staticImage: Bitmap, testImage: Bitmap): Boolean {
        val differenceThreshold = 10
        var difference: Int
        var differentPixels = 0
        for (row in 0 until testImage.height) {
            for (col in 0 until testImage.width) {
                val staticImageRgb = staticImage.getColor(col, row)
                val testImageRgb = testImage.getColor(col, row)
                // Extracting individual float represented colors and
                // converting them to integer represented colors
                val staticImageRed = (staticImageRgb.red() * 255).toInt()
                val staticImageGreen = (staticImageRgb.green() * 255).toInt()
                val staticImageBlue = (staticImageRgb.blue() * 255).toInt()
                val testImageRed = (testImageRgb.red() * 255).toInt()
                val testImageGreen = (testImageRgb.green() * 255).toInt()
                val testImageBlue = (testImageRgb.blue() * 255).toInt()
                difference = abs(staticImageRed - testImageRed)
                difference += abs(staticImageGreen - testImageGreen)
                difference += abs(staticImageBlue - testImageBlue)
                difference /= 3
                if (difference > differenceThreshold) differentPixels++
            }
        }
        val totalPixels = testImage.width * testImage.height
        val errorRate = differentPixels / totalPixels
        val errorThreshold = 0.01
        return errorRate <= errorThreshold
    }

    @MockK
    private lateinit var mockVideFrameBuffer: VideoFrameRGBABuffer

    @Before
    fun setup() {
        MockKAnnotations.init(this, relaxUnitFun = true)
        testBackgroundFilterVideoFrameProcessor = BackgroundFilterVideoFrameProcessor(
            logger,
            context,
            "BackgroundFilterVideoFrameProcessor"
        )
        frame = VideoFrame(1L, mockVideFrameBuffer, VideoRotation.Rotation270)
        scaledBitmap = Bitmap.createScaledBitmap(bitmap, 720, 1280, false)
        scaledBlurredImageBitmap = Bitmap.createScaledBitmap(blurredImageBitmap, staticImageWidth, staticImageHeight, false)
        scaledReplacedImageBitmap = Bitmap.createScaledBitmap(replacedImageBitmap, staticImageWidth, staticImageHeight, false)
        rgbaData = ByteBuffer.allocateDirect(scaledBitmap.width * scaledBitmap.height * 4)
        filteredByteBuffer = ByteBuffer.allocateDirect(scaledBitmap.width * scaledBitmap.height * 4)

        scaledBitmap.copyPixelsToBuffer(rgbaData)
        every { mockVideFrameBuffer.height } returns scaledBitmap.height
        every { mockVideFrameBuffer.width } returns scaledBitmap.width
        every { mockVideFrameBuffer.data } returns rgbaData
    }

    @Test
    fun returnInputFrameIfThereIsAnyErrorInProcessingFrame() {
        testBackgroundFilterVideoFrameProcessor.getProcessedFrame(frame, null, rgbaData)
        verify(atLeast = 1) {
            VideoFrameRGBABuffer(
                frame.getRotatedWidth(),
                frame.getRotatedHeight(),
                rgbaData,
                frame.getRotatedWidth() * 4,
                Runnable { JniUtil.nativeFreeByteBuffer(null) })
        }
    }

    @Test
    fun returnInputFrameWhenFilteredBitmapIsNotNull() {
        mockkStatic(JniUtil::class)
        every { JniUtil.nativeAllocateByteBuffer(any()) } returns filteredByteBuffer

        testBackgroundFilterVideoFrameProcessor.getProcessedFrame(frame, scaledBitmap, rgbaData)

        verify(atLeast = 1) {
            VideoFrameRGBABuffer(
                frame.getRotatedWidth(),
                frame.getRotatedHeight(),
                filteredByteBuffer,
                frame.getRotatedWidth() * 4,
                Runnable { JniUtil.nativeFreeByteBuffer(null) })
        }
    }

    @Test
    fun bitmapHeightShouldBeLessThanWidthInLandscapeOrientation() {
        frame = VideoFrame(1L, mockVideFrameBuffer, VideoRotation.Rotation180)
        val scaledBitmap =
            testBackgroundFilterVideoFrameProcessor.getScaledInputBitmap(frame, bitmap)
        assertTrue(
            "Frame height " + scaledBitmap.height + " should be less than width " + scaledBitmap.width,
            scaledBitmap.height < scaledBitmap.width
        )
    }

    @Test
    fun bitmapHeightShouldBeGreaterThanWidthInPortraitOrientation() {
        val scaledBitmap =
            testBackgroundFilterVideoFrameProcessor.getScaledInputBitmap(frame, bitmap)
        assertTrue(
            "Frame height " + scaledBitmap.height + " should be greater than width " + scaledBitmap.width,
            scaledBitmap.height > scaledBitmap.width
        )
    }

    @Test
    fun getBackgroundBlurredBitmap() {
        // By default input VideoFrame has width > height irrespective of device orientation. Rotated
        // height/weight gives correct dimensions depending on Rotation value. Since input image is
        // portrait (height > width), for testing purpose I flip frames height and width to get
        // correct rotated height based on rotation value.
        every { mockVideFrameBuffer.height } returns scaledBitmap.width
        every { mockVideFrameBuffer.width } returns scaledBitmap.height
        testBackgroundBlurVideoFrameProcessor = BackgroundBlurVideoFrameProcessor(
            logger,
            eglCoreFactory,
            context,
            BackgroundBlurConfiguration()
        )
        val backgroundBlurredBitmap =
            testBackgroundBlurVideoFrameProcessor.getBackgroundBlurredBitmap(scaledBitmap, frame)

        val similarity =
            backgroundBlurredBitmap?.let { checkImageSimilarity(scaledBlurredImageBitmap, it) }
        assertEquals(similarity, true)
    }

    @Test
    fun getBackgroundReplacedBitmap() {
        // By default input VideoFrame has width > height irrespective of device orientation. Rotated
        // height/weight gives correct dimensions depending on Rotation value. Since input image is
        // portrait (height > width), for testing purpose I flip frames height and width to get
        // correct rotated height based on rotation value.
        every { mockVideFrameBuffer.height } returns scaledBitmap.width
        every { mockVideFrameBuffer.width } returns scaledBitmap.height

        testBackgroundReplacementVideoFrameProcessor = BackgroundReplacementVideoFrameProcessor(
            logger,
            eglCoreFactory,
            context,
            BackgroundReplacementConfiguration()
        )
        val backgroundReplacedBitmap =
            testBackgroundReplacementVideoFrameProcessor.getBackgroundReplacedBitmap(
                scaledBitmap,
                frame
            )

        val similarity =
            backgroundReplacedBitmap?.let { checkImageSimilarity(scaledReplacedImageBitmap, it) }
        assertEquals(similarity, true)
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.internal.utils

import android.content.ContentValues
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.amazonaws.services.chime.sdk.meetings.internal.ingestion.database.DatabaseTable
import com.amazonaws.services.chime.sdk.meetings.internal.ingestion.database.IN_MEMORY_DATABASE
import com.amazonaws.services.chime.sdk.meetings.internal.ingestion.database.SQLiteDatabaseManager
import com.amazonaws.services.chime.sdk.meetings.utils.logger.ConsoleLogger
import com.amazonaws.services.chime.sdk.meetings.utils.logger.LogLevel
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class SQLiteDatabaseManagerIntegrationTests {
    private class TestTable : DatabaseTable {
        val pKName = "id"
        val pKType = "TEXT"
        val cDataName = "data"
        val cDataType = "TEXT"
        val cIntName = "number"
        val cIntType = "INTEGER"

        override val tableName: String = "test"
        override val columns: Map<String, String> =
            mapOf(cDataName to cDataType, cIntName to cIntType)
        override val primaryKey: Pair<String, String> = (pKName to pKType)
    }

    private val testTable = TestTable()

    private val contentValues: ContentValues = createEntry(5)

    private class MalformedTestTable : DatabaseTable {
        override val tableName: String = "malformmmeee"
        override val columns: Map<String, String> = mapOf("data" to "TEXTTEEEEEEE")
        override val primaryKey: Pair<String, String> = ("id" to "HELLO YYPEEE;;;;;;")
    }

    companion object {
        private lateinit var sqliteDatabaseManager: SQLiteDatabaseManager

        @BeforeClass
        @JvmStatic
        fun classSetup() {
            val context = ApplicationProvider.getApplicationContext<Context>()
            val logger = ConsoleLogger(LogLevel.INFO)
            sqliteDatabaseManager = SQLiteDatabaseManager(context, logger, IN_MEMORY_DATABASE)
        }
    }

    @Before
    fun setup() {
        sqliteDatabaseManager.createTable(testTable)
    }

    @After
    fun tearDown() {
        sqliteDatabaseManager.clear(testTable.tableName)
        dropTable()
        sqliteDatabaseManager.close()
    }

    // Create Table
    @Test
    fun createTableShouldCreateNewTableIfNotExists() {
        dropTable()
        val created = sqliteDatabaseManager.createTable(testTable)

        Assert.assertEquals(true, created)
    }

    @Test
    fun createTableShouldNotFailWhenTableExists() {
        val created = sqliteDatabaseManager.createTable(testTable)

        Assert.assertEquals(true, created)
    }

    @Test
    fun createTableShouldFailWhenGivenColumnIsMalformed() {
        val created = sqliteDatabaseManager.createTable(MalformedTestTable())

        Assert.assertEquals(false, created)
    }

    // Query
    @Test
    fun queryShouldBeAbleToLimitItemSize() {
        val contents = createEntries(5)
        sqliteDatabaseManager.insert(testTable.tableName, contents)

        val resp = sqliteDatabaseManager.query(testTable.tableName, 2)

        Assert.assertEquals(2, resp.size)
    }

    @Test
    fun queryShouldReturnEmptyListWhenTableIsEmpty() {
        val resp = sqliteDatabaseManager.query(testTable.tableName, 10)

        Assert.assertEquals(0, resp.size)
    }

    @Test
    fun queryShouldBeAbleToReturnSameDataAsInserted() {
        val isInsertSuccessful =
            sqliteDatabaseManager.insert(testTable.tableName, listOf(contentValues))
        Assert.assertEquals(true, isInsertSuccessful)

        val expectedId = contentValues.get(testTable.pKName)
        val expectedData = contentValues.get(testTable.cDataName)
        val expectedNumber = contentValues.get(testTable.cIntName)
        val resp = sqliteDatabaseManager.query(testTable.tableName, 2)

        Assert.assertEquals(1, resp.size)
        Assert.assertEquals(expectedId, resp[0][testTable.pKName])
        Assert.assertEquals(expectedData, resp[0][testTable.cDataName])
        Assert.assertEquals((expectedNumber as Int).toLong(), resp[0][testTable.cIntName])
    }

    @Test
    fun queryItemShouldReturnEmptyListWhenTableDoesNotExist() {
        dropTable()
        val resp = sqliteDatabaseManager.query(testTable.tableName, 2)

        Assert.assertEquals(0, resp.size)
    }

    // Insert
    @Test
    fun insertShouldBeAbleToInsertAnItem() {
        val isInsertSuccessful =
            sqliteDatabaseManager.insert(testTable.tableName, listOf(contentValues))

        Assert.assertEquals(true, isInsertSuccessful)
    }

    @Test
    fun insertItemShouldReturnFalseWhenTableDoesNotExist() {
        dropTable()
        val isInsertSuccessful =
            sqliteDatabaseManager.insert(testTable.tableName, listOf(contentValues))

        Assert.assertEquals(false, isInsertSuccessful)
    }

    @Test
    fun insertShouldBeAbleToInsertEmptyList() {
        val isInsertSuccessful =
            sqliteDatabaseManager.insert(testTable.tableName, listOf())

        Assert.assertEquals(true, isInsertSuccessful)
    }

    @Test
    fun insertShouldFailWhenInsertingAnMalformedItem() {
        val malformedContent = ContentValues().apply {
            put("malformed zzzz", "zzzzzz")
            put("malformed2", "ewww")
        }
        val isInsertSuccessful =
            sqliteDatabaseManager.insert(testTable.tableName, listOf(malformedContent))

        Assert.assertEquals(false, isInsertSuccessful)
    }

    @Test
    fun insertMultipleShouldBeAbleToInsertMultipleItems() {
        val size = 5
        val contents = createEntries(size)

        val isInsertSuccessful = sqliteDatabaseManager.insert(testTable.tableName, contents)

        Assert.assertEquals(true, isInsertSuccessful)

        val items = sqliteDatabaseManager.query(testTable.tableName, size)

        Assert.assertEquals(size, items.size)
        val itemsIdSet = items.map { it[testTable.pKName] }.toSet()

        for (content in contents) {
            Assert.assertTrue(itemsIdSet.contains(content[testTable.pKName]))
        }
    }

    @Test
    fun insertShouldFailToInsertTheSameItemAgain() {
        val isInsertSuccessful1 =
            sqliteDatabaseManager.insert(testTable.tableName, listOf(contentValues))
        val isInsertSuccessful2 =
            sqliteDatabaseManager.insert(testTable.tableName, listOf(contentValues))

        Assert.assertEquals(true, isInsertSuccessful1)
        Assert.assertEquals(false, isInsertSuccessful2)

        val resp = sqliteDatabaseManager.query(testTable.tableName, 10)

        Assert.assertEquals(1, resp.size)
    }

    // DELETE
    @Test
    fun deleteShouldBeAbleToDeleteAnItem() {
        val isInsertSuccessful =
            sqliteDatabaseManager.insert(testTable.tableName, listOf(contentValues))
        Assert.assertEquals(true, isInsertSuccessful)

        val id = contentValues.get(testTable.pKName)
        val deleted = sqliteDatabaseManager.delete(
            testTable.tableName,
            testTable.pKName,
            listOf(id as String)
        )

        Assert.assertEquals(1, deleted)
    }

    @Test
    fun deleteShouldReturnZeroWhenEmptyTable() {
        val deleted = sqliteDatabaseManager.delete(
            testTable.tableName,
            testTable.pKName,
            listOf("abc")
        )

        Assert.assertEquals(0, deleted)
    }

    @Test
    fun deleteShouldBeAbleToDeleteMultipleItems() {
        val count = 3
        val contents = createEntries(count)
        sqliteDatabaseManager.insert(testTable.tableName, contents)

        val resp = sqliteDatabaseManager.query(testTable.tableName, count)
        val ids = resp.map { it[testTable.pKName] as String }
        val deleted =
            sqliteDatabaseManager.delete(testTable.tableName, testTable.pKName, ids)

        Assert.assertEquals(count, deleted)
    }

    @Test
    fun deleteShouldBeAbleToDeleteItemsFewerThanInserted() {
        val count = 5
        val expectedDeletedCount = 3
        val contents = createEntries(count)
        sqliteDatabaseManager.insert(testTable.tableName, contents)

        val resp = sqliteDatabaseManager.query(testTable.tableName, expectedDeletedCount)
        val ids = resp.map { it[testTable.pKName] as String }
        val deleted =
            sqliteDatabaseManager.delete(testTable.tableName, testTable.pKName, ids)

        Assert.assertEquals(expectedDeletedCount, deleted)

        val resp2 = sqliteDatabaseManager.query(testTable.tableName, count)

        Assert.assertEquals(count - expectedDeletedCount, resp2.size)
    }

    @Test
    fun deleteEventItemShouldDeleteValidEventItemsWhenPassedWithExtraneousEventItems() {
        val isInsertSuccessful =
            sqliteDatabaseManager.insert(testTable.tableName, listOf(contentValues))

        Assert.assertEquals(true, isInsertSuccessful)

        val id = contentValues.get(testTable.pKName)

        val deleted = sqliteDatabaseManager.delete(
            testTable.tableName,
            testTable.pKName,
            listOf(id as String, "fake-id", "exxxx-id-didid-ddd")
        )

        Assert.assertEquals(1, deleted)

        val resp = sqliteDatabaseManager.query(testTable.tableName, 2)

        Assert.assertEquals(0, resp.size)
    }

    @Test
    fun deleteItemsShouldReturnErrorWhenTableDoesNotExist() {
        dropTable()
        val deletedCount = sqliteDatabaseManager.delete(
            testTable.tableName,
            testTable.pKName,
            listOf("aese-dsdfdf-sdfd")
        )
        Assert.assertEquals(-1, deletedCount)
    }

    @Test
    fun clearShouldDeleteAllItems() {
        val contents = createEntries(5)
        val inserted = sqliteDatabaseManager.insert(testTable.tableName, contents)
        val removed = sqliteDatabaseManager.clear(testTable.tableName)

        Assert.assertEquals(true, inserted)
        Assert.assertEquals(true, removed)

        val resp = sqliteDatabaseManager.query(testTable.tableName, 5)

        Assert.assertEquals(0, resp.size)
    }

    // DROP
    @Test
    fun dropTableShouldReturnTrueWhenTableDoesNotExist() {
        dropTable()
        val dropped = sqliteDatabaseManager.dropTable(testTable.tableName)

        Assert.assertTrue(dropped)
    }

    @Test
    fun dropTableShouldReturnTrueWhenTableExist() {
        sqliteDatabaseManager.createTable(testTable)
        val dropped = sqliteDatabaseManager.dropTable(testTable.tableName)

        Assert.assertTrue(dropped)
    }

    @Test
    fun dropTableShouldReturnFalseWhenInputIsMalformed() {
        val dropped = sqliteDatabaseManager.dropTable("zzz:Ewewer;;;;")

        Assert.assertFalse(dropped)
    }

    private fun dropTable() {
        sqliteDatabaseManager.dropTable(testTable.tableName)
    }

    private fun createEntry(id: Int): ContentValues =
        ContentValues().apply {
            put(testTable.pKName, id.toString())
            put(testTable.cDataName, "data$id")
            put(testTable.cIntName, id)
        }

    private fun createEntries(size: Int): List<ContentValues> =
        (1..size).map { createEntry(it) }.toList()
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

import com.amazonaws.services.chime.sdk.meetings.ingestion.EventReporter
import com.amazonaws.services.chime.sdk.meetings.internal.ingestion.SDKEvent
import com.amazonaws.services.chime.sdk.meetings.internal.utils.ConcurrentSet
import com.amazonaws.services.chime.sdk.meetings.internal.utils.EventAttributesUtils
import com.amazonaws.services.chime.sdk.meetings.internal.utils.ObserverUtils
import com.amazonaws.services.chime.sdk.meetings.session.MeetingSessionConfiguration
import com.amazonaws.services.chime.sdk.meetings.utils.logger.Logger
import java.util.Calendar

class DefaultEventAnalyticsController(
    private val logger: Logger,
    private val meetingSessionConfiguration: MeetingSessionConfiguration,
    private val meetingStatsCollector: MeetingStatsCollector,
    private val eventReporter: EventReporter? = null
) : EventAnalyticsController {
    private var eventAnalyticsObservers = ConcurrentSet.createConcurrentSet<EventAnalyticsObserver>()

    override fun publishEvent(name: EventName, attributes: EventAttributes?) {
        val now = Calendar.getInstance().timeInMillis

        // Also pushes to the history
        meetingStatsCollector.addMeetingHistoryEvent(
            MeetingHistoryEventName.fromMeetingEvent(name),
            now
        )

        // Create event to publish
        val eventAttributes = attributes ?: mutableMapOf()
        eventAttributes[EventAttributeName.timestampMs] = now

        // Add meeting stats for meeting cycle related events
        when (name) {
            EventName.meetingStartSucceeded, EventName.meetingStartFailed,
            EventName.meetingEnded, EventName.meetingFailed ->
                eventAttributes.putAll(meetingStatsCollector.getMeetingStatsEventAttributes())
            else -> Unit
        }

        eventReporter?.report(SDKEvent(name, eventAttributes))

        ObserverUtils.notifyObserverOnMainThread(eventAnalyticsObservers) {
            it.onEventReceived(name, eventAttributes)
        }
    }

    override fun pushHistory(historyEventName: MeetingHistoryEventName) {
        val currentTimeMs = Calendar.getInstance().timeInMillis
        val eventAttributes = mutableMapOf(
            EventAttributeName.timestampMs to currentTimeMs
        ) as EventAttributes

        eventReporter?.report(SDKEvent(historyEventName, eventAttributes))

        meetingStatsCollector.addMeetingHistoryEvent(historyEventName, currentTimeMs)
    }

    override fun getMeetingHistory(): List<MeetingHistoryEvent> {
        return meetingStatsCollector.getMeetingHistory()
    }

    override fun getCommonEventAttributes(): EventAttributes {
        return EventAttributesUtils.getCommonAttributes(meetingSessionConfiguration)
    }

    override fun addEventAnalyticsObserver(observer: EventAnalyticsObserver) {
        this.eventAnalyticsObservers.add(observer)
    }

    override fun removeEventAnalyticsObserver(observer: EventAnalyticsObserver) {
        this.eventAnalyticsObservers.remove(observer)
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

import com.amazonaws.services.chime.sdk.meetings.utils.logger.Logger
import java.util.Calendar

class DefaultMeetingStatsCollector(
    private val logger: Logger
) : MeetingStatsCollector {
    // The time that the meeting is requested to be started
    private var meetingStartConnectingTimeMs: Long = 0L
    // The time meeting has started
    private var meetingStartTimeMs: Long = 0L
    // The number of attempts to reconnect to the meeting
    private var retryCount: Int = 0
    // The number of poor connection occurrences
    private var poorConnectionCount: Int = 0
    // The max count of video tile during the meeting
    private var maxVideoTileCount: Int = 0

    private val historyEvents = mutableListOf<MeetingHistoryEvent>()

    override fun incrementRetryCount() {
        retryCount++
    }

    override fun incrementPoorConnectionCount() {
        poorConnectionCount++
    }

    override fun updateMaxVideoTile(videoTileCount: Int) {
        maxVideoTileCount = videoTileCount.coerceAtLeast(maxVideoTileCount)
    }

    override fun updateMeetingStartConnectingTimeMs() {
        meetingStartConnectingTimeMs = Calendar.getInstance().timeInMillis
    }

    override fun updateMeetingStartTimeMs() {
        meetingStartTimeMs = Calendar.getInstance().timeInMillis
    }

    override fun resetMeetingStats() {
        meetingStartConnectingTimeMs = 0L
        meetingStartTimeMs = 0L
        retryCount = 0
        poorConnectionCount = 0
        maxVideoTileCount = 0
    }

    override fun getMeetingStatsEventAttributes(): EventAttributes {
        return mutableMapOf(
            EventAttributeName.maxVideoTileCount to maxVideoTileCount,
            EventAttributeName.retryCount to retryCount,
            EventAttributeName.poorConnectionCount to poorConnectionCount,
            EventAttributeName.meetingDurationMs to if (meetingStartTimeMs == 0L) 0L else Calendar.getInstance().timeInMillis - meetingStartTimeMs,
            EventAttributeName.meetingStartDurationMs to if (meetingStartTimeMs == 0L) 0L else meetingStartTimeMs - meetingStartConnectingTimeMs
        )
    }

    override fun addMeetingHistoryEvent(historyEventName: MeetingHistoryEventName, timestampMs: Long) {
        historyEvents.add(MeetingHistoryEvent(historyEventName, timestampMs))
    }

    override fun getMeetingHistory(): List<MeetingHistoryEvent> = historyEvents
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

/**
 * [EventAnalyticsController] keeps track of events and notifies [EventAnalyticsObserver].
 * An event describes the success and failure conditions for the meeting session.
 */
interface EventAnalyticsController {
    /**
     * Publish an event with updated [EventAttributes].
     *
     * @param name: [EventName] - Name of event to publish
     * @param attributes: [EventAttributes] - Attributes of event to pass to builders.
     */
    fun publishEvent(name: EventName, attributes: EventAttributes? = null)

    /**
     * Push [MeetingHistoryEventName] to internal [MeetingStatsCollector].
     *
     * @param historyEventName: MeetingHistoryEventName - History event name to add.
     */
    fun pushHistory(historyEventName: MeetingHistoryEventName)

    /**
     * Retrieve meeting history.
     */
    fun getMeetingHistory(): List<MeetingHistoryEvent>

    /**
     * Retrieve common attributes, including deviceName, osName, and more.
     */
    fun getCommonEventAttributes(): EventAttributes

    /**
     * Add specified [EventAnalyticsObserver].
     *
     * @param observer: [EventAnalyticsObserver] - The observer to subscribe to events with.
     */
    fun addEventAnalyticsObserver(observer: EventAnalyticsObserver)

    /**
     * Remove specified [EventAnalyticsObserver].
     *
     * @param observer: [EventAnalyticsObserver] - The observer to unsubscribe from events with.
     */
    fun removeEventAnalyticsObserver(observer: EventAnalyticsObserver)
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

/**
 * [EventAnalyticsFacade] allows builders to listen to meeting analytics events
 * through adding/removing [EventAnalyticsObserver].
 *
 * For instance, if meeting start succeeded, you'll receive data
 * [EventAnalyticsObserver.onEventReceived] with name as [EventName] and attributes as [EventAttributes],
 * which is just [Map]. This will include attributes specific to the event.
 *
 * For more information about the attributes, please refer to [EventAttributeName].
 *
 * It can have additional attributes based on the event.
 */
interface EventAnalyticsFacade {
    /**
     * Subscribe to meeting event related data with an observer.
     *
     * @param observer: [EventAnalyticsObserver] - An observer to add to start receiving meeting events
     */
    fun addEventAnalyticsObserver(observer: EventAnalyticsObserver)

    /**
     * Unsubscribe from meeting event by removing the specified observer.
     *
     * @param observer: [EventAnalyticsObserver] - An observer to remove to stop receiving meeting events
     */
    fun removeEventAnalyticsObserver(observer: EventAnalyticsObserver)

    /**
     * Retrieve meeting history.
     */
    fun getMeetingHistory(): List<MeetingHistoryEvent>

    /**
     * Retrieve common attributes, including deviceName, osName, and more.
     */
    fun getCommonEventAttributes(): EventAttributes
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

/**
 * [EventAnalyticsObserver] handles events regarding to analytics.
 */
interface EventAnalyticsObserver {
    /**
     * Called when specific events occur during the meeting. Each event includes attributes of the event.
     *
     * For more information about the attributes, please refer to [EventAttributeName].
     *
     * One example could be [EventName] as [EventName.meetingStartSucceeded] and
     * attributes would be [MutableMap] of [EventAttributeName] and it's value.
     *
     * @param name: [EventName] - name of meeting event
     * @param attributes: [EventAttributes] - attributes of meeting event
     *
     * NOTE: all callbacks will be called on main thread.
     */
    fun onEventReceived(name: EventName, attributes: EventAttributes)
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

enum class EventAttributeName {
    /**
     * Name of device = Manufacturer of Device + Device Model
     */
    deviceName,

    /**
     * Manufacturer of Device
     */
    deviceManufacturer,

    /**
     * Model of Device
     */
    deviceModel,

    /**
     * Version of media SDK
     */
    mediaSdkVersion,

    /**
     * Operating system name
     */
    osName,

    /**
     * Operating system version
     */
    osVersion,

    /**
     * Name of SDK
     */
    sdkName,

    /**
     * Version of SDK
     */
    sdkVersion,

    /**
     * Timestamp of event occurrence
     */
    timestampMs,

    /**
     * AttendeeId
     */
    attendeeId,

    /**
     * External Meeting Id
     */
    externalMeetingId,

    /**
     * External Attendee Id
     */
    externalUserId,

    /**
     * Meeting Id
     */
    meetingId,

    /**
     * History of the meeting events in chronological order
     */
    meetingHistory,

    // Meeting Stats Event Attributes

    /**
     * Maximum number video tile shared during the meeting, including self video tile
     */
    maxVideoTileCount,

    /**
     * Duration of the meeting starting process
     */
    meetingStartDurationMs,

    /**
     * Duration of the meeting
     */
    meetingDurationMs,

    /**
     * Error message of the meeting
     */
    meetingErrorMessage,

    /**
     * Meeting Status [MeetingSessionStatus]
     */
    meetingStatus,

    /**
     * The number of poor connection count during the meeting from start to end
     */
    poorConnectionCount,

    /**
     * The number of meeting retry connection count during the meeting from start to end
     */
    retryCount,

    // Device Event Attributes - videoInputFailed

    /**
     * The error of video input selection such as starting camera
     */
    videoInputError;
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

import com.amazonaws.services.chime.sdk.meetings.internal.utils.JsonUtils

/**
 * [EventAttributes] describes meeting event.
 */
typealias EventAttributes = MutableMap<EventAttributeName, Any>

/**
 * Convert event attributes into JSON string.
 *
 * @return string
 */
fun EventAttributes.toJsonString(): String {
    return JsonUtils.marshal(this)
}

fun EventAttributes.toStringKeyMap(): Map<String, Any> = this.map { (k, v) -> k.name to v }.toMap()


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

/**
 * [EventName] represent sdk event that could help builders to analyze the data.
 */
enum class EventName {
    /**
     * The camera selection failed.
     */
    videoInputFailed,

    /**
     * The meeting will start.
     */
    meetingStartRequested,

    /**
     * The meeting started.
     */
    meetingStartSucceeded,

    /**
     * The meeting failed to start.
     */
    meetingStartFailed,

    /**
     * The meeting ended.
     */
    meetingEnded,

    /**
     * The meeting ended with failure.
     */
    meetingFailed
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

data class MeetingHistoryEvent(val meetingHistoryEventName: MeetingHistoryEventName, val timestamp: Long)


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

/**
 * [MeetingHistoryEventName] is a notable event (such as MeetingStartSucceeded) that occur during meeting.
 * Thus, this also includes events in [EventName].
 */
enum class MeetingHistoryEventName {
    /**
     * The microphone was selected.
     */
    audioInputSelected,

    /**
     * The camera selection failed.
     */
    videoInputFailed,

    /**
     * The camera was selected.
     */
    videoInputSelected,

    /**
     * The meeting failed to start.
     */
    meetingStartFailed,

    /**
     * The meeting will start.
     */
    meetingStartRequested,

    /**
     * The meeting started.
     */
    meetingStartSucceeded,

    /**
     * The meeting ended.
     */
    meetingEnded,

    /**
     * The meeting failed.
     */
    meetingFailed,

    /**
     * The meeting reconnected.
     */
    meetingReconnected;

    companion object {
        fun fromMeetingEvent(name: EventName): MeetingHistoryEventName {
            return when (name) {
                EventName.meetingStartSucceeded -> meetingStartSucceeded
                EventName.videoInputFailed -> videoInputFailed
                EventName.meetingStartRequested -> meetingStartRequested
                EventName.meetingStartFailed -> meetingStartFailed
                EventName.meetingEnded -> meetingEnded
                EventName.meetingFailed -> meetingFailed
            }
        }
    }
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.analytics

interface MeetingStatsCollector {
    /**
     * Increment meeting session retry count.
     */
    fun incrementRetryCount()

    /**
     * Increment poor connection count during the session.
     */
    fun incrementPoorConnectionCount()

    /**
     * Update max video tile count during the meeting.
     *
     * @param videoTileCount: Int - Current video tile count.
     */
    fun updateMaxVideoTile(videoTileCount: Int)

    /**
     * Update meeting start time.
     */
    fun updateMeetingStartTimeMs()

    /**
     * Update meeting start request time.
     */
    fun updateMeetingStartConnectingTimeMs()

    /**
     * Clear meeting stats.
     */
    fun resetMeetingStats()

    /**
     * Get the meeting stats attributes.
     *
     * @return [EventAttributes] - Event attributes of meeting stats.
     */
    fun getMeetingStatsEventAttributes(): EventAttributes

    /**
     * Get a list of meeting history events.
     *
     * @return [List<MeetingHistoryEvent>] - The list of meeting history events.
     */
    fun getMeetingHistory(): List<MeetingHistoryEvent>

    /**
     * Add a history meeting event.
     *
     * @param historyEventName: MeetingHistoryEventName - History event name to add.
     * @param timestampMs: Long - Timestamp of the event in millisecond.
     */
    fun addMeetingHistoryEvent(historyEventName: MeetingHistoryEventName, timestampMs: Long)
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.audiovideo

data class AttendeeInfo(val attendeeId: String, val externalUserId: String)

data class VolumeUpdate(val attendeeInfo: AttendeeInfo, val volumeLevel: VolumeLevel)

data class SignalUpdate(val attendeeInfo: AttendeeInfo, val signalStrength: SignalStrength)


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.amazonaws.services.chime.sdk.meetings.audiovideo

import com.amazonaws.services.chime.sdk.meetings.audiovideo.audio.AudioMode
import com.amazonaws.services.chime.sdk.meetings.audiovideo.audio.AudioRecordingPresetOverride
import com.amazonaws.services.chime.sdk.meetings.audiovideo.audio.AudioStreamType

/**
 * [AudioVideoConfiguration] represents the configuration to be used for audio and video during a
 * meeting session.
 *
 * @property audioMode: AudioMode - the audio mode in which the audio client should operate during
 * a meeting session.
 *
 * @property audioStreamType: AudioStreamType - the audio stream type in which the audio client
 * should operate during a meeting session.
 */
data class AudioVideoConfiguration @JvmOverloads constructor(
    val audioMode: AudioMode = AudioMode.Stereo48K,
    val audioStreamType: AudioStreamType = AudioStreamType.VoiceCall,
    val audioRecordingPresetOverride: AudioRecordingPresetOverride = AudioRecordingPresetOverride.None
)